Skip to content

McpHub:MCP 服务器生命周期管理

源码版本v4.0.10

职责

McpHub 是 Cline 接入外部 MCP 服务器的中央枢纽。它负责读取 cline_mcp_settings.json 里配置的所有服务器,为每个服务器建立一条独立的 client + transport 连接,然后把暴露出来的工具 (tool)、资源 (resource)、提示 (prompt) 缓存到内存里供主 agent 调用。整条 MCP 工具链——从配置变更、文件监听、连接/重连、能力发现、到 tools/call 调用——都收敛到这一个类,主 Task 不需要直接碰 MCP SDK。

它的位置在「主 agent loop」和「具体 MCP 协议」之间。Task 想调一个 MCP 工具时,通过 McpHub.callTool (callTool:1233) 走出去;UI 想看当前服务器列表,通过 getServers() 拿到一个排序后的快照 (getServers:112)。一个 Extension 实例里只有一个 McpHub,但 connections: McpConnection[] 数组里每条都是独立 transport,挂了不影响别的。

设计动机

  • 每服务器一个 client:不同 MCP 服务器有不同 capabilities、不同配置、不同错误处理,独立 client 让重连和作用域隔离都简单 (per-server client:363)。
  • 统一三种 transport:stdio 跑本地进程、sse 走 Server-Sent Events、streamableHttp 用新版 HTTP 流,在 connectToServerswitch 里分发 (transport switch:382)。
  • **文件监听 + 内部更新双通道watchMcpSettingsFile 监听外部编辑器改配置,isUpdatingClineSettings 标记内部自己写时跳过触发 (isUpdatingClineSettings:76),避免回环。
  • OAuth 按需懒加载:只有 sse/streamableHttp 才找 McpOAuthManager 要 authProvider,stdio 不走 (authProvider setup:377)。
  • 企业策略前置于连接:企业部署可以禁 marketplace、设白名单,在 stdio 连接前就拦截掉 (enterprise allowlist:310)。
  • UID 缩短工具名:每个服务器分配一个 c + nanoid(5) 的短 uid,避免 MCP 工具名拼成长串 (getMcpServerKey:130)。

关键文件

数据流

connectToServer 进来先做三件事:校验企业策略、构造 disabled 占位或真实 client、选 transport。transport 选完建 McpConnection 推进数组,然后 client.connect(transport) 真正握手。握手成功后才注册通知回调并拉四份能力清单:

typescript
// apps/vscode/src/services/mcp/McpHub.ts
connection.server.status = "connected"
connection.server.error = ""

// Register notification handler for real-time messages
try {
  // ...
  connection.client.setNotificationHandler(NotificationMessageSchema as any, async (notification: any) => {
    // ...
    if (this.notificationCallback) {
      this.notificationCallback(name, level, message)
    } else {
      this.pendingNotifications.push({ serverName: name, level, message, timestamp: Date.now() })
    }
  })
} catch (error) {
  Logger.error(`[MCP Debug] Error setting notification handlers for ${name}:`, error)
}

// Initial fetch of tools, resources, and prompts
connection.server.tools = await this.fetchToolsList(name)
connection.server.resources = await this.fetchResourcesList(name)
connection.server.resourceTemplates = await this.fetchResourceTemplatesList(name)
connection.server.prompts = await this.fetchPromptsList(name)

这段在 post-connect setup:584 附近。连接建好后,外部工具调用都走 callTool——查连接、按 config.timeout 算超时、发 tools/call (tools/call request:1270)。资源读类似,走 resources/read (resources/read:1183)。

边界与失败

  • OAuth 缺失即降级:client.connectUnauthorizedError 时,不报错而是把 connection 标成 oauthRequired: true,等用户在前端授权 (UnauthorizedError branch:556)。
  • disabled 服务器占位但不连接:disabled 配置仍进 connections 数组以便 UI 显示,但 client/transport 都是 null (disabled placeholder:339)。
  • stdio stderr 分类:stderr 里出现 error 字样才记 error,否则当 info log (stderr classification:420)。
  • streamableHttp 404 当 405:很多服务器把 SSE 不支持时该返 405 错返 404,这里在 fetch 层把 404 归一成 405 让 SDK 接受 (404 to 405:499)。
  • SSE token 动态刷新:sse 分支用自定义 fetch 每次现取 authProvider.tokens(),否则 token 过期后重连会一直 401 (dynamic token fetch:459)。
  • streamableHttp 重连交给 handler:StreamableHttpReconnectHandler 拿到 connectToServer 闭包,自己决定重连节奏 (reconnect handler:519)。
  • 通知暂存:没有活动 Task 时,服务器来的 notification 先存 pendingNotifications,等下一个 Task 起来再取 (pendingNotifications:631)。

小结

McpHub 把 MCP 协议细节全收进自己,对主 agent 暴露 callTool/readResource/getServers 三个口子。想看它如何被工具层包装成 use_mcp_tool 可以读 cap-tools/use-mcp-tool;想看 subagent 如何隔离自己的上下文窗口可以读 subagent;想看主 Task 怎么把 MCP 服务器列表塞进系统提示可以读 agent-loop/task-class

对照官方资料:Cline 文档 · README