MCP 协议集成深度解析
Model Context Protocol(MCP)是 Anthropic 推出的一项开放协议,旨在标准化 AI 模型与外部工具、数据源之间的交互方式。在 Claude Code 中,MCP 不仅仅是一个"插件接口",而是被深度集成到整个工具调用链路和用户交互流程中的核心基础设施。本文将从协议规范、Server 生命周期、传输层实现、工具动态注册以及安全隔离五个维度,对 Claude Code 的 MCP 集成进行全景式深度解析。
一、MCP 协议规范概述
1.1 JSON-RPC 2.0 消息格式
MCP 协议底层基于 JSON-RPC 2.0 构建,所有通信都通过标准化的请求-响应或通知消息进行。JSON-RPC 2.0 是一种轻量级的远程过程调用协议,使用 JSON 作为数据格式,具有良好的可读性和跨语言兼容性。在 Claude Code 的源码中,client.ts 大量使用了 @modelcontextprotocol/sdk 提供的类型和 Schema 来确保协议合规:
// src/services/mcp/client.ts (第 1-50 行)
import {
CallToolResultSchema,
ErrorCode,
type JSONRPCMessage,
type ListToolsResult,
ListToolsResultSchema,
ListResourcesResultSchema,
ListPromptsResultSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js'MCP 消息分为三种基本类型:Request(请求)、Response(响应)和 Notification(通知)。Claude Code 在调用 MCP Server 的工具时,使用 client.request() 方法发送标准请求,并通过 Zod Schema 对响应进行严格校验:
// src/services/mcp/client.ts (第 1747-1752 行)
const result = (await client.client.request(
{ method: 'tools/list' },
ListToolsResultSchema,
)) as ListToolsResult这种设计保证了无论 MCP Server 是通过 stdio、SSE 还是 WebSocket 传输,上层业务逻辑都不需要关心底层差异。
1.2 Server Capability 声明
MCP 协议要求 Server 在初始化阶段通过 initialize 请求返回其支持的能力集(capabilities)。Claude Code 在 types.ts 中定义了与 SDK 类型兼容的 Server 连接状态:
// src/services/mcp/types.ts (第 95-105 行)
export type ConnectedMCPServer = {
client: Client
name: string
type: 'connected'
capabilities: ServerCapabilities
serverInfo?: {
name: string
version: string
}
instructions?: string
config: ScopedMcpServerConfig
cleanup: () => Promise<void>
}ServerCapabilities 来自 MCP SDK,包含以下几个关键字段:
tools:Server 是否支持工具调用resources:Server 是否支持资源读取prompts:Server 是否支持 Prompt 模板logging:Server 是否支持日志通知experimental:实验性功能标志
Claude Code 在连接 Server 后会立即检查 capabilities,并据此决定是否获取工具列表、资源列表或 Prompt 列表:
// src/services/mcp/client.ts (第 1745-1748 行)
if (!client.capabilities?.tools) {
return []
}1.3 Tool 和 Resource 的协议定义
在 MCP 协议中,Tool 的定义包含名称、描述和输入参数 Schema(遵循 JSON Schema 规范)。Claude Code 在获取工具列表后,会将每个 MCP Tool 转换为内部的 Tool 类型,同时保留原始的 inputJSONSchema:
// src/services/mcp/client.ts (第 1785-1792 行)
return {
...MCPTool,
name: skipPrefix ? tool.name : fullyQualifiedName,
mcpInfo: { serverName: client.name, toolName: tool.name },
isMcp: true,
inputJSONSchema: tool.inputSchema as Tool['inputJSONSchema'],
// ...
}Resource 则遵循 URI 标识的只读数据模型。Claude Code 在 types.ts 中扩展了 Resource 类型,增加了 server 字段来标识资源所属的 Server:
// src/services/mcp/types.ts (第 115-120 行)
export type ServerResource = Resource & {
server: string
}这种扩展使得在多 Server 环境下,Claude Code 能够准确追踪每个资源的来源,为后续的权限管理和冲突解决提供依据。
二、MCP Server 生命周期管理
2.1 初始化流程:initialize → initialized
MCP Server 的生命周期始于一次完整的握手过程。Claude Code 的 connectToServer 函数(位于 client.ts)封装了这一流程。在连接建立后,SDK 会自动发送 initialize 请求,Server 返回其能力声明,随后 SDK 发送 initialized 通知,标志握手完成:
sequenceDiagram
participant CC as Claude Code
participant SDK as MCP SDK Client
participant S as MCP Server
CC->>SDK: new Client(clientInfo, capabilities)
CC->>SDK: client.connect(transport)
SDK->>S: initialize (protocolVersion, capabilities, clientInfo)
S-->>SDK: InitializeResult (capabilities, serverInfo, instructions)
SDK->>S: initialized (notification)
SDK-->>CC: connection ready
CC->>S: tools/list
S-->>CC: ListToolsResult
CC->>S: resources/list (optional)
S-->>CC: ListResourcesResult值得注意的是,Claude Code 在初始化时向 Server 报告了详细的客户端信息,包括名称、版本和网站地址:
// src/services/mcp/client.ts (第 3580-3588 行)
const client = new Client(
{
name: 'claude-code',
title: 'Claude Code',
version: MACRO.VERSION ?? 'unknown',
description: "Anthropic's agentic coding tool",
websiteUrl: PRODUCT_URL,
},
{ capabilities: {} }
)这种详细的客户端声明不仅符合协议规范,也为 Server 提供了足够的上下文来进行服务端适配。
2.2 能力协商:tools/list、resources/list
初始化完成后,Claude Code 会立即进行能力协商。fetchToolsForClient、fetchResourcesForClient 和 fetchCommandsForClient 三个函数分别负责获取工具、资源和 Prompt:
// src/services/mcp/client.ts (第 1743-1755 行)
export const fetchToolsForClient = memoizeWithLRU(
async (client: MCPServerConnection): Promise<Tool[]> => {
if (client.type !== 'connected') return []
try {
if (!client.capabilities?.tools) return []
const result = await client.client.request(
{ method: 'tools/list' },
ListToolsResultSchema,
)
// ... 转换为内部 Tool 类型
}
},
(client) => client.name,
MCP_FETCH_CACHE_SIZE,
)这三个函数都使用了 memoizeWithLRU 进行缓存,避免在每次对话轮次中重复请求。缓存键为 Server 名称,缓存大小由 MCP_FETCH_CACHE_SIZE 控制。当 Server 主动通知列表变更(通过 notifications/tools/list_changed)时,useManageMCPConnections 会清除对应缓存并重新获取:
// src/services/mcp/useManageMCPConnections.ts (第 200-220 行)
// 处理 Tool 列表变更通知
const toolListChangedHandler = () => {
fetchToolsForClient.cache.delete(serverName)
// 重新获取并更新 AppState
}2.3 心跳和重连机制
Claude Code 的 MCP 连接管理采用了一套完善的重连策略。在 useManageMCPConnections 中,定义了指数退避的重连参数:
// src/services/mcp/useManageMCPConnections.ts (第 90-92 行)
const MAX_RECONNECT_ATTEMPTS = 5
const INITIAL_BACKOFF_MS = 1000
const MAX_BACKOFF_MS = 30000当 SSE 或 WebSocket 连接断开时,系统会触发 onclose 回调,标记 Server 为断开状态,并启动重连定时器。每次重连尝试的间隔呈指数增长,从 1 秒开始,最高不超过 30 秒,最多尝试 5 次。
对于 HTTP 传输,Claude Code 还实现了 Session 过期检测。当 Server 返回 HTTP 404 且 JSON-RPC 错误码为 -32001("Session not found")时,系统会自动清除连接缓存,迫使下一次工具调用时重新建立连接:
// src/services/mcp/client.ts (第 160-175 行)
export function isMcpSessionExpiredError(error: Error): boolean {
const httpStatus = 'code' in error
? (error as Error & { code?: number }).code : undefined
if (httpStatus !== 404) return false
return (
error.message.includes('"code":-32001') ||
error.message.includes('"code": -32001')
)
}三、传输层实现
MCP 协议本身不规定传输层细节,因此 Claude Code 需要为不同的部署场景提供多样化的传输实现。当前源码中支持六种传输类型:stdio、sse、sse-ide、http、ws 和 sdk。
3.1 stdio 子进程传输
stdio 传输是最常见的 MCP Server 部署方式,Server 作为子进程启动,通过标准输入输出与 Claude Code 通信。在 connectToServer 中,stdio Server 的启动逻辑如下:
// src/services/mcp/client.ts (第 1200-1250 行,概述)
const stdioTransport = new StdioClientTransport({
command: stdioConfig.command,
args: stdioConfig.args,
env: { ...subprocessEnv(), ...stdioConfig.env },
})stdio 传输的一个关键挑战是子进程的生命周期管理。由于 MCP Server 可能是一个长期运行的进程,甚至是一个 Docker 容器,简单的关闭连接往往无法确保子进程被正确回收。Claude Code 在关闭连接时实现了一套优雅的进程终止策略:首先发送 SIGINT,等待 100ms;如果进程仍然存在,则发送 SIGTERM,再等待 400ms;最后如果仍不退出,则强制发送 SIGKILL:
// src/services/mcp/client.ts (第 1400-1450 行,概述)
// 首先尝试 SIGINT
try { process.kill(childPid, 'SIGINT') } catch (e) { return }
await sleep(100)
// SIGINT 失败,尝试 SIGTERM
try { process.kill(childPid, 'SIGTERM') } catch (e) { return }
await sleep(400)
// 最后手段:SIGKILL
try { process.kill(childPid, 'SIGKILL') } catch (e) {}这种分层信号策略既尊重了 Server 的优雅关闭需求,又防止了僵尸进程的产生。
3.2 WebSocket 传输
WebSocket 传输主要用于远程 MCP Server 的连接。Claude Code 在 utils/mcpWebSocketTransport.ts 中实现了自定义的 WebSocketTransport 类,实现了 MCP SDK 的 Transport 接口:
// src/utils/mcpWebSocketTransport.ts (第 20-30 行)
export class WebSocketTransport implements Transport {
private started = false
private opened: Promise<void>
private isBun = typeof Bun !== 'undefined'
constructor(private ws: WebSocketLike) {
this.opened = new Promise((resolve, reject) => {
if (this.ws.readyState === WS_OPEN) {
resolve()
} else if (this.isBun) {
// Bun 环境使用原生 WebSocket 事件
const onOpen = () => { /* ... */ resolve() }
const onError = (event: Event) => { /* ... */ reject(event) }
nws.addEventListener('open', onOpen)
nws.addEventListener('error', onError)
} else {
// Node 环境使用 ws 库
nws.on('open', () => resolve())
nws.on('error', error => reject(error))
}
})
}
}这个实现的一个亮点是跨运行时兼容性。Claude Code 同时支持 Bun 和 Node.js 两个运行时,因此 WebSocketTransport 在初始化时会检测当前环境(通过 typeof Bun !== 'undefined'),并分别绑定对应的事件监听器。在 Bun 环境下使用原生 WebSocket 的 addEventListener API,而在 Node 环境下使用 ws 库的 on 方法。
WebSocket 连接的建立过程在 client.ts 中完成。Claude Code 支持通过代理连接 WebSocket,并且在需要时会附加 Authorization 头进行认证:
// src/services/mcp/client.ts (第 1050-1080 行,概述)
const wsHeaders = {
'User-Agent': getMCPUserAgent(),
...(sessionIngressToken && {
Authorization: `Bearer ${sessionIngressToken}`,
}),
...combinedHeaders,
}3.3 消息序列化和反序列化
在传输层之上,所有消息都需要经过 JSON 序列化和反序列化。Claude Code 在 WebSocketTransport 中通过 jsonParse 和 jsonStringify 工具函数处理消息:
// src/utils/mcpWebSocketTransport.ts (第 70-80 行)
private onNodeMessage = (data: Buffer) => {
try {
const messageObj = jsonParse(data.toString('utf-8'))
const message = JSONRPCMessageSchema.parse(messageObj)
this.onmessage?.(message)
} catch (error) {
this.handleError(error)
}
}这里使用了 Zod 的 JSONRPCMessageSchema 对消息进行运行时校验,确保收到的数据符合 JSON-RPC 2.0 规范。不符合规范的消息会被捕获并触发 onerror 回调,而不会破坏整个连接状态。
四、工具动态注册与命名空间隔离
4.1 MCP Server 工具如何动态加入工具池
Claude Code 的工具系统通过 getMcpToolsCommandsAndResources 函数将所有 MCP Server 的工具、命令和资源统一聚合到应用状态中。这个过程采用分批并发策略,本地 Server(stdio/sdk)使用较低的并发度(默认 3),远程 Server 使用较高的并发度(默认 20):
// src/services/mcp/client.ts (第 2200-2230 行,概述)
const localServers = configEntries.filter(([_, config]) =>
isLocalMcpServer(config)
)
const remoteServers = configEntries.filter(
([_, config]) => !isLocalMcpServer(config)
)
// 使用 p-map 实现动态槽位释放的并发控制
await pMap(localServers, processServer, {
concurrency: getMcpServerConnectionBatchSize(), // 默认 3
})
await pMap(remoteServers, processServer, {
concurrency: getRemoteMcpServerConnectionBatchSize(), // 默认 20
})使用 p-map 而非固定批次的原因在源码注释中有详细说明:旧版实现采用固定大小的顺序批次(等待批次 1 全部完成后再启动批次 2),这意味着批次中一个慢速 Server 会阻塞后续所有 Server。p-map 的动态槽位释放机制确保一个慢速 Server 只会占用一个槽位,大大提高了连接效率。
4.2 命名空间隔离
为了避免不同 MCP Server 之间的工具名称冲突,Claude Code 实现了一套严格的命名空间隔离机制。每个 MCP 工具的全限定名(fully qualified name)遵循 mcp__serverName__toolName 的格式:
// src/services/mcp/mcpStringUtils.ts (第 35-38 行)
export function buildMcpToolName(serverName: string, toolName: string): string {
return `${getMcpPrefix(serverName)}${normalizeNameForMCP(toolName)}`
}其中 normalizeNameForMCP 会对名称进行规范化处理(如替换特殊字符),确保生成的名称符合内部标识符规范。这套命名机制使得来自不同 Server 的同名工具可以和平共存,例如 github:get_issue 和 gitlab:get_issue 会被映射为 mcp__github__get_issue 和 mcp__gitlab__get_issue。
在工具调用时,系统通过 mcpInfoFromString 函数从全限定名中解析出 Server 名称和原始工具名:
// src/services/mcp/mcpStringUtils.ts (第 15-25 行)
export function mcpInfoFromString(toolString: string): {
serverName: string
toolName: string | undefined
} | null {
const parts = toolString.split('__')
const [mcpPart, serverName, ...toolNameParts] = parts
if (mcpPart !== 'mcp' || !serverName) return null
const toolName = toolNameParts.length > 0
? toolNameParts.join('__') : undefined
return { serverName, toolName }
}4.3 工具冲突处理
在某些场景下,Claude Code 允许 MCP 工具覆盖内置工具。这通过 CLAUDE_AGENT_SDK_MCP_NO_PREFIX 环境变量控制。当该环境变量为真时,SDK 类型的 MCP Server 的工具会保留原始名称,不再添加 mcp__ 前缀:
// src/services/mcp/client.ts (第 1758-1762 行)
const skipPrefix =
client.config.type === 'sdk' &&
isEnvTruthy(process.env.CLAUDE_AGENT_SDK_MCP_NO_PREFIX)
return {
...MCPTool,
name: skipPrefix ? tool.name : fullyQualifiedName,
mcpInfo: { serverName: client.name, toolName: tool.name },
}这种设计主要用于 IDE 插件场景,其中 MCP Server 提供的工具(如代码执行、诊断获取)需要直接替代 Claude Code 的内置实现。同时,mcpInfo 字段始终保留,确保权限检查仍然基于全限定名进行,防止未经授权的工具调用。
五、安全与隔离
5.1 Server 审批流程
Claude Code 对 MCP Server 的连接和工具调用实施了多层安全审批。在配置层面,channelPermissions.ts 和 channelAllowlist.ts 实现了基于通道(Channel)的权限控制:
// src/services/mcp/useManageMCPConnections.ts (第 250-280 行,概述)
// Channel Permission Relay 机制
if (feature('KAIROS') || feature('KAIROS_CHANNELS')) {
const callbacks = createChannelPermissionCallbacks()
// 将回调存入 AppState,供 interactiveHandler 订阅
setAppState(prev => ({
...prev,
channelPermissionCallbacks: callbacks,
}))
}在工具调用层面,每个 MCP 工具的 checkPermissions 方法都会返回一个 "passthrough" 行为,并提示用户添加权限规则:
// src/services/mcp/client.ts (第 1810-1825 行,概述)
async checkPermissions() {
return {
behavior: 'passthrough' as const,
message: 'MCPTool requires permission.',
suggestions: [{
type: 'addRules' as const,
rules: [{ toolName: fullyQualifiedName, ruleContent: undefined }],
behavior: 'allow' as const,
destination: 'localSettings' as const,
}],
}
}这意味着首次调用某个 MCP 工具时,Claude Code 会暂停执行并向用户展示审批提示,用户可以选择允许、拒绝或添加持久化规则。
5.2 工具执行沙箱
虽然 MCP 协议本身没有定义沙箱机制,但 Claude Code 通过多种手段实现了工具执行的隔离和安全控制。这些机制共同构成了一道多层次的防护网,防止恶意或异常的 MCP Server 对系统造成不可逆的损害。
输出截断控制:mcpValidation.ts 实现了 MCP 工具输出的 Token 上限控制,防止恶意 Server 通过超大输出消耗上下文窗口:
// src/utils/mcpValidation.ts (第 20-40 行)
export function getMaxMcpOutputTokens(): number {
const envValue = process.env.MAX_MCP_OUTPUT_TOKENS
if (envValue) {
const parsed = parseInt(envValue, 10)
if (Number.isFinite(parsed) && parsed > 0) return parsed
}
const overrides = getFeatureValue_CACHED_MAY_BE_STALE<Record<string, number> | null>(
'tengu_satin_quoll', {}
)
const override = overrides?.['mcp_tool']
if (typeof override === 'number' && Number.isFinite(override) && override > 0) {
return override
}
return DEFAULT_MAX_MCP_OUTPUT_TOKENS // 25000
}输出截断的优先级为:环境变量 > GrowthBook Feature Flag > 硬编码默认值。当输出超过阈值时,Claude Code 会截断内容并附加提示信息,建议用户使用分页或过滤工具获取特定数据。
超时控制:每个 MCP 工具调用都有超时保护。默认超时为约 27.8 小时(100_000_000 毫秒),但可以通过 MCP_TOOL_TIMEOUT 环境变量调整:
// src/services/mcp/client.ts (第 210-220 行)
const DEFAULT_MCP_TOOL_TIMEOUT_MS = 100_000_000
function getMcpToolTimeoutMs(): number {
return parseInt(process.env.MCP_TOOL_TIMEOUT || '', 10)
|| DEFAULT_MCP_TOOL_TIMEOUT_MS
}OAuth 认证隔离:对于需要 OAuth 的远程 Server,Claude Code 实现了独立的认证状态缓存(mcp-needs-auth-cache.json),缓存 TTL 为 15 分钟。当 Server 返回 401 时,系统会将该 Server 标记为 "needs-auth" 状态,避免在短时间内重复尝试无效连接:
// src/services/mcp/client.ts (第 250-270 行)
const MCP_AUTH_CACHE_TTL_MS = 15 * 60 * 1000 // 15 min
function setMcpAuthCacheEntry(serverId: string): void {
writeChain = writeChain.then(async () => {
const cache = await getMcpAuthCache()
cache[serverId] = { timestamp: Date.now() }
await writeFile(cachePath, jsonStringify(cache))
authCachePromise = null
})
}5.3 错误隔离
Claude Code 为 MCP 相关的错误定义了专门的错误类,确保不同类型的错误能够被正确捕获和处理:
// src/services/mcp/client.ts (第 80-100 行)
export class McpAuthError extends Error {
serverName: string
constructor(serverName: string, message: string) {
super(message)
this.name = 'McpAuthError'
this.serverName = serverName
}
}
export class McpToolCallError extends TelemetrySafeError {
constructor(
message: string,
telemetryMessage: string,
readonly mcpMeta?: { _meta?: Record<string, unknown> },
) {
super(message, telemetryMessage)
this.name = 'McpToolCallError'
}
}在工具调用链路中,错误按照以下层次进行隔离和处理:
flowchart TD
A[用户请求调用 MCP 工具] --> B{检查连接状态}
B -->|已断开| C[尝试重连]
C --> D{重连成功?}
D -->|否| E[返回连接失败错误]
D -->|是| F[发送 tools/call 请求]
B -->|已连接| F
F --> G{响应类型}
G -->|正常完成| H[处理并截断输出]
G -->|isError=true| I[抛出 McpToolCallError]
G -->|401 Unauthorized| J[抛出 McpAuthError]
G -->|Session 过期| K[清除缓存并抛出 McpSessionExpiredError]
G -->|超时| L[抛出超时错误]
H --> M[返回结果给 LLM]
I --> N[展示错误信息给用户]
J --> O[标记 Server 为 needs-auth]
K --> P[上层捕获并重试]
L --> Q[展示超时提示]特别值得关注的是 URL Elicitation 机制。当 MCP 工具需要用户打开浏览器完成 OAuth 授权时,Server 会返回特殊的 JSON-RPC 错误码 -32042。Claude Code 的 callMCPToolWithUrlElicitationRetry 函数会捕获这一错误,弹出交互式对话框让用户确认是否打开 URL:
// src/services/mcp/client.ts (第 2700-2750 行,概述)
if (errorCode === -32042) {
const elicitations = ElicitRequestSchema.parse(errorData)
for (const elicitation of elicitations) {
// 先尝试 Hook 自动处理
const hookResponse = await runElicitationHooks(serverName, elicitation, signal)
if (hookResponse?.action === 'accept') continue
// 否则弹出用户确认对话框
const userResult = await showElicitationDialog(elicitation)
if (userResult.action !== 'accept') {
return { content: 'URL elicitation was declined by user.' }
}
}
// 用户确认后重试工具调用
}这种两阶段授权流程(Hook 自动处理 → 用户手动确认)既支持了自动化测试和 CI 场景,又确保了生产环境中的用户知情同意。
六、总结与展望
Claude Code 的 MCP 集成展现了生产级 AI 工具链应有的设计深度。从协议层面看,它严格遵循 JSON-RPC 2.0 规范,通过 Zod Schema 实现了运行时类型安全;从架构层面看,它采用分层设计将传输、连接、工具调用和权限控制解耦,支持 stdio、SSE、HTTP、WebSocket 等多种传输方式;从工程实践看,它在进程生命周期管理、连接重连、输出截断和错误隔离等方面都体现了对鲁棒性的极致追求。
MCP 协议正在快速演进,2025 年 3 月的更新引入了 Streamable HTTP 传输和更完善的认证流程。Claude Code 的源码中可以看到对这些新特性的积极跟进,例如 StreamableHTTPClientTransport 的集成和 OAuth Step-Up 检测的实现。对于开发者而言,深入理解 Claude Code 的 MCP 集成机制,不仅有助于更好地使用和扩展 MCP Server,也为构建自己的 AI Agent 工具链提供了宝贵的参考范式。
参考源码文件:
src/services/mcp/client.ts:连接管理、工具调用、重连逻辑src/services/mcp/types.ts:类型定义与配置 Schemasrc/services/mcp/useManageMCPConnections.ts:连接生命周期与重连策略src/services/mcp/MCPConnectionManager.tsx:React 上下文封装src/utils/mcpWebSocketTransport.ts:WebSocket 传输实现src/utils/mcpValidation.ts:输出验证与截断src/services/mcp/mcpStringUtils.ts:工具名称解析与构建src/services/mcp/config.ts:配置管理与 Server 去重