Skip to content

MCP 客户端实现 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与资源管理
  8. 配置选项与连接参数调优
  9. 故障排查与调试指南
  10. 结论

引言 ​

本文面向 MCP(Model Context Protocol)客户端实现,聚焦 Agent 端如何作为 MCP 客户端接入外部工具服务器。文档覆盖以下目标:

  • 连接建立过程与四种传输协议差异:stdio、SSE、Streamable HTTP、WebSocket。
  • 消息序列化与反序列化机制,以及工具自动发现流程。
  • 远程工具调用完整链路:参数校验、请求构建、响应处理、错误处理与重试策略。
  • 连接池管理、超时控制、资源清理策略。
  • 客户端配置项、连接参数调优与故障恢复方案。
  • 实际连接示例与调试技巧。

本项目采用双进程架构:Chrome 扩展端通过本地 HTTP/WebSocket 调用 Agent;Agent 再作为 MCP 客户端连接第三方 MCP Server。MCP 客户端相关逻辑集中在 agent/src/mcp 目录下,由 server.js 暴露 REST API 供扩展端调用。

项目结构 ​

MCP 客户端实现主要位于 Agent 端的 agent/src/mcp 目录,包含三个核心文件:

  • client.js:封装单个 MCP 客户端实例,负责传输层选择、连接、工具列表获取和工具调用。
  • registry.js:维护已连接的 MCP 客户端映射表,提供批量初始化、状态查询、工具扁平化、单服务连接/断开、全局关闭等能力。
  • mcp-config.js:持久化 MCP Server 配置,提供带缓存的加载、写入锁、增删改查接口。

Agent 主服务 server.js 将 MCP 管理能力暴露为 REST API,例如添加/删除/启用/禁用 MCP Server、连接/断开、查询工具列表、调用工具等。

graph TB
subgraph "扩展端"
BG["Background 脚本<br/>工具注册与调用"]
end
subgraph "Agent 端"
SRV["HTTP 服务<br/>/api/mcp/* 路由"]
REG["MCP 注册表<br/>registry.js"]
CFG["MCP 配置管理<br/>mcp-config.js"]
CLI["MCP 客户端<br/>client.js"]
end
subgraph "外部 MCP 服务器"
STDIO["本地子进程<br/>stdio 传输"]
SSE_SVR["SSE 服务端"]
HTTP_SVR["Streamable HTTP 服务端"]
WS_SVR["WebSocket 服务端"]
end
BG --> SRV
SRV --> REG
REG --> CLI
REG --> CFG
CLI --> STDIO
CLI --> SSE_SVR
CLI --> HTTP_SVR
CLI --> WS_SVR

图表来源

章节来源

核心组件 ​

本节从职责边界角度梳理 MCP 客户端相关组件。

组件主要职责关键方法/属性对外暴露点
McpClient单个 MCP 客户端生命周期管理_createTransport()、connect()、callTool()、disconnect()、getStatus()被 registry.js 使用
registry.js多 MCP 客户端注册表、工具聚合、连接管理initializeMcpRegistry()、getMcpServersStatus()、getMcpTools()、callMcpTool()、connectMcpServer()、disconnectMcpServer()、shutdownMcpRegistry()被 server.js 使用
mcp-config.jsMCP Server 配置文件读写、内存缓存、写入互斥锁loadMcpConfig()、saveMcpConfig()、addMcpServer()、removeMcpServer()、toggleMcpServer()被 registry.js 使用
server.js MCP 路由将 MCP 操作暴露为 HTTP API/api/mcp/servers、/api/mcp/servers/connect、/api/mcp/servers/disconnect、/api/mcp/tools、/api/mcp/call被扩展端 Background 调用

章节来源

架构总览 ​

MCP 客户端在 Agent 中处于“扩展端 → Agent → 外部 MCP Server”链路的中间层。整体数据流如下:

sequenceDiagram
participant Ext as "扩展端 Background"
participant Api as "Agent HTTP API"
participant Reg as "MCP 注册表"
participant Cli as "MCP 客户端"
participant Tr as "传输层"
participant Srv as "外部 MCP 服务器"
Ext->>Api : "POST /api/mcp/servers/connect"
Api->>Reg : "connectMcpServer(serverId)"
Reg->>Cli : "new McpClient(config)"
Cli->>Tr : "_createTransport()"
Tr-->>Cli : "Stdio/SSE/StreamableHTTP/WebSocket"
Cli->>Srv : "connect + initialize"
Srv-->>Cli : "serverInfo + capabilities"
Cli->>Srv : "listTools"
Srv-->>Cli : "tools[]"
Cli-->>Reg : "connected = true, tools"
Reg-->>Api : "{ success, toolCount }"
Api-->>Ext : "连接结果"

图表来源

详细组件分析 ​

McpClient:单个 MCP 客户端实现 ​

McpClient 是 MCP 客户端的核心类,封装了传输层选择、连接、工具发现、工具调用和资源释放。

传输层选择 ​

_createTransport() 根据 serverConfig.transport 创建对应传输实例:

  • stdio:默认值,需要 command、可选 args、env、cwd。
  • sse:需要 url,支持通过 headers 注入请求头。
  • streamableHttp:需要 url,支持通过 headers 注入请求头。
  • websocket:需要 url。

如果缺少必要字段,会抛出国际化错误信息,例如“SSE 传输需要提供 url”“stdio 传输需要提供 command”。

flowchart TD
Start(["创建传输层"]) --> CheckType["读取 transport 类型"]
CheckType --> |sse| CheckSseUrl["检查 url 是否存在"]
CheckType --> |streamableHttp| CheckHttpUrl["检查 url 是否存在"]
CheckType --> |websocket| CheckWsUrl["检查 url 是否存在"]
CheckType --> |stdio| CheckCmd["检查 command 是否存在"]
CheckSseUrl --> |存在| CreateSse["创建 SSEClientTransport"]
CheckSseUrl --> |不存在| ThrowSse["抛出 SSE 缺少 url 错误"]
CheckHttpUrl --> |存在| CreateHttp["创建 StreamableHTTPClientTransport"]
CheckHttpUrl --> |不存在| ThrowHttp["抛出 StreamableHTTP 缺少 url 错误"]
CheckWsUrl --> |存在| CreateWs["创建 WebSocketClientTransport"]
CheckWsUrl --> |不存在| ThrowWs["抛出 WebSocket 缺少 url 错误"]
CheckCmd --> |存在| CreateStdio["创建 StdioClientTransport"]
CheckCmd --> |不存在| ThrowStdio["抛出 stdio 缺少 command 错误"]

图表来源

连接与工具发现 ​

connect() 执行以下步骤:

  1. 若已连接则直接返回成功。
  2. 创建传输层实例。
  3. 创建 SDK 客户端实例,名称固定为 ai-helper-agent,版本固定为 1.3.4。
  4. 调用 SDK 的 connect 完成握手。
  5. 读取服务器版本和能力。
  6. 调用 listTools 获取工具列表并缓存到 this.tools。
  7. 设置 connected = true,返回连接结果。

连接失败时会调用 disconnect() 清理资源,并返回包含错误信息的对象。

sequenceDiagram
participant C as "McpClient"
participant T as "传输层"
participant SDK as "SDK Client"
participant S as "MCP 服务器"
C->>C : "connect()"
alt 已连接
C-->>C : "直接返回成功"
else 未连接
C->>T : "_createTransport()"
T-->>C : "transport 实例"
C->>SDK : "new Client({ name, version })"
C->>SDK : "connect(transport)"
SDK->>S : "initialize"
S-->>SDK : "serverInfo, capabilities"
C->>SDK : "listTools"
SDK->>S : "tools/list"
S-->>SDK : "tools[]"
SDK-->>C : "tools"
C->>C : "connected = true"
C-->>C : "返回 { success, toolCount }"
end

图表来源

工具调用 ​

callTool() 的流程:

  1. 检查是否已连接,未连接则返回“MCP Server 未连接”错误。
  2. 调用 SDK 的 callTool,传入工具名和参数。
  3. 如果返回结果中包含 error,转换为失败响应。
  4. 否则从 result.content 中提取 type === 'text' 的内容,拼接为文本。
  5. 返回 { success, content, raw }。
  6. 捕获异常后返回国际化错误信息。

当前实现没有显式重试逻辑,但上层 Background 侧对工具调用结果有重试机制。

flowchart TD
Start(["调用工具"]) --> Connected{"是否已连接"}
Connected --> |否| ReturnNotConnected["返回未连接错误"]
Connected --> |是| CallSdk["调用 SDK callTool"]
CallSdk --> HasError{"返回结果是否包含 error"}
HasError --> |是| ReturnError["返回失败错误"]
HasError --> |否| ExtractText["提取 text 类型内容"]
ExtractText --> JoinText["拼接为字符串"]
JoinText --> ReturnSuccess["返回成功结果"]
CallSdk --> CatchErr["捕获异常"]
CatchErr --> ReturnCallError["返回调用异常错误"]

图表来源

连接状态与资源清理 ​

disconnect() 会:

  • 设置 connected = false。
  • 清空 tools。
  • 尝试关闭 SDK 客户端。
  • 尝试关闭传输层。
  • 将引用置空。

getStatus() 返回当前服务的 ID、名称、连接状态、工具数量和服务器信息。

章节来源

registry.js:MCP 工具注册表 ​

registry.js 维护一个 Map<serverId, McpClient>,提供多服务管理、工具扁平化和统一调用入口。

启动时初始化 ​

initializeMcpRegistry() 会:

  1. 加载 MCP 配置。
  2. 遍历所有启用的 server。
  3. 为每个 server 创建 McpClient 并连接。
  4. 记录成功连接数和失败列表。
  5. 输出日志提示连接情况。

工具列表获取 ​

getMcpTools() 会:

  1. 遍历所有已连接 client。
  2. 过滤掉未连接或指定 serverId 不匹配的服务。
  3. 将每个工具的 name、description、inputSchema 扁平化为统一结构。
  4. 返回 { success, tools }。

工具调用 ​

callMcpTool() 会:

  1. 查找 serverId 对应的 client。
  2. 检查 client 是否存在且已连接。
  3. 调用底层 client.callTool()。
  4. 返回调用结果。

连接管理 ​

  • connectMcpServer():先断开已有连接,再新建 client 并连接。
  • disconnectMcpServer():断开指定服务并从 Map 移除。
  • shutdownMcpRegistry():Agent 关闭时统一断开所有连接并清空 Map。
classDiagram
class Registry {
-clients : Map
+initializeMcpRegistry() Promise
+getMcpServersStatus() Object
+getMcpTools(serverId?) Object
+callMcpTool(serverId, toolName, args) Promise
+connectMcpServer(serverId) Promise
+disconnectMcpServer(serverId) Promise
+shutdownMcpRegistry() Promise
}
class McpClient {
+serverId
+serverName
+connected
+tools
+connect() Promise
+callTool(toolName, args) Promise
+disconnect() Promise
+getStatus() Object
}
Registry --> McpClient : "管理多个实例"

图表来源

章节来源

mcp-config.js:MCP 配置管理 ​

该模块负责 MCP Server 配置的持久化与内存缓存。

配置路径与缓存 ​

  • 配置文件路径:用户目录下的 .ai-helper-agent/mcp_servers.json。
  • 内存缓存:configCache 和 configCacheMtime,避免重复解析。
  • 写入互斥锁:writeLock 保证并发写安全。

主要接口 ​

  • loadMcpConfig():带 mtime 检测的加载函数,首次不存在时创建默认空配置。
  • saveMcpConfig():加锁写入并更新缓存。
  • addMcpServer():新增或更新 server。
  • removeMcpServer():按 id 删除。
  • toggleMcpServer():切换 enabled 状态。
flowchart TD
Load["loadMcpConfig()"] --> CheckMtime["检查文件 mtime"]
CheckMtime --> |命中缓存| ReturnCache["返回缓存"]
CheckMtime --> |未命中| CheckFile{"文件是否存在"}
CheckFile --> |不存在| CreateDefault["创建默认空配置"]
CheckFile --> |存在| ReadFile["读取并解析 JSON"]
CreateDefault --> SaveCache["保存缓存"]
ReadFile --> ValidateServers["确保 servers 字段存在"]
ValidateServers --> SaveCache
SaveCache --> ReturnResult["返回配置"]

图表来源

章节来源

server.js:MCP REST API 路由 ​

server.js 将 MCP 管理能力暴露为 HTTP API,关键路由如下:

路由方法功能参数校验
/api/mcp/serversPOST添加 MCP Server校验 id、http 需要 url、stdio 需要 command、stdio 命令安全检查
/api/mcp/serversDELETE删除 MCP Server校验 id
/api/mcp/servers/connectPOST连接指定 MCP Server校验 id
/api/mcp/servers/disconnectPOST断开指定 MCP Server校验 id
/api/mcp/servers/togglePOST启用/禁用 MCP Server校验 id 和 enabled;禁用时先断开连接
/api/mcp/callPOST调用 MCP 工具校验 serverId 和 toolName
/api/mcp/serversGET获取所有 MCP Server 状态无参数
/api/mcp/toolsGET获取所有 MCP 工具可选 serverId

其中,stdio 传输会对 command + args 进行安全校验,防止 MCP 成为命令管控后门。

sequenceDiagram
participant Ext as "扩展端"
participant Api as "Agent API"
participant Reg as "MCP 注册表"
Ext->>Api : "POST /api/mcp/call"
Api->>Api : "校验 serverId、toolName"
Api->>Reg : "callMcpTool(serverId, toolName, args)"
Reg->>Reg : "查找 client"
Reg->>Reg : "检查 connected"
Reg->>Reg : "调用底层 client.callTool"
Reg-->>Api : "返回结果"
Api-->>Ext : "JSON 响应"

图表来源

章节来源

依赖关系分析 ​

MCP 客户端实现的依赖关系如下:

graph LR
Server["server.js"] --> Registry["registry.js"]
Registry --> Config["mcp-config.js"]
Registry --> Client["client.js"]
Client --> SDK["@modelcontextprotocol/sdk"]
Client --> TransportStdio["StdioClientTransport"]
Client --> TransportSse["SSEClientTransport"]
Client --> TransportHttp["StreamableHTTPClientTransport"]
Client --> TransportWs["WebSocketClientTransport"]

图表来源

章节来源

性能与资源管理 ​

连接池管理 ​

当前实现不是传统意义上的连接池,而是“注册表 + 单例 per server”模式:

  • 每个启用的 MCP Server 对应一个 McpClient 实例。
  • registry.js 中的 clients Map 充当轻量级连接池。
  • 重启或手动重连时,会先断开旧连接再创建新实例。

这种设计适合 MCP Server 数量较少、连接数可控的场景。

超时控制 ​

  • McpClient 本身没有显式超时配置。
  • 超时控制主要由 SDK 和传输层实现决定。
  • Agent 层的通用请求超时逻辑不在 MCP 客户端内部体现。

资源清理策略 ​

  • disconnect() 会关闭 SDK 客户端和传输层。
  • shutdownMcpRegistry() 会在 Agent 关闭时统一清理所有连接。
  • 配置写入使用互斥锁,避免并发写导致文件损坏。

工具发现与缓存 ​

  • 工具列表在连接成功后立即拉取并缓存到 client.tools。
  • Background 侧对 MCP 工具列表有 30 秒缓存,减少频繁网络请求。

章节来源

配置选项与连接参数调优 ​

MCP Server 配置字段 ​

字段类型必填条件说明
idstring所有传输服务唯一标识
namestring可选显示名称,默认为 id
transportstring可选传输类型:stdio、sse、streamableHttp、websocket
urlstringhttp 类传输必填SSE、Streamable HTTP、WebSocket 需要 URL
commandstringstdio 传输必填本地子进程命令
argsarraystdio 传输可选子进程参数数组
envobjectstdio 传输可选子进程环境变量
cwdstringstdio 传输可选子进程工作目录
headersobjecthttp 类传输可选自定义请求头
enabledboolean可选是否启用,影响启动时自动连接

传输协议适用场景 ​

传输协议适用场景优点注意事项
stdio本地 MCP Server 子进程简单、低延迟、适合本地工具需命令安全检查,敏感命令可能被拦截
SSE单向流式 HTTP 服务兼容现有 HTTP 基础设施SDK 升级计划中标记为 deprecated
streamableHttp现代 MCP Server官方推荐、支持更多特性需要服务端支持 Streamable HTTP
websocket双向实时通信低延迟、适合长连接非官方标准,兼容性需谨慎评估

连接参数调优建议 ​

  • stdio:优先使用白名单命令,避免高危命令;合理设置 env 传递敏感配置。
  • SSE/Streamable HTTP:通过 headers 注入认证头;注意 URL 必须有效。
  • WebSocket:仅在与服务端明确兼容时使用。
  • 连接重试:当前 MCP 客户端无内置重试,可在上层实现指数退避重试。

章节来源

故障排查与调试指南 ​

常见错误与定位 ​

现象可能原因排查步骤
连接失败缺少 url/command、URL 无效、命令被拦截检查 transport 对应必填字段;查看 stdio 命令安全检查日志
工具列表为空服务器未返回 tools、连接未成功检查 connect() 返回值;确认服务器支持 tools/list
工具调用失败参数不合法、服务器返回 error、网络异常查看 callTool() 返回的 error 字段;检查输入 schema
服务未连接未调用 connect、服务被禁用、连接断开检查 registry.js 中 client 状态;确认 enabled 字段

调试技巧 ​

  1. 使用测试服务器:agent/test/mcp-test-server.cjs 提供本地 stdio MCP 服务器,可用于验证客户端行为。
  2. 运行单元测试:agent/test/mcp-client-test.js 演示连接、状态查询、工具调用和断开的完整流程。
  3. 查看日志:Agent 端会输出 [MCP] 前缀的连接和调用日志。
  4. 检查配置:确认 ~/.ai-helper-agent/mcp_servers.json 中配置正确。
  5. 浏览器控制台:扩展端 Background 会缓存 MCP 工具列表,可通过存储键 mcpTools 查看。

实际连接示例 ​

以下示例描述如何通过 Agent API 连接一个 stdio MCP Server:

  1. 添加 MCP Server:
    • 调用 POST /api/mcp/servers。
    • body 包含 id、name、transport=stdio、command、args、enabled=true。
  2. 连接 MCP Server:
    • 调用 POST /api/mcp/servers/connect。
    • body 包含 id。
  3. 获取工具列表:
    • 调用 GET /api/mcp/tools。
  4. 调用工具:
    • 调用 POST /api/mcp/call。
    • body 包含 serverId、toolName、args。

章节来源

结论 ​

本实现以 McpClient 为核心,结合 registry.js 的多服务管理和 mcp-config.js 的配置持久化,提供了完整的 MCP 客户端能力。其特点包括:

  • 支持四种传输协议,适配不同 MCP Server 部署形态。
  • 工具自动发现流程清晰,连接成功后立即拉取工具列表。
  • 工具调用链路简洁,错误信息经过国际化处理。
  • 配置管理具备内存缓存和写入锁,提升安全性和性能。
  • 当前实现未内置连接池和重试机制,但可通过上层扩展实现。

未来可考虑的方向包括:

  • 增加连接超时和重试策略。
  • 支持 MCP SDK v2.x 的新特性,如 server/discover、Mcp-Method header 等。
  • 对 SSE 和 WebSocket 传输进行兼容性评估和迁移规划。
  • 增强工具参数校验和 schema 验证能力。