MCP 客户端集成:连接生命周期与工具 URI 路由
MCP 客户端集成:连接生命周期与工具 URI 路由
学习目标
本章要解决的核心问题:当 AI 编程助手需要连接多个外部 MCP 服务器时,如何管理连接的生命周期、路由工具调用、处理认证并支持配置热更新?
你将学到:
MCPManagerSingleton单例模式的多连接管理mcp://URI 协议的工具路由机制- MCP OAuth 认证流程
- 配置变更时的智能连接热更新
前置知识
本章涉及 MCP 协议的通用原理,建议先阅读:
下文假设你已理解 MCP 的 Stdio/SSE 传输层和 JSON-RPC 2.0 消息协议,直接聚焦 Continue 的客户端实现。
项目实践
MCPManagerSingleton:多连接中枢
Continue 使用单例模式管理所有 MCP 连接:
class MCPManagerSingleton { private static instance: MCPManagerSingleton; public connections: Map<string, MCPConnection> = new Map();
createConnection(id: string, options: InternalMcpOptions): MCPConnection getConnection(id: string): MCPConnection | undefined async refreshConnection(id: string): Promise<void> async setConnections(servers: InternalMcpOptions[], forceRefresh: boolean): void async shutdown(): void}核心职责:
- 创建连接:首次访问时创建,已存在则复用
- 连接追踪:通过
id索引所有连接 - 批量同步:
setConnections比对新旧服务器列表,删除不再需要的连接,创建新的连接 - 状态管理:维护每个服务器的运行状态(connected, disconnected, error)
- 生命周期:
shutdown时中止所有连接并清理资源
连接热更新策略
当用户修改 Continue 配置中的 MCP 服务器列表时,setConnections 执行智能更新:
新的服务器列表(从配置读取) ↓遍历现有连接 → 如果不在新列表中 → 断开并删除 ↓遍历新列表 → 如果不在现有连接中 → 创建新连接 ↓如果已存在但配置变更 → 更新选项元数据 ↓触发 onConnectionsRefreshed 回调 → 重载配置关键优化:连接选项比对。Continue 在判断是否需要重建连接时,比较传输层选项(transport type、command、args 等)。只有传输层选项变化时才重建连接,仅名称或图标变化时只更新元数据。
mcp:// URI 路由
Continue 定义了一套自定义 URI 协议来路由 MCP 工具调用:
mcp://{serverId}/{toolName}路由流程:
核心函数:
function encodeMCPToolUri(mcpId: string, toolName: string): string { return `mcp://${encodeURIComponent(mcpId)}/${encodeURIComponent(toolName)}`;}
function decodeMCPToolUri(uri: string): [string, string] | null { const url = new URL(uri); if (url.protocol !== "mcp:") return null; return [ decodeURIComponent(url.hostname), decodeURIComponent(url.pathname).slice(1), ];}在 callTool.ts 中,三种工具调用方式统一处理:
| 协议 | 调用方式 |
|---|---|
http:// / https:// | HTTP POST 到外部工具服务 |
mcp:// | 通过 MCPManager 路由到对应 MCP client |
| 内置工具名 | 直接调用 implementations/ 中的实现 |
OAuth 认证流程
MCP 服务器可能需要 OAuth 认证。Continue 的认证流程:
用户启用需要认证的 MCP 服务器 ↓调用 mcp/startAuthentication ↓MCPManager 设置状态为 "authenticating" ↓启动 OAuth 流程(打开浏览器 → 用户授权 → 回调) ↓收到授权码 → 交换 access token ↓token 存储(通过 IDE 的 SecretStorage) ↓refreshConnection → 使用新 token 重建连接 ↓状态更新为 "connected"认证解除:
用户调用 mcp/removeAuthentication ↓清除存储的 token ↓refreshConnection → 以未认证状态重建连接问题与规避
陷阱 1:MCP 连接失败导致整个配置加载失败
现象:某个 MCP 服务器不可用,Continue 的配置加载卡住。
规避:MCPManager 的连接操作都有超时和错误处理。setConnections 中使用 Promise.allSettled 批量关闭连接,确保一个连接失败不影响其他连接。refreshConnection 也有独立的 AbortController 可中止。
陷阱 2:MCP 工具调用延迟
现象:通过 MCP 调用外部工具比内置工具慢很多。
原因:MCP 通常使用 stdio 传输层,需要启动子进程。首次调用时进程启动可能有 1-2 秒延迟。
规避:MCPConnection 保持连接(进程存活),后续调用复用已有进程。但用户首次体验可能感知到启动延迟。
陷阱 3:OAuth token 过期
现象:认证过的 MCP 服务器过一段时间后连接失败。
规避:需要在连接层实现 token 刷新逻辑。如果 MCP SDK 支持 refresh token,自动刷新;否则需要用户重新认证。
设计取舍
为什么用单例模式
MCP 连接是全局资源——所有子系统(配置加载、工具调用、上下文提供者)都需要访问。单例确保:
- 连接不会被重复创建
- 状态变更对所有消费者可见
- 配置重载时统一清理
替代方案:依赖注入(DI)容器。Continue 选择了更简单的单例,因为 MCPManager 的生命周期与整个应用一致,不需要更细粒度的作用域控制。
mcp:// URI vs 直接工具名引用
Continue 选择用 URI 协议来标识 MCP 工具,而非在工具表中存储 serverId + toolName 的组合。好处是:
- 统一的工具路由:
callTool只需解析一个 URI 字符串 - 可扩展:未来可以添加新的协议(如
http-tool://)而不改变路由逻辑 - 序列化友好:URI 是字符串,可安全地存储在历史记录中