用于模型上下文协议(MCP)的中间件层,能够监控、验证和转换AI工具交互。
模型上下文协议(MCP)是一种标准,允许AI助手(如Claude)与外部工具和服务进行交互。可以将其视为一种通用语言,使AI模型能够:
当你使用支持MCP的AI助手时,它可以代表你执行实际操作,使其在自动化和生产力方面非常强大。
虽然MCP的强大功能令人兴奋,但也提出了重要的问题:
这就是挂钩发挥作用的地方。就像Web应用程序使用中间件来处理身份验证、日志记录和请求处理一样,MCP也可以从类似的模式中受益。
我们的方法引入了一个“透明服务器”,位于AI和实际MCP工具之间:
AI助手 ←→ 透明服务器 ←→ 目标MCP服务器
↓↑
[挂钩]
当AI想要使用一个工具时,会发生以下情况:
1. 请求:AI → 透明服务器 → 挂钩1 → 挂钩2 → ... → 目标MCP服务器
2. 响应:AI ← 透明服务器 ← 挂钩2 ← 挂钩1 ← ... ← 目标MMCP服务器
透明服务器:
链条中的每个挂钩都可以:
为什么我们需要特定于MCP的挂钩?这关乎关注点分离以及大型语言模型(LLM)工具使用的独特挑战。
核心问题:MCP服务器设计用于做好一件事——提供工具。它们不应该被身份验证逻辑、审计轨迹或上下文特定的修改所困扰。此外,OAuth(MCP使用)缺乏对LLM交互所需的细粒度控制——无法表达“允许读取文件但仅限于/docs文件夹”或“允许调用API但基于内容进行速率限制”。
示例1:针对LLM的防护措施
你的MCP文件服务器提供了简单的文件操作。但是,LLM需要不同于人类用户的规则:
人类用户:可以精确点击他们需要的文件
LLM:可能会尝试读取整个目录树以“有所帮助”
防护措施挂钩:限制目录遍历深度,防止读取二进制文件,并设置文件大小上限——这些规则仅适用于LLM。
MCP服务器保持简单,而挂钩则增加了针对LLM的安全措施。
示例2:基于上下文的工具描述和提示
同一工具可能需要针对不同用例的不同描述:
标准获取工具:“检索网页内容”
在研究环境中:
“检索网页内容(首选学术来源,检查Sci-Hub)”
在企业环境中:
“检索网页内容(仅内部维基,阻止外部站点)”
自定义描述挂钩会根据您的上下文修改工具描述——这是原始MCP服务器不应处理的事情。
示例3:强制透明性与解释挂钩
LLM可以在不解释原因的情况下使用工具。解释挂钩添加了一个必需的“原因”参数:
没有挂钩:
AI:execute_sql("DROP TABLE users")
有解释挂钩:
AI:execute_sql("DROP TABLE users", reason="用户请求清理数据库")
这种修改发生在中间件层——MCP服务器不需要改变。
示例4:语义审计轨迹
MCP服务器返回原始响应。审计挂钩可以添加语义含义:
MCP服务器返回:{状态:"成功",影响行数:1523}
审计挂钩记录:
- 工具:数据库查询
- 动作:“批量更新客户电子邮件”
- 影响:修改了1523条记录
- 上下文:“作为从旧电子邮件域迁移的一部分”
- 风险级别:高(批量数据修改)
MCP服务器专注于数据库操作,而挂钩则处理合规性日志记录。
这个单体仓库包含了你需要的一切,以便为MCP添加中间件层:
@civic/passthrough-mcp-server 主要代理服务器,拦截MCP流量并将其路由到你的挂钩。这是使其他一切成为可能的基础。
@civic/hook-common
构建挂钩的共享实用程序和TypeScript类型。提供AbstractHook基类,使得创建新挂钩变得简单。
@civic/audit-hook 记录每个请求和响应,用于调试和合规性。非常适合了解你的AI正在做什么并维护审计轨迹。
@civic/guardrail-hook 实现安全规则以过滤和验证请求。包括一个示例,防止访问某些域,但可以根据需要扩展任何验证逻辑。
@civic/simple-log-hook 最简单的挂钩——仅仅记录到控制台。了解挂钩工作原理和构建自己的挂钩的良好起点。
@civic/explain-hook 向所有工具添加“原因”参数,鼓励AI解释其使用每个工具的原因。有助于透明性和调试。
@civic/custom-description-hook 根据配置替换工具描述。对于提供有关工具在你的环境中做什么的上下文特定信息很有用。
@civic/rate-limit-hook 对每个用户的工具调用实施速率限制。每分钟和每小时可配置限制,并带有明确的重试时间响应。
@civic/local-tools-hook 一个编程挂钩,允许直接将本地工具添加到透明MCP服务器,而无需单独的MCP服务器。非常适合在应用代码中定义自定义工具。
@civic/fetch-docs 一个简单的MCP服务器,抓取网页并将其转换为markdown。作为测试目标包含在内,用于透明服务器和挂钩。
@civic/whoami-server 一个集成Civic Auth的MCP服务器,识别认证用户。提供一个“whoami”工具,返回当前用户的信息,对于测试认证流程很有用。
git clone https://github.com/civicteam/mcp-hooks.git
cd mcp-hooks
pnpm install
pnpm build
# 终端1:启动fetch-docs MCP服务器
cd packages/fetch-docs
pnpm start
# 终端2:启动审计挂钩
cd packages/audit-hook
pnpm start
# 终端3:启动透明服务器
cd packages/passthrough-mcp-server
export TARGET_SERVER_URL="http://localhost:33005"
export HOOKS="http://localhost:33004"
pnpm start
现在,连接到端口34000的任何MCP客户端都会先由审计挂钩记录所有请求,然后再转发到fetch-docs服务器。
test/目录包含随时可用的配置:
cd test
./test.sh simple-log-passthrough.json # 观察基本日志记录
./test.sh audit-passthrough.json # 测试审计轨迹功能
./test.sh guardrail-passthrough.json # 测试安全过滤
构建自定义挂钩很简单:
mkdir packages/my-hook
cd packages/my-hook
pnpm init
pnpm add @civic/hook-common @trpc/server
import { AbstractHook, createHookRouter, ToolCallRequestHookResult } from "@civic/hook-common";
import { createHTTPServer } from "@trpc/server/adapters/standalone";
import type { CallToolRequest } from "@modelcontextprotocol/sdk/types.js";
class MyHook extends AbstractHook {
get name(): string {
return "my-hook";
}
async processCallToolRequest(request: CallToolRequest): Promise<ToolCallRequestHookResult> {
// 你的逻辑在这里
console.log(`处理:${toolCall.params.name}`);
// 继续请求
return {
resultType: "continue",
request: toolCall
};
}
}
// 启动服务器
const hook = new MyHook();
const router = createHookRouter(hook);
const server = createHTTPServer({ router, createContext: () => ({}) });
server.listen(33007);
export HOOKS="http://localhost:33007"
pnpm start
挂钩可以串联在一起:
export HOOKS="http://localhost:33004,http://localhost:33005,http://localhost:33006"
它们按顺序处理请求,按相反顺序处理响应。
挂钩可以返回三种类型的响应:
PORT:透明服务器的HTTP端口(默认:34000)TARGET_SERVER_URL:要转发请求的目标MCP服务器TARGET_SERVER_TRANSPORT:传输类型(httpStream,sse,stdio)HOOKS:逗号分隔的挂钩URL列表mcp-hooks/
├── packages/
│ ├── passthrough-mcp-server/ # 主代理服务器
│ ├── hook-common/ # 共享类型和实用程序
│ ├── audit-hook/ # 示例:日志记录挂钩
│ ├── guardrail-hook/ # 示例:安全挂钩
│ ├── simple-log-hook/ # 示例:最小挂钩
│ ├── explain-hook/ # 示例:透明挂钩
│ ├── custom-description-hook/ # 示例:转换挂钩
│ ├── rate-limit-hook/ # 示例:速率限制挂钩
│ ├── local-tools-hook/ # 示例:编程工具挂钩
│ ├── fetch-docs/ # 测试MCP服务器
│ └── whoami-server/ # 测试认证服务器
├── test/ # 测试配置
└── docs/ # 额外文档
所有挂钩都实现了这个简单的接口:
interface Hook {
name: string;
processCallToolRequest?(request: CallToolRequest): Promise<ToolCallRequestHookResult>;
processCallToolResult?(response: CallToolResult, originalCallToolRequest: CallToolRequest): Promise<ToolCallResponseHookResult>;
processToolsList?(request: ListToolsRequest): Promise<ListToolsRequestHookResult>;
processListToolsResult?(response: ListToolsResult, originalRequest: ListToolsRequest): Promise<ListToolsResponseHookResult>;
processToolCallTransportError?(error: unknown, originalCallToolRequest: CallToolRequest): Promise<ToolCallTransportErrorHookResult>;
processToolsListTransportError?(error: unknown, originalRequest: ListToolsRequest): Promise<ListToolsTransportErrorHookResult>;
}
我们欢迎贡献!请参阅我们的贡献指南以获取详细信息。
pnpm install # 安装依赖项
pnpm build # 构建所有包
pnpm test # 运行测试
pnpm lint # 检查代码风格
pnpm dev # 在监视模式下启动
MIT - 详情见LICENSE。