主题
MCP 工具注册表
本文引用的源码文件
本文引用的文件
目录
引言
本文聚焦 Agent 端的 MCP 工具注册表,解释其如何从配置中加载 MCP Server、建立连接、发现并缓存工具、暴露 HTTP API,以及扩展端如何消费这些工具。需要特别强调:Agent 是独立 Node.js 进程;修改 Agent 源码后必须重启进程,新接口和逻辑才会生效。
项目结构
MCP 相关代码主要分布在 Agent 端 agent/src/mcp 三个文件,并由 agent/src/server.js 暴露 HTTP 接口;扩展端通过 background 脚本拉取工具列表并动态注入到工具执行器。
graph TB subgraph "Agent 端" REG["mcp/registry.js<br/>注册表"] CLI["mcp/client.js<br/>MCP 客户端"] CFG["mcp/mcp-config.js<br/>配置缓存与写锁"] SRV["server.js<br/>HTTP 路由"] end subgraph "扩展端" BE["background/tool-executor.js<br/>拉取并注入 MCP 工具"] UI["side_panel/tool-panel.js<br/>服务级开关 UI"] AM["side_panel/agent-manager.js<br/>按服务聚合工具"] end SRV --> REG REG --> CLI REG --> CFG BE --> SRV UI --> BE AM --> BE
图表来源
- agent/src/server.js#L1900-L2021
- agent/src/mcp/registry.js#L1-L181
- agent/src/mcp/client.js#L1-L184
- agent/src/mcp/mcp-config.js#L1-L129
- src/background/tool-executor.js#L547-L588
- src/side_panel/tool-panel.js#L215-L244
- src/side_panel/agent-manager.js#L902-L928
章节来源
- agent/src/mcp/registry.js#L1-L181
- agent/src/mcp/client.js#L1-L184
- agent/src/mcp/mcp-config.js#L1-L129
- agent/src/server.js#L1900-L2021
- src/background/tool-executor.js#L547-L588
核心组件
- 注册表(registry.js):维护已连接的 MCP Client 实例,提供初始化、查询工具、调用工具、连接/断开服务器、关闭所有连接等能力。
- MCP 客户端(client.js):封装 Model Context Protocol SDK,负责创建传输层(stdio/SSE/streamableHttp/websocket)、连接远端 MCP Server、列出工具、调用工具、断开连接。
- 配置管理(mcp-config.js):读写
~/.ai-helper-agent/mcp_servers.json,带内存缓存、mtime 失效、写入互斥锁。 - HTTP 路由(server.js):将 MCP 管理操作映射为
/api/mcp/*接口,并在请求入口同步语言设置。 - 扩展端工具注入(tool-executor.js):根据全局开关、禁用服务器列表、强制下发列表,把 MCP 工具动态注入到扩展的工具执行体系。
- UI 分组与过滤(tool-panel.js、agent-manager.js):MCP 工具按服务维度展示,支持“服务级开放/关闭”。
章节来源
- agent/src/mcp/registry.js#L1-L181
- agent/src/mcp/client.js#L1-L184
- agent/src/mcp/mcp-config.js#L1-L129
- agent/src/server.js#L1900-L2021
- src/background/tool-executor.js#L547-L588
- src/side_panel/tool-panel.js#L215-L244
- src/side_panel/agent-manager.js#L902-L928
架构总览
MCP 工具注册表不是传统意义上的“文件系统扫描 + 模块加载”系统,而是“配置驱动 + 协议客户端 + 运行时连接池”的系统。工具发现来自远端 MCP Server 的 listTools,而不是本地目录扫描。
sequenceDiagram
participant Ext as "扩展端 Background"
participant Srv as "Agent server.js"
participant Reg as "registry.js"
participant Cli as "client.js"
participant Mps as "远端 MCP Server"
Ext->>Srv : GET /api/mcp/tools
Srv->>Reg : getMcpTools()
Reg-->>Srv : { success, tools[] }
Srv-->>Ext : JSON 工具列表
Ext->>Srv : POST /api/mcp/call { serverId, toolName, args }
Srv->>Reg : callMcpTool(serverId, toolName, args)
Reg->>Cli : client.callTool(toolName, args)
Cli->>Mps : MCP callTool
Mps-->>Cli : result
Cli-->>Reg : { success, content, raw }
Reg-->>Srv : 结果
Srv-->>Ext : JSON 响应
图表来源
- agent/src/server.js#L1916-L1922
- agent/src/server.js#L2018-L2021
- agent/src/mcp/registry.js#L89-L128
- agent/src/mcp/client.js#L96-L157
详细组件分析
注册表:registry.js
注册表是 MCP 能力的统一入口,职责包括:
- 启动时遍历配置中的启用服务器,逐个连接并缓存 Client。
- 提供扁平化工具列表,包含
serverId、serverName、name、description、inputSchema。 - 转发工具调用到对应 Client。
- 提供连接/断开/关闭全部连接的生命周期方法。
- 重新导出配置管理函数,供 server.js 直接调用。
关键行为要点:
- 未启用的服务器在初始化阶段被跳过。
- 连接失败会记录失败服务器列表并输出警告日志。
- 工具调用前校验 Client 是否存在且已连接。
- 关闭时对所有 Client 调用 disconnect,并清空 Map。
classDiagram
class Registry {
+initializeMcpRegistry() Promise
+getMcpServersStatus() Object
+getMcpTools(serverId?) Object
+callMcpTool(serverId, toolName, args) Promise
+connectMcpServer(serverId) Promise
+disconnectMcpServer(serverId) Promise
+shutdownMcpRegistry() Promise
}
class McpClient {
+connect() Promise
+callTool(toolName, args) Promise
+disconnect() Promise
+getStatus() Object
}
class Config {
+loadMcpConfig() Object
+addMcpServer(server) Promise
+removeMcpServer(serverId) Promise
+toggleMcpServer(serverId, enabled) Promise
}
Registry --> McpClient : "管理多个实例"
Registry --> Config : "读取/更新配置"
图表来源
- agent/src/mcp/registry.js#L1-L181
- agent/src/mcp/client.js#L35-L184
- agent/src/mcp/mcp-config.js#L37-L129
章节来源
MCP 客户端:client.js
McpClient 封装了 MCP SDK 的使用方式,支持多种传输协议:
- stdio:通过命令、参数、环境变量和工作目录启动子进程。
- SSE:需要 URL,可选 headers。
- streamableHttp:需要 URL,可选 headers。
- websocket:需要 URL。
连接流程:
- 根据配置创建传输层。
- 创建 SDK Client 并 connect。
- 获取 serverInfo 和 capabilities。
- 调用 listTools 缓存工具元数据。
- 标记 connected。
工具调用流程:
- 检查 connected。
- 调用 callTool。
- 解析 result.content 中的 text 内容。
- 返回 success/content/raw。
flowchart TD Start(["connect()"]) --> CreateTransport["根据 transport 类型创建传输层"] CreateTransport --> Validate["校验必要字段<br/>url/command"] Validate --> ConnectSDK["SDK Client.connect()"] ConnectSDK --> FetchMeta["获取 serverInfo / capabilities"] FetchMeta --> ListTools["listTools()"] ListTools --> CacheTools["缓存 tools 数组"] CacheTools --> MarkConnected["connected = true"] MarkConnected --> Done(["返回成功"])
图表来源
章节来源
配置管理:mcp-config.js
配置路径位于用户主目录下的 .ai-helper-agent/mcp_servers.json。该模块提供:
- 带 mtime 的内存缓存。
- 首次缺失时自动创建默认空配置。
- 解析失败时降级为空 servers 数组。
- 写入互斥锁,避免并发写破坏 JSON。
- add/remove/toggle 等原子化配置变更。
flowchart TD
Load["loadMcpConfig()"] --> CheckMtime{"缓存有效?"}
CheckMtime --> |是| ReturnCache["返回缓存"]
CheckMtime --> |否| Exists{"文件存在?"}
Exists --> |否| CreateDefault["创建默认配置并缓存"]
Exists --> |是| ReadFile["读取并解析 JSON"]
ReadFile --> ParseOk{"解析成功?"}
ParseOk --> |是| UpdateCache["更新缓存 mtime"]
ParseOk --> |否| Fallback["降级为空 servers 并记录错误"]
UpdateCache --> ReturnData["返回配置"]
Fallback --> ReturnData
图表来源
章节来源
HTTP 路由:server.js
server.js 将 MCP 能力暴露为 REST 接口,主要包括:
/api/mcp/servers:GET 获取服务器状态。/api/mcp/tools:GET 获取工具列表,可传serverId过滤。/api/mcp/call:POST 调用工具。/api/mcp/servers/connect、/api/mcp/servers/disconnect、/api/mcp/servers/toggle、/api/mcp/servers(POST/DELETE):管理服务器配置与连接。
每个请求进入时,server.js 会将当前请求的语言同步到各子模块,包括 MCP 客户端和注册表。
sequenceDiagram
participant C as "客户端"
participant R as "server.js 路由"
participant G as "registry.js"
participant L as "client.js"
C->>R : POST /api/mcp/call
R->>R : 校验 serverId/toolName
R->>G : callMcpTool(serverId, toolName, args)
G->>L : client.callTool(toolName, args)
L-->>G : { success, content, raw }
G-->>R : 结果
R-->>C : JSON 响应
图表来源
章节来源
扩展端工具注入:tool-executor.js
扩展端不会直接调用 MCP Server,而是先通过 Agent 的 /api/mcp/tools 拉取工具定义,再注入到扩展的工具执行器中。关键策略:
- 有全局 MCP 开关,关闭则跳过加载。
- 每次加载前先清理之前注册的 MCP 工具。
- 并行拉取工具列表和服务器状态。
- 忽略 disabled 服务器的工具。
- 支持 forcedMcpServerIds 强制下发,绕过 deny-list。
flowchart TD
Entry(["loadMcpTools()"]) --> GlobalCheck{"全局 MCP 开启?"}
GlobalCheck --> |否| Skip["跳过加载"]
GlobalCheck --> |是| Clean["清理旧 MCP 工具"]
Clean --> FetchAll["并行获取 tools 和 servers"]
FetchAll --> HasTools{"tools 非空?"}
HasTools --> |否| Skip
HasTools --> |是| BuildDisabled["构建禁用服务器集合"]
BuildDisabled --> Inject["注入可用工具到执行器"]
Inject --> Done(["完成"])
图表来源
章节来源
UI 分组与服务级开关:tool-panel.js 与 agent-manager.js
- tool-panel.js 明确说明:MCP 工具不参与工具级勾选,而是按“服务级开关”判定。
- agent-manager.js 将工具按 serverId 聚合为服务对象,便于 UI 展示和计数。
这意味着 MCP 的“分组”本质上是“服务分组”,而不是传统的 category 分类。
章节来源
依赖关系分析
- registry.js 依赖 client.js 和 mcp-config.js。
- server.js 依赖 registry.js 暴露的函数。
- 扩展端 tool-executor.js 依赖 Agent 的 HTTP API。
- UI 层依赖 tool-executor.js 提供的工具列表和服务聚合结果。
graph LR ToolExecutor["tool-executor.js"] --> ServerAPI["server.js /api/mcp/*"] ServerAPI --> Registry["registry.js"] Registry --> Client["client.js"] Registry --> Config["mcp-config.js"] UIPanel["tool-panel.js / agent-manager.js"] --> ToolExecutor
图表来源
- src/background/tool-executor.js#L547-L588
- agent/src/server.js#L1900-L2021
- agent/src/mcp/registry.js#L1-L181
- agent/src/mcp/client.js#L1-L184
- agent/src/mcp/mcp-config.js#L1-L129
- src/side_panel/tool-panel.js#L215-L244
- src/side_panel/agent-manager.js#L902-L928
章节来源
- agent/src/mcp/registry.js#L1-L181
- agent/src/mcp/client.js#L1-L184
- agent/src/mcp/mcp-config.js#L1-L129
- agent/src/server.js#L1900-L2021
- src/background/tool-executor.js#L547-L588
- src/side_panel/tool-panel.js#L215-L244
- src/side_panel/agent-manager.js#L902-L928
性能与并发控制
- 配置缓存:mcp-config.js 使用内存缓存和 mtime 判断,避免频繁读盘。
- 写入互斥:saveMcpConfig 使用 Promise 链式锁,保证并发写串行化。
- 工具列表缓存:client.js 在 connect 后缓存 tools 数组,后续 getMcpTools 直接读取内存。
- 扩展端并发拉取:tool-executor.js 并行拉取 tools 和 servers,减少等待时间。
- 连接复用:registry.js 以 serverId 为键缓存 McpClient,避免重复连接。
需要注意:
- 当前实现没有显式的工具调用限流或超时控制,工具调用耗时取决于远端 MCP Server。
- 如果某个 MCP Server 不稳定,建议通过 toggle 禁用,而不是无限重试。
章节来源
- agent/src/mcp/mcp-config.js#L14-L26
- agent/src/mcp/mcp-config.js#L69-L79
- agent/src/mcp/client.js#L114-L122
- src/background/tool-executor.js#L569-L573
- agent/src/mcp/registry.js#L19-L20
安全与错误处理
- 认证与 CORS:server.js 对大多数接口要求 Bearer Token,并对 Chrome 扩展来源放行 CORS。
- 输入校验:/api/mcp/call 校验 serverId 和 toolName。
- 错误传播:client.js 将 SDK 错误包装为国际化消息;registry.js 对未连接或未找到服务器返回错误。
- 日志:server.js 记录 mcp_tool_call 等系统事件,便于审计。
- 配置解析失败保护:mcp-config.js 解析失败时降级为空配置,不阻断启动。
潜在风险点:
- stdio 模式下 command/args/env 由配置决定,应视为可信配置来源;若配置来源不可信,需额外校验。
- MCP Server 返回的 tool description 和 inputSchema 会被透传到扩展端,UI 渲染时应做基本防护。
章节来源
- agent/src/server.js#L438-L443
- agent/src/server.js#L1916-L1922
- agent/src/mcp/client.js#L129-L157
- agent/src/mcp/registry.js#L116-L128
- agent/src/mcp/mcp-config.js#L52-L63
生命周期管理
- 启动初始化:server.js 启动时可通过 initializeMcpRegistry 连接所有启用的 MCP Server。
- 动态连接:connectMcpServer 支持运行时连接指定服务器,先断开已有连接再新建。
- 动态断开:disconnectMcpServer 清理 Client 并从 Map 删除。
- 关闭清理:shutdownMcpRegistry 遍历所有 Client 调用 disconnect 并清空 Map。
- 热重载限制:由于 MCP 连接和工具缓存存在于进程内存中,修改 Agent 源码后必须重启进程才能生效。
stateDiagram-v2 [*] --> 未连接 未连接 --> 已连接 : "connectMcpServer()" 已连接 --> 已连接 : "callMcpTool()" 已连接 --> 未连接 : "disconnectMcpServer()" 未连接 --> 已连接 : "initializeMcpRegistry()" 已连接 --> 已关闭 : "shutdownMcpRegistry()" 未连接 --> 已关闭 : "shutdownMcpRegistry()"
图表来源
章节来源
分组、命名空间与版本机制
- 分组:MCP 工具按 serverId 分组,UI 层按服务聚合展示。
- 命名空间:工具名称来自远端 MCP Server,registry.js 在扁平化时保留 serverId 和 serverName,形成“服务名/工具名”的隐式命名空间。
- 版本:client.js 在连接后获取 serverInfo 和 capabilities,但当前注册表并未将版本信息合并到工具列表中;serverInfo 主要用于状态展示。
因此,当前版本的“版本管理”更偏向“服务端能力探测”,而非严格的语义化版本控制。
章节来源
- agent/src/mcp/registry.js#L89-L108
- agent/src/mcp/client.js#L109-L122
- src/side_panel/agent-manager.js#L902-L928
最佳实践
- 错误处理
- 始终检查工具调用结果中的 success 字段。
- 对未连接服务器优先调用 disconnect 或 toggle 禁用。
- 捕获 MCP Server 返回的 error.message,避免向用户暴露内部堆栈。
- 性能优化
- 避免频繁重连同一 MCP Server;利用 registry.js 的连接复用。
- 批量刷新工具列表时,先清理旧工具再注入,避免脏状态。
- 对不常用的 MCP Server,按需连接,不要全部保持长连接。
- 安全检查
- 不要信任远端 MCP Server 返回的描述和 schema 作为执行依据。
- stdio 模式的 command/args/env 应由可信配置源生成。
- 对工具调用结果进行长度和内容过滤,防止注入。
- i18n
- 扩展端 UI 文案应从 shared locales 动态读取,不应硬编码中文。
- Agent 端语言通过 server.js 的请求入口同步到各子模块。
章节来源
- agent/src/mcp/client.js#L129-L157
- agent/src/mcp/registry.js#L116-L128
- agent/src/server.js#L428-L443
- src/shared/locales/en.js#L970-L985
自定义工具开发指南
如果你要开发一个 MCP Server,使其能被 ai-helper-agent 发现和调用,应遵循以下契约:
实现 MCP Server
- 支持 MCP 协议(JSON-RPC 2.0)。
- 暴露
listTools,返回工具元数据,至少包含 name、description、inputSchema。 - 暴露
callTool,接收 name 和 arguments,返回 content 和 isError。
选择传输方式
- stdio:适合本地命令行工具,配置 command、args、env、cwd。
- SSE/streamableHttp/websocket:适合网络服务,配置 url 和可选 headers。
在 Agent 端添加配置
- 编辑
~/.ai-helper-agent/mcp_servers.json,或通过 Agent 的/api/mcp/servers接口添加。 - 确保 id 唯一,enabled 为 true。
- 编辑
验证连接与工具
- 调用
/api/mcp/servers查看连接状态。 - 调用
/api/mcp/tools确认工具列表。 - 调用
/api/mcp/call测试工具执行。
- 调用
扩展端集成
- 扩展端会自动拉取工具列表并注入执行器。
- UI 上按服务维度展示,可在侧边栏或服务管理器中启用/禁用服务。
章节来源
- agent/src/mcp/client.js#L50-L94
- agent/src/mcp/mcp-config.js#L86-L98
- agent/src/server.js#L1846-L1913
- agent/src/server.js#L2013-L2021
- src/background/tool-executor.js#L547-L588
故障排查
常见问题与定位思路:
MCP 工具列表为空
- 检查全局 MCP 开关是否开启。
- 检查服务器是否 enabled。
- 检查
/api/mcp/servers中 connected 是否为 true。 - 检查远端 MCP Server 是否正确实现 listTools。
工具调用失败
- 检查 serverId 和 toolName 是否正确。
- 检查 Client 是否 connected。
- 查看 server.js 中 mcp_tool_call 日志。
- 检查 client.js 返回的错误消息。
配置保存失败
- 检查
~/.ai-helper-agent/mcp_servers.json权限。 - 检查 JSON 格式是否合法。
- 观察 mcp-config.js 的解析错误日志。
- 检查
连接反复失败
- 检查 command/url 是否正确。
- 检查网络可达性。
- 考虑暂时 toggle 禁用该服务,避免影响其他工具。
扩展端看不到 MCP 工具
- 检查 tool-executor.js 是否成功拉取 tools。
- 检查 disabledServerIds 是否包含目标服务。
- 检查 UI 层的服务级开关是否排除该服务。
章节来源
- src/background/tool-executor.js#L547-L588
- agent/src/mcp/registry.js#L116-L128
- agent/src/mcp/client.js#L129-L157
- agent/src/mcp/mcp-config.js#L52-L79
- src/side_panel/tool-panel.js#L215-L244
结论
MCP 工具注册表是一个以配置为中心、以协议客户端为核心的工具扩展机制。它不依赖本地文件系统扫描,而是通过 MCP 协议从远端 Server 发现工具;它通过注册表统一管理连接、缓存工具、转发调用;扩展端则负责拉取工具定义并注入执行体系。对于开发者而言,最重要的是保证 MCP Server 协议正确、配置可信、错误可观测,并在 Agent 进程变更后及时重启。