MCP 集成
Claude Code 对 Model Context Protocol (MCP) 提供一等支持——这是一个用于将 AI 助手连接到外部 tool 和数据源的开放标准。MCP tool 被视为与 builtin tool 平级,共享相同的分发、permission 和 hook 系统。
graph TD
A[MCP Configuration] --> B[useManageMCPConnections]
B --> C{Transport Type}
C --> D[stdio - local process]
C --> E[sse - Server-Sent Events]
C --> F[http - Streamable HTTP]
C --> G[ws - WebSocket]
C --> H[sdk - SDK control]
C --> I[sse-ide / ws-ide - IDE]
C --> J[claudeai-proxy]
D --> K[MCP Client]
E --> K
F --> K
G --> K
H --> K
I --> K
J --> K
K --> L[ListTools]
L --> M[MCPTool wrappers]
M --> N[Merged with built-in tools]
N --> O[ToolUseContext.options.tools]
K --> P[ListResources]
P --> Q[ListMcpResources / ReadMcpResource tools]
MCP 服务器配置
Section titled “MCP 服务器配置”MCP 服务器在 settings.json 中配置(用户、项目或企业级范围)。配置定义于 src/services/mcp/types.ts:
Transport 类型
Section titled “Transport 类型”export const TransportSchema = z.enum([ 'stdio', // Local subprocess 'sse', // Server-Sent Events (HTTP/2) 'sse-ide', // IDE-specific SSE 'http', // Streamable HTTP 'ws', // WebSocket 'sdk', // SDK control transport]);配置 Schema
Section titled “配置 Schema”stdio — 启动本地进程:
const McpStdioServerConfigSchema = z.object({ type: z.literal('stdio').optional(), command: z.string().min(1), args: z.array(z.string()).default([]), env: z.record(z.string(), z.string()).optional(),});sse/http — 连接远程服务器:
const McpSSEServerConfigSchema = z.object({ type: z.literal('sse'), url: z.string(), headers: z.record(z.string(), z.string()).optional(), headersHelper: z.string().optional(), oauth: z.object({ clientId: z.string().optional(), callbackPort: z.number().optional(), authServerMetadataUrl: z.string().url().optional(), }).optional(),});每个服务器配置携带一个范围,决定其信任级别:
export type ConfigScope = 'local' | 'user' | 'project' | 'dynamic' | 'enterprise' | 'claudeai' | 'managed';
export type ScopedMcpServerConfig = McpServerConfig & { scope: ConfigScope; pluginSource?: string; // For plugin-provided servers};连接生命周期
Section titled “连接生命周期”服务器连接状态
Section titled “服务器连接状态”stateDiagram-v2
[*] --> pending: Config detected
pending --> connected: Client connects successfully
pending --> failed: Connection error
pending --> needs_auth: OAuth required (401)
pending --> disabled: User disabled
failed --> pending: Reconnect attempt
needs_auth --> pending: OAuth flow completed
connected --> failed: Connection lost
connected --> pending: Reconnect (tool list changed)
disabled --> pending: User re-enabled
export type MCPServerConnection = | ConnectedMCPServer // type: 'connected' | FailedMCPServer // type: 'failed' | NeedsAuthMCPServer // type: 'needs-auth' | PendingMCPServer // type: 'pending' | DisabledMCPServer // type: 'disabled'已连接服务器的结构
Section titled “已连接服务器的结构”export type ConnectedMCPServer = { client: Client; // MCP SDK Client instance name: string; type: 'connected'; capabilities: ServerCapabilities; serverInfo?: { name: string; version: string }; instructions?: string; // Server-provided instructions (capped at 2048 chars) config: ScopedMcpServerConfig; cleanup: () => Promise<void>;};指数退避重连
Section titled “指数退避重连”const MAX_RECONNECT_ATTEMPTS = 5;const INITIAL_BACKOFF_MS = 1000;const MAX_BACKOFF_MS = 30000;断连时:
- 将服务器状态设为
pending,并带有reconnectAttempt计数器 - 等待
min(INITIAL_BACKOFF_MS * 2^attempt, MAX_BACKOFF_MS)毫秒 - 尝试重连
- 若失败且
attempt < MAX_RECONNECT_ATTEMPTS,递增并重试 - 若所有尝试耗尽,将状态设为
failed
Tool 注册
Section titled “Tool 注册”MCP 服务器连接后,其 tool 会被获取并包装:
// src/services/mcp/client.ts (conceptual flow)async function fetchToolsForClient( server: ConnectedMCPServer,): Promise<Tool[]> { const result: ListToolsResult = await server.client.listTools();
return result.tools.map(mcpTool => { const normalizedName = buildMcpToolName(server.name, mcpTool.name);
return { ...MCPTool, // Base MCPTool template name: normalizedName, // e.g., "mcp__github__create_issue" isMcp: true, mcpInfo: { serverName: server.name, toolName: mcpTool.name }, inputJSONSchema: mcpTool.inputSchema, shouldDefer: mcpTool._meta?.['anthropic/deferLoading'] ?? false, alwaysLoad: mcpTool._meta?.['anthropic/alwaysLoad'] ?? false,
async description() { return truncate(mcpTool.description, MAX_MCP_DESCRIPTION_LENGTH); },
async call(args, context, canUseTool, parentMessage, onProgress) { const result = await server.client.callTool({ name: mcpTool.name, arguments: args, }, CallToolResultSchema, { timeout: getMcpToolTimeoutMs(), signal: context.abortController.signal, onprogress: (progress) => { onProgress?.({ toolUseID: context.toolUseId!, data: { type: 'mcp_progress', ...progress }, }); }, }); return { data: formatMcpResult(result) }; }, }; });}Tool 命名规范
Section titled “Tool 命名规范”MCP tool 通过命名空间防止冲突:
mcp__{serverName}__{toolName}示例:名为 github 的 GitHub MCP 服务器中的 create_issue tool 会成为 mcp__github__create_issue。
src/services/mcp/normalization.ts 中的规范化逻辑处理:
- 服务器/tool 名称中的特殊字符
- 名称长度限制
- 冲突检测
统一 Tool 分发
Section titled “统一 Tool 分发”MCP tool 和 builtin tool 共享同一分发管道:
graph TD
A[API Response: tool_use block] --> B[findToolByName]
B --> C{Is MCP tool?}
C -- yes --> D[MCPTool.call → client.callTool]
C -- no --> E[BuiltinTool.call → local execution]
D --> F[Format MCP result]
E --> G[Format built-in result]
F --> H[tool_result message]
G --> H
同一管道适用于:
- 输入验证:MCP tool 针对
inputJSONSchema进行验证 - Permission 检查:相同的
checkPermissions流程 - PreToolUse/PostToolUse hook:相同的 hook 系统
- 结果格式化:相同的
mapToolResultToToolResultBlockParam - 并发执行:MCP tool 默认是并发安全的(从 Claude Code 视角来看是只读的)
MCP 专属 Permission 处理
Section titled “MCP 专属 Permission 处理”MCP tool 对 checkPermissions 使用 behavior: 'passthrough',意味着它们始终需要走标准 permission 流程:
async checkPermissions(): Promise<PermissionResult> { return { behavior: 'passthrough', message: 'MCPTool requires permission.', };}实际的 permission 决策经过完整链路:hook → 基于规则 → 用户提示。
MCP 服务器可以在 tool 之外暴露资源(文件、数据):
export type ServerResource = Resource & { server: string };两个 builtin tool 负责处理资源:
- ListMcpResources(
src/tools/ListMcpResourcesTool/):列出所有已连接服务器的可用资源 - ReadMcpResource(
src/tools/ReadMcpResourceTool/):通过 URI 读取特定资源
MCP 结果处理
Section titled “MCP 结果处理”MCP tool 结果可包含多种内容类型。客户端会对其进行处理:
// Content types from MCPtype MCPToolResult = { content: Array< | { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string } | { type: 'resource'; resource: { uri: string; text?: string } } >; isError?: boolean; _meta?: Record<string, unknown>; structuredContent?: Record<string, unknown>;};大结果会被截断以防止 context 溢出:
const MAX_MCP_TOOL_RESULT_SIZE = 100_000; // characters
function truncateMcpContentIfNeeded(content: MCPToolResult): MCPToolResult { if (!mcpContentNeedsTruncation(content)) return content; // Truncate and add "output truncated" marker}Session 过期错误处理
Section titled “Session 过期错误处理”MCP session 可能过期(HTTP 404 + JSON-RPC code -32001)。客户端会透明处理此情况:
export function isMcpSessionExpiredError(error: Error): boolean { const httpStatus = 'code' in error ? error.code : undefined; if (httpStatus !== 404) return false; return ( error.message.includes('"code":-32001') || error.message.includes('"code": -32001') );}Session 过期时:
- 清除缓存的客户端连接
- 通过
ensureConnectedClient获取新客户端 - 重试 tool 调用
已连接的 MCP 服务器可以推送通知,触发 tool/资源刷新:
server.client.setNotificationHandler(ToolListChangedNotificationSchema, () => { // Re-fetch tools from server void fetchToolsForClient(server);});
server.client.setNotificationHandler(ResourceListChangedNotificationSchema, () => { // Re-fetch resources from server void fetchResourcesForClient(server);});
server.client.setNotificationHandler(PromptListChangedNotificationSchema, () => { // Re-fetch prompts from server});这实现了动态 tool 注册——MCP 服务器可以在运行时添加/删除 tool,Claude Code 会自动感知变更。
OAuth 认证
Section titled “OAuth 认证”远程 MCP 服务器(SSE、HTTP)可能需要 OAuth:
export class ClaudeAuthProvider { // Implements the MCP SDK's AuthProvider interface // Handles OAuth authorization code flow // Stores tokens in secure storage (macOS Keychain, etc.)}认证流程:
- 服务器返回 401
- 客户端以服务器的 OAuth 配置创建
ClaudeAuthProvider - Provider 发现授权服务器元数据 URL
- 打开浏览器供用户授权
- 接收带授权码的回调
- 用授权码换取 token
- 存储 token 并重试连接
认证状态缓存 15 分钟,避免重复认证流程:
const MCP_AUTH_CACHE_TTL_MS = 15 * 60 * 1000;