MCPTool 深度解读 —— Claude Code 的可扩展性基石
MCP (Model Context Protocol) 是 Claude Code 连接外部世界的协议层。通过 MCP,任何外部服务都可以将自己的能力注册为 Claude Code 的工具,而无需修改 Claude Code 的源码。
MCPTool 在架构中的位置:
┌──────────────────────────────────────────────┐│ Claude Code ││ ┌────────────────┐ ┌────────────────────┐ ││ │ 内置工具 (30+) │ │ MCP 工具 (动态) │ ││ │ Bash, Read... │ │ mcp__github__... │ ││ └────────────────┘ └────────┬───────────┘ ││ │ ││ ┌──────────┴──────────┐ ││ │ MCPTool 包装器 │ ││ │ (统一接口适配) │ ││ └──────────┬──────────┘ │└───────────────────────────────┼──────────────┘ │ ┌─────────────────┼─────────────────┐ │ │ │ ┌─────┴─────┐ ┌─────┴─────┐ ┌───────┴───────┐ │ stdio 服务器│ │ HTTP 服务器 │ │ WebSocket 服务器│ │ (本地进程) │ │ (远程) │ │ (实时) │ └───────────┘ └───────────┘ └───────────────┘1. MCPTool 包装器
Section titled “1. MCPTool 包装器”关键文件:tools/MCPTool/MCPTool.ts
MCPTool 是一个模板工具 —— 它定义了基本结构,但具体的 description()、prompt() 和 call() 方法会被每个 MCP 服务器发现的工具动态覆盖。
// 基础模板const MCPTool = buildTool({ name: 'mcp__placeholder', // 会被覆盖 maxResultSizeChars: 100_000, // 100KB 结果上限 isMcp: true, // 标记为 MCP 工具
inputSchema: z.object({}).passthrough(), // 接受任意输入 // MCP 服务器定义自己的 schema,这里用 passthrough 透传
outputSchema: z.string(), // 统一返回字符串})
// 动态覆盖(在 fetchToolsForClient 中)const concreteTools = mcpServerTools.map(serverTool => ({ ...MCPTool, name: `mcp__${serverName}__${toolName}`, description: () => serverTool.description, call: (args) => callMCPTool(client, serverTool.name, args), // inputJSONSchema 直接使用 MCP 服务器提供的 JSON Schema inputJSONSchema: serverTool.inputSchema,}))工具命名规范
Section titled “工具命名规范”格式: mcp__{normalized_server}__{normalized_tool}
规范化规则: - 非法字符(非 a-zA-Z0-9_-)替换为 _ - claude.ai 服务器: 合并连续 _,去除首尾 _
示例: 服务器: my-github, 工具: create_issue → mcp__my_github__create_issue
服务器: claude.ai Figma, 工具: get_screenshot → mcp__claude_ai_Figma__get_screenshotMCP 工具注解
Section titled “MCP 工具注解”// MCP 服务器可以声明工具的安全属性annotations: { title?: string // 人类可读名称 readOnly?: boolean // 只读操作 destructive?: boolean // 不可逆操作 openWorld?: boolean // 访问外部系统}
// 这些注解直接映射到 Tool 接口的安全方法:isReadOnly(input) { return annotations.readOnly }isDestructive(input) { return annotations.destructive }2. MCP 客户端生命周期
Section titled “2. MCP 客户端生命周期”关键文件:services/mcp/client.ts
ensureConnectedClient(serverName) ↓ cache 命中? ├─ 是 → 返回缓存的连接 └─ 否 → connectToServer(name, config) ↓ 检测传输类型 ↓ 创建传输层 ↓ new Client({ capabilities: { roots, elicitation } }) ↓ client.connect(transport) // 超时 30s ↓ 注册事件处理: ├─ onerror → 检测终端错误 ├─ onclose → 清除缓存(触发重连) └─ ElicitationHandler → 用户交互处理 ↓ 注册 ListRoots 处理器(返回 cwd) ↓ 返回 ConnectedMCPServer + cleanup()工具发现流程
Section titled “工具发现流程”ConnectedMCPServer ↓client.request({ method: 'tools/list' }) ↓sanitizeUnicode(response) // 清理 MCP 服务器返回的数据 ↓遍历每个 MCP tool: ↓ { ...MCPTool(模板), name: mcp__server__tool, description: async () => tool.description, call: async (args) => callMCPToolWithUrlElicitationRetry(...), inputJSONSchema: tool.inputSchema, isReadOnly: tool.annotations?.readOnly, isDestructive: tool.annotations?.destructive, } ↓过滤 IDE 工具(只有白名单内的可用) ↓返回 Tool[]三层缓存: 1. 连接缓存: connectToServer() 结果被 memoize → 相同 server config → 复用连接 → onclose 时清除缓存 → 下次自动重连
2. 工具缓存: LRU(size=20),按 server name 索引 → 避免重复调用 tools/list
3. 认证缓存: OAuth token 持久化到 Keychain → 15 分钟 TTL(防止频繁重新认证)3. 五种传输协议
Section titled “3. 五种传输协议”关键文件:services/mcp/client.ts + 各传输实现文件
| 传输 | 使用场景 | 连接方式 | 特点 |
|---|---|---|---|
| stdio | 本地 CLI 工具 | 子进程 stdin/stdout | 最常用,零网络开销 |
| SSE | 远程 HTTP 服务 | GET(长连接) + POST(请求) | 兼容性好,支持 OAuth |
| HTTP | Streamable HTTP | POST with SSE response | MCP 规范推荐 |
| WebSocket | 实时双向通信 | ws:// 或 wss:// | 低延迟,支持 mTLS |
| SDK | 进程内集成 | 控制消息通道 | 零开销,SDK 专用 |
stdio 传输(最常用)
Section titled “stdio 传输(最常用)”配置:{ "command": "node", "args": ["server.js"], "env": { "API_KEY": "..." }}
实现: - 子进程: spawn(command, args, { env, stdin: 'pipe', stdout: 'pipe' }) - 通信: JSON-RPC over stdin/stdout - stderr: 捕获(最大 64MB),连接失败时用于诊断 - 清理: SIGINT → SIGTERM → SIGKILL(渐进升级,100-400ms 间隔)SSE/HTTP 传输(远程服务)
Section titled “SSE/HTTP 传输(远程服务)”SSE 模式: GET /sse ← 长连接,接收服务器推送 POST /message ← 发送请求
HTTP Streamable 模式: POST / ← 请求+响应都在一个 HTTP 请求中 Accept: application/json, text/event-stream
共同特性: - OAuth 认证: Bearer token 自动附加 - 超时保护: POST 请求 60s 超时,SSE 流无超时 - Step-up 检测: 403 + WWW-Authenticate: insufficient_scope → 重新认证 - Session ID: X-Mcp-Client-Session-Id headerIn-Process 传输(零开销)
Section titled “In-Process 传输(零开销)”// 用于内置 MCP 服务器(如 Chrome DevTools、Computer Use)// 不需要子进程或网络连接
const [clientTransport, serverTransport] = createLinkedTransportPair()// 消息通过 queueMicrotask() 直接传递// 任一端关闭 → 另一端也关闭// 省去子进程开销(~325MB for Chrome server)4. 认证系统
Section titled “4. 认证系统”关键文件:services/mcp/auth.ts
OAuth 流程
Section titled “OAuth 流程”1. 发现阶段: GET /.well-known/oauth-protected-resource → authorization_servers[0] GET /.well-known/oauth-authorization-server → { authorization_endpoint, token_endpoint, ... }
2. 客户端注册 (DCR): POST /register → { client_id, client_secret } 存储到 Keychain: mcpOAuthClientConfig[serverKey]
3. PKCE 授权码流程: 浏览器打开 → authorization_endpoint + code_challenge (S256) + redirect_uri (localhost:port) 用户授权 → callback with code POST /token (code + code_verifier) → { access_token, refresh_token, expires_in } 存储到 Keychain: mcpOAuth[serverKey]
4. Token 刷新: expires_in < 5min → 自动刷新 POST /token (grant_type=refresh_token) → 新 token 去重: 同一时间只有一个刷新请求在飞
5. Token 撤销 (RFC 7009): POST /revoke (refresh_token) POST /revoke (access_token) 清除本地存储XAA 跨应用访问 (SEP-990)
Section titled “XAA 跨应用访问 (SEP-990)”场景: 企业用户通过统一身份提供商 (IdP) 登录
流程: 1. OIDC 登录到 IdP → id_token(缓存复用) 2. RFC 8693 Token Exchange: id_token → access_token (for MCP server) 3. 每个 MCP 服务器独立的 client_id/client_secret
配置: oauth: { xaa: true, clientId: "...", ... } + settings.xaaIdp (全局 IdP 配置)| 错误 | 场景 | 处理 |
|---|---|---|
UnauthorizedError (401) | 连接时认证失败 | 返回 needs-auth 状态,创建 McpAuthTool |
McpAuthError (401) | 工具调用时失败 | 标记 needs-auth,中止重试 |
McpSessionExpiredError (404 + -32001) | 会话过期 | 清除连接缓存,自动重连 |
InvalidGrantError | Refresh token 失效 | 清除 token,要求用户重新认证 |
5. Elicitation —— 工具执行中的用户交互
Section titled “5. Elicitation —— 工具执行中的用户交互”关键文件:services/mcp/elicitationHandler.ts
什么是 Elicitation?
Section titled “什么是 Elicitation?”MCP 工具在执行过程中可能需要用户输入(如 OAuth 授权 URL、表单数据)。Elicitation 机制让工具「暂停」,等待用户交互后继续。
URL 模式: 服务器: "请用户打开 https://example.com/auth 进行授权" 客户端: 打开浏览器 → 等待服务器确认 → 继续执行
Form 模式: 服务器: "请用户填写以下表单: { name: string, email: string }" 客户端: 显示表单 UI → 收集用户输入 → 返回给服务器MCP 工具调用中... ↓服务器发送 Elicitation 请求 (JSON-RPC notification) ↓runElicitationHooks() // 先检查是否有 Hook 可以自动回应 ├─ Hook 提供了回应 → 返回给服务器(不显示 UI) └─ 无 Hook → 继续 ↓添加到 UI 队列: AppState.elicitation.queue ↓ElicitationDialog 组件显示 ├─ URL 模式 → 打开浏览器 └─ Form 模式 → 显示输入表单 ↓用户交互完成 ↓runElicitationResultHooks() // 后处理 Hook ↓结果返回给 MCP 服务器 ↓工具继续执行URL Elicitation 重试
Section titled “URL Elicitation 重试”场景: 工具调用需要用户授权(如 GitHub OAuth)
callMCPTool() ↓ 服务器返回 -32042 (UrlElicitationRequiredError) ↓显示 URL 给用户 ↓ 用户打开浏览器授权 ↓ 等待服务器发送 ElicitationComplete 通知 ↓重试工具调用(最多 3 次) ↓成功 → 返回结果失败 → "URL elicitation was declined/cancelled"6. 资源管理
Section titled “6. 资源管理”// ListMcpResourcesToolinput: { server?: string } // 可选:按服务器过滤output: [ { uri: "file:///path/to/doc.md", name: "Documentation", mimeType: "text/markdown", server: "my-server" }, ...]
// ReadMcpResourceToolinput: { uri: "file:///...", server?: string }output: 资源内容(文本/二进制/图片)文本资源 → 直接返回图片资源 → maybeResizeAndDownsampleImageBuffer() → 缩放后返回二进制资源 → persistBinaryContent() → 持久化到磁盘大资源 → 截断到 100KB7. 连接故障检测与恢复
Section titled “7. 连接故障检测与恢复”终端错误检测
Section titled “终端错误检测”const TERMINAL_ERRORS = [ 'ECONNRESET', 'ETIMEDOUT', 'EPIPE', 'EHOSTUNREACH', 'ECONNREFUSED', 'Body Timeout Error', 'terminated', 'SSE stream disconnected', 'Maximum reconnection attempts']连续终端错误计数 ↓ < 3 继续使用现有连接 ↓ ≥ 3 closeTransportAndRejectPending() 清除 memoization 缓存 ↓ 下次工具调用 ensureConnectedClient() → cache miss → 全新连接会话过期特殊处理
Section titled “会话过期特殊处理”HTTP 404 + JSON-RPC code -32001 ↓识别为 McpSessionExpiredError ↓立即关闭连接 + 清除缓存 ↓下次调用自动重连(新 session)8. 权限集成
Section titled “8. 权限集成”MCP 工具走与内置工具完全相同的权限管线:
权限规则示例: allow: ["mcp__github__*"] → 允许 GitHub 服务器所有工具 deny: ["mcp__github__delete_repo"] → 禁止删除仓库 ask: ["mcp__slack__send_message"] → 发消息前询问
规则匹配: 1. 全服务器: "mcp__github" → 匹配该服务器所有工具 2. 全工具: "mcp__github__*" → 同上(通配符) 3. 具体工具: "mcp__github__create_issue" → 精确匹配9. 架构洞察
Section titled “9. 架构洞察”模式 1: 协议适配器模式
Section titled “模式 1: 协议适配器模式”MCPTool 是经典的「适配器模式」: 外部世界(MCP 服务器)── 各种协议 ──→ MCPTool 包装器 ──→ 统一 Tool 接口任何 MCP 兼容服务器都可以无缝接入 Claude Code,无需修改任何源码。
模式 2: 传输层抽象
Section titled “模式 2: 传输层抽象”应用层 (JSON-RPC) ↓Client 层 (MCP SDK) ↓传输层 (可替换) ├─ StdioTransport ├─ SSETransport ├─ HTTPTransport ├─ WebSocketTransport └─ InProcessTransport5 种传输层都实现相同接口,对上层完全透明。
模式 3: 认证即插件
Section titled “模式 3: 认证即插件”认证不是硬编码的,而是: - OAuth: 自动发现 + DCR + PKCE(标准流程) - XAA: 企业 IdP 集成(可选) - Headers: 静态 + 动态(headersHelper 命令) - Token: Keychain 持久化 + 自动刷新模式 4: 优雅降级
Section titled “模式 4: 优雅降级”MCP 服务器不可用时: 1. 连接失败 → needs-auth → 创建 McpAuthTool(引导认证) 2. 工具调用失败 → 返回错误消息(不崩溃) 3. 会话过期 → 自动重连 4. 连续错误 → 标记服务器不可用(不影响其他服务器)每个故障都有明确的恢复路径,不会导致整个系统崩溃。