Skip to content

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 服务器│
│ (本地进程) │ │ (远程) │ │ (实时) │
└───────────┘ └───────────┘ └───────────────┘

关键文件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,
}))
格式: 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_screenshot
// MCP 服务器可以声明工具的安全属性
annotations: {
title?: string // 人类可读名称
readOnly?: boolean // 只读操作
destructive?: boolean // 不可逆操作
openWorld?: boolean // 访问外部系统
}
// 这些注解直接映射到 Tool 接口的安全方法:
isReadOnly(input) { return annotations.readOnly }
isDestructive(input) { return annotations.destructive }

关键文件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()
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(防止频繁重新认证)

关键文件services/mcp/client.ts + 各传输实现文件

传输使用场景连接方式特点
stdio本地 CLI 工具子进程 stdin/stdout最常用,零网络开销
SSE远程 HTTP 服务GET(长连接) + POST(请求)兼容性好,支持 OAuth
HTTPStreamable HTTPPOST with SSE responseMCP 规范推荐
WebSocket实时双向通信ws:// 或 wss://低延迟,支持 mTLS
SDK进程内集成控制消息通道零开销,SDK 专用
配置:
{
"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 模式:
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 header
// 用于内置 MCP 服务器(如 Chrome DevTools、Computer Use)
// 不需要子进程或网络连接
const [clientTransport, serverTransport] = createLinkedTransportPair()
// 消息通过 queueMicrotask() 直接传递
// 任一端关闭 → 另一端也关闭
// 省去子进程开销(~325MB for Chrome server)

关键文件services/mcp/auth.ts

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)
清除本地存储
场景: 企业用户通过统一身份提供商 (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)会话过期清除连接缓存,自动重连
InvalidGrantErrorRefresh token 失效清除 token,要求用户重新认证

5. Elicitation —— 工具执行中的用户交互

Section titled “5. Elicitation —— 工具执行中的用户交互”

关键文件services/mcp/elicitationHandler.ts

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 服务器
工具继续执行
场景: 工具调用需要用户授权(如 GitHub OAuth)
callMCPTool()
↓ 服务器返回 -32042 (UrlElicitationRequiredError)
显示 URL 给用户
↓ 用户打开浏览器授权
↓ 等待服务器发送 ElicitationComplete 通知
重试工具调用(最多 3 次)
成功 → 返回结果
失败 → "URL elicitation was declined/cancelled"

// ListMcpResourcesTool
input: { server?: string } // 可选:按服务器过滤
output: [
{ uri: "file:///path/to/doc.md", name: "Documentation", mimeType: "text/markdown", server: "my-server" },
...
]
// ReadMcpResourceTool
input: { uri: "file:///...", server?: string }
output: 资源内容(文本/二进制/图片)
文本资源 → 直接返回
图片资源 → maybeResizeAndDownsampleImageBuffer() → 缩放后返回
二进制资源 → persistBinaryContent() → 持久化到磁盘
大资源 → 截断到 100KB

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 → 全新连接
HTTP 404 + JSON-RPC code -32001
识别为 McpSessionExpiredError
立即关闭连接 + 清除缓存
下次调用自动重连(新 session)

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" → 精确匹配

MCPTool 是经典的「适配器模式」:
外部世界(MCP 服务器)── 各种协议 ──→ MCPTool 包装器 ──→ 统一 Tool 接口

任何 MCP 兼容服务器都可以无缝接入 Claude Code,无需修改任何源码。

应用层 (JSON-RPC)
Client 层 (MCP SDK)
传输层 (可替换)
├─ StdioTransport
├─ SSETransport
├─ HTTPTransport
├─ WebSocketTransport
└─ InProcessTransport

5 种传输层都实现相同接口,对上层完全透明。

认证不是硬编码的,而是:
- OAuth: 自动发现 + DCR + PKCE(标准流程)
- XAA: 企业 IdP 集成(可选)
- Headers: 静态 + 动态(headersHelper 命令)
- Token: Keychain 持久化 + 自动刷新
MCP 服务器不可用时:
1. 连接失败 → needs-auth → 创建 McpAuthTool(引导认证)
2. 工具调用失败 → 返回错误消息(不崩溃)
3. 会话过期 → 自动重连
4. 连续错误 → 标记服务器不可用(不影响其他服务器)

每个故障都有明确的恢复路径,不会导致整个系统崩溃。