模块八:MCP 协议 | 前置依赖:第 10 课 | 预计学习时间:70 分钟
学习目标
完成本课后,你将能够:
- 解释 MCP(Model Context Protocol)的设计目标及其在 Claude Code 中的角色
- 列出 Claude Code 支持的所有传输层类型及其适用场景
- 说明
services/mcp/client.ts中连接管理、工具注册、资源注册的完整流程 - 理解
.mcp.json配置文件的层级合并机制与config.ts的实现
23.1 什么是 MCP
MCP(Model Context Protocol)是 Anthropic 制定的开放协议标准,目的是让 AI 模型以统一方式访问外部工具和外部数据源。你可以把它理解为 AI 世界的 "USB 接口":
┌────────────────┐ MCP 协议 ┌────────────────┐
│ │ ─────────────────────▶ │ │
│ Claude Code │ JSON-RPC over │ MCP Server │
│ (MCP Client) │ Stdio / SSE / HTTP │ (工具提供者) │
│ │ ◀───────────────────── │ │
└────────────────┘ └────────────────┘
核心价值:
- 统一接口:无论 MCP Server 提供的是 Slack 消息、GitHub PR、数据库查询还是浏览器操作,Claude Code 用同一套协议与之通信
- 动态发现:客户端连接后自动发现服务器提供的工具(tools)、资源(resources)、提示(prompts)和命令(commands)
- 安全隔离:MCP Server 在独立进程中运行,Claude Code 通过传输层与之通信
services/mcp/ 目录概览
services/mcp/ 包含 25 个文件,是 Claude Code 中最大的服务模块之一:
| 文件 | 大小 | 职责 |
|---|---|---|
client.ts | 119KB | 连接管理、工具注册、资源注册、工具调用 |
auth.ts | 89KB | OAuth 认证、Token 刷新、凭证存储 |
config.ts | 51KB | 配置加载、合并、校验、.mcp.json 管理 |
useManageMCPConnections.ts | 45KB | React Hook,连接生命周期管理 |
types.ts | 7KB | 类型定义与 Zod Schema |
InProcessTransport.ts | 1.8KB | 进程内传输层 |
SdkControlTransport.ts | 4.5KB | SDK 控制传输桥接 |
channelPermissions.ts | 9KB | Channel 权限中继 |
elicitationHandler.ts | 10KB | 交互式认证流 |
officialRegistry.ts | 2KB | 官方注册表查询 |
normalization.ts | 879B | 名称标准化 |
MCPConnectionManager.tsx | 8KB | React Context Provider |
23.2 类型系统与连接状态
传输层类型
types.ts 定义了所有支持的传输层:
export const TransportSchema = lazySchema(() =>
z.enum(['stdio', 'sse', 'sse-ide', 'http', 'ws', 'sdk']),
)
每种传输对应一个配置 Schema:
// Stdio — 本地子进程通信
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 — Server-Sent Events(远程)
McpSSEServerConfigSchema = z.object({
type: z.literal('sse'),
url: z.string(),
headers: z.record(z.string(), z.string()).optional(),
oauth: McpOAuthConfigSchema().optional(),
})
// HTTP — Streamable HTTP(MCP 2025-03-26 规范推荐)
McpHTTPServerConfigSchema = z.object({
type: z.literal('http'),
url: z.string(),
headers: z.record(z.string(), z.string()).optional(),
oauth: McpOAuthConfigSchema().optional(),
})
// WebSocket — 双向实时通信
McpWebSocketServerConfigSchema = z.object({
type: z.literal('ws'),
url: z.string(),
headers: z.record(z.string(), z.string()).optional(),
})
// SDK — SDK 进程内服务器
McpSdkServerConfigSchema = z.object({
type: z.literal('sdk'),
name: z.string(),
})
// Claude.ai Proxy — Claude.ai 代理连接
McpClaudeAIProxyServerConfigSchema = z.object({
type: z.literal('claudeai-proxy'),
url: z.string(),
id: z.string(),
})
连接状态机
每个 MCP Server 的连接状态是一个判别联合类型:
type MCPServerConnection =
| ConnectedMCPServer // 连接成功
| FailedMCPServer // 连接失败
| NeedsAuthMCPServer // 需要认证
| PendingMCPServer // 连接中
| DisabledMCPServer // 已禁用
状态转换图:
┌──────────┐
┌────────────▶│ pending │◀─────────────┐
│ └────┬─────┘ │
│ │ │
│ ┌────────┼────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────┐ ┌──────────┐ │
│ │connected │ │failed│ │needs-auth│ │ reconnect
│ └──────────┘ └──────┘ └──────────┘ │
│ │ │ │
│ │ auth失败/session过期 │ 认证完成│
│ ▼ │ │
│ ┌──────────┐ └───────┘
│ │needs-auth│
│ └──────────┘
│
│ ┌──────────┐
└──────────│ disabled │ (用户手动禁用)
└──────────┘
ConnectedMCPServer 包含完整的连接信息:
type ConnectedMCPServer = {
client: Client // MCP SDK 客户端实例
name: string // 服务器名称
type: 'connected'
capabilities: ServerCapabilities // 服务器能力声明
serverInfo?: {
name: string
version: string
}
instructions?: string // 服务器提供的使用说明
config: ScopedMcpServerConfig
cleanup: () => Promise<void> // 清理函数
}
23.3 传输层实现
Stdio 传输
最常见的传输方式 — 启动一个子进程,通过 stdin/stdout 通信:
// client.ts 中的 Stdio 连接逻辑
const finalCommand = process.env.CLAUDE_CODE_SHELL_PREFIX || serverRef.command
const finalArgs = process.env.CLAUDE_CODE_SHELL_PREFIX
? [[serverRef.command, ...serverRef.args].join(' ')]
: serverRef.args
transport = new StdioClientTransport({
command: finalCommand,
args: finalArgs,
env: {
...subprocessEnv(),
...serverRef.env, // 用户配置的环境变量
},
stderr: 'pipe', // 防止 MCP Server 的错误输出污染 UI
})
stderr 处理:MCP 规范仅定义 stdout 上的 JSON-RPC 通信。stderr 被独立捕获并限制为 64MB,用于调试失败的连接。
SSE 传输
SSE(Server-Sent Events)用于远程 MCP Server。这是一个长连接,服务器可以主动推送消息:
const transportOptions: SSEClientTransportOptions = {
authProvider,
fetch: wrapFetchWithTimeout(
wrapFetchWithStepUpDetection(createFetchWithInit(), authProvider),
),
requestInit: {
headers: {
'User-Agent': getMCPUserAgent(),
...combinedHeaders,
},
},
// EventSource 连接是长期存活的,不应用 60 秒超时
eventSourceInit: {
fetch: async (url, init) => {
const authHeaders = {}
const tokens = await authProvider.tokens()
if (tokens) authHeaders.Authorization = `Bearer ${tokens.access_token}`
return fetch(url, {
...init,
headers: { ...authHeaders, ...init?.headers, Accept: 'text/event-stream' },
})
},
},
}
transport = new SSEClientTransport(new URL(serverRef.url), transportOptions)
关键设计:SSE 有两个独立的 fetch 函数 — 一个用于普通 POST 请求(带 60 秒超时),另一个用于 EventSource 长连接(无超时)。
HTTP 传输(Streamable HTTP)
MCP 2025-03-26 规范推荐的新传输方式:
// 要求客户端在 POST 请求中声明 Accept 头
const MCP_STREAMABLE_HTTP_ACCEPT = 'application/json, text/event-stream'
transport = new StreamableHTTPClientTransport(
new URL(serverRef.url),
{
authProvider,
fetch: wrapFetchWithTimeout(
wrapFetchWithStepUpDetection(createFetchWithInit(), authProvider),
),
requestInit: { headers: { 'User-Agent': getMCPUserAgent(), ...headers } },
},
)
InProcessTransport — 进程内传输
对于某些内置 MCP Server(如 Claude-in-Chrome、Computer Use),启动一个单独的子进程太重(~325 MB)。InProcessTransport 允许 Server 和 Client 在同一进程内通信:
class InProcessTransport implements Transport {
private peer: InProcessTransport | undefined
private closed = false
onclose?: () => void
onerror?: (error: Error) => void
onmessage?: (message: JSONRPCMessage) => void
_setPeer(peer: InProcessTransport): void {
this.peer = peer
}
async send(message: JSONRPCMessage): Promise<void> {
if (this.closed) throw new Error('Transport is closed')
// 使用 queueMicrotask 异步投递,避免同步调用导致栈溢出
queueMicrotask(() => {
this.peer?.onmessage?.(message)
})
}
async close(): Promise<void> {
if (this.closed) return
this.closed = true
this.onclose?.()
if (this.peer && !this.peer.closed) {
this.peer.closed = true
this.peer.onclose?.()
}
}
}
// 创建成对的传输通道
function createLinkedTransportPair(): [Transport, Transport] {
const a = new InProcessTransport()
const b = new InProcessTransport()
a._setPeer(b)
b._setPeer(a)
return [a, b] // [clientTransport, serverTransport]
}
使用场景:
// Chrome MCP Server 的进程内连接
const { createLinkedTransportPair } = await import('./InProcessTransport.js')
const [clientTransport, serverTransport] = createLinkedTransportPair()
await inProcessServer.connect(serverTransport)
transport = clientTransport
SdkControlTransport — SDK 桥接传输
当 MCP Server 运行在 SDK 进程中时,需要通过 stdout/stdin 的控制消息桥接通信:
CLI 进程 SDK 进程
┌──────────┐ 控制消息 (stdout) ┌──────────┐
│MCP Client│ ───────────────────────▶ │MCP Server│
│ │ ◀─────────────────────── │ │
└──────────┘ 控制响应 (stdin) └──────────┘
▲ ▲
│ │
SdkControlClientTransport SdkControlServerTransport
23.4 连接管理(client.ts)
client.ts 是 119KB 的核心模块,包含连接建立、工具注册、资源管理的完整逻辑。
连接建立 — connectToServer
使用 lodash memoize 缓存连接,避免重复连接同一服务器:
export const connectToServer = memoize(
async (name, serverRef, serverStats?) => {
// 1. 根据 serverRef.type 选择传输层
// 2. 创建 Client 实例
const client = new Client(
{
name: 'claude-code',
title: 'Claude Code',
version: MACRO.VERSION ?? 'unknown',
},
{
capabilities: {
roots: {},
elicitation: {}, // 声明支持交互式认证
},
},
)
// 3. 注册 elicitation handler(交互式认证)
// 4. 注册 roots handler(工作目录通知)
// 5. 连接(带超时,默认 30 秒)
await client.connect(transport)
// 6. 返回 ConnectedMCPServer 或错误状态
},
getServerCacheKey, // 缓存键 = 名称 + JSON(配置)
)
连接批量控制:
// 本地服务器(Stdio)默认并发 3 个
function getMcpServerConnectionBatchSize(): number {
return parseInt(process.env.MCP_SERVER_CONNECTION_BATCH_SIZE || '', 10) || 3
}
// 远程服务器默认并发 20 个(网络 I/O 为主,可以更高)
function getRemoteMcpServerConnectionBatchSize(): number {
return parseInt(process.env.MCP_REMOTE_SERVER_CONNECTION_BATCH_SIZE || '', 10) || 20
}
工具注册 — fetchToolsForClient
连接成功后,自动获取服务器提供的工具并包装为 Claude Code 的 Tool 接口:
ConnectedMCPServer
│
▼
client.listTools()
│
▼
┌─────────────────────────────────┐
│ 对每个 MCP Tool: │
│ 1. 规范化名称(normalizeNameForMCP)│
│ 2. 截断描述(max 2048 chars) │
│ 3. 包装为 MCPTool 实例 │
│ 4. 注册为 Claude Code Tool │
└─────────────────────────────────┘
│
▼
MCPTool[] → 加入 AppState.mcp.tools
MCP 工具的命名规范:mcp__<serverName>__<toolName>,使用双下划线分隔。
资源注册
同样自动发现并注册资源(如文件、数据库表):
// 通过两个内置工具暴露给模型
ListMcpResourcesTool // 列出所有 MCP 资源
ReadMcpResourceTool // 读取特定 MCP 资源的内容
Session 过期处理
MCP 协议定义了 Session 概念,服务器可能返回 404 + JSON-RPC code -32001 表示 Session 过期:
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 过期时清除缓存的连接,下次调用自动重连。
23.5 配置系统(config.ts)
.mcp.json 文件
项目根目录的 .mcp.json 是最常见的 MCP 配置方式:
{
"mcpServers": {
"my-local-tool": {
"command": "npx",
"args": ["-y", "@my-org/mcp-server"],
"env": {
"API_KEY": "${API_KEY}"
}
},
"remote-api": {
"type": "http",
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "Bearer ${TOKEN}"
}
}
}
}
配置层级
配置来自多个 scope,按优先级合并:
优先级(高 → 低):
┌──────────────┐
│ dynamic │ ← SDK 运行时注入
├──────────────┤
│ local │ ← .mcp.json(项目根目录)
├──────────────┤
│ project │ ← settings.json 中的 projectSettings
├──────────────┤
│ user │ ← ~/.claude/settings.json
├──────────────┤
│ managed │ ← 企业管理策略
├──────────────┤
│ enterprise │ ← managed-mcp.json
├──────────────┤
│ claudeai │ ← Claude.ai Web 端配置的连接器
└──────────────┘
每个配置项附带 scope 标记:
type ScopedMcpServerConfig = McpServerConfig & {
scope: ConfigScope // 'local' | 'user' | 'project' | 'dynamic' | ...
pluginSource?: string // 插件来源标记
}
环境变量扩展
配置中的 ${VAR_NAME} 会在运行时被替换:
// envExpansion.ts
function expandEnvVarsInString(value: string): string
原子写入
.mcp.json 的写入使用原子操作防止数据损坏:
async function writeMcpjsonFile(config: McpJsonConfig): Promise<void> {
// 1. 读取原文件权限
const stats = await stat(mcpJsonPath)
// 2. 写入临时文件
const handle = await open(tempPath, 'w', existingMode ?? 0o644)
await handle.writeFile(jsonStringify(config, null, 2))
await handle.datasync() // 刷到磁盘
// 3. 原子重命名
await rename(tempPath, mcpJsonPath)
}
去重机制
当插件和手动配置指向同一 MCP Server 时,config.ts 通过签名去重:
function getMcpServerSignature(config: McpServerConfig): string | null {
const cmd = getServerCommandArray(config)
if (cmd) return `stdio:${jsonStringify(cmd)}`
const url = getServerUrl(config)
if (url) return `url:${unwrapCcrProxyUrl(url)}`
return null
}
规则:手动配置 > 插件配置 > Claude.ai 连接器。
23.6 MCPConnectionManager — React 集成
MCPConnectionManager.tsx 将 MCP 连接管理集成到 React 组件树:
function MCPConnectionManager({ children, dynamicMcpConfig, isStrictMcpConfig }) {
const { reconnectMcpServer, toggleMcpServer } =
useManageMCPConnections(dynamicMcpConfig, isStrictMcpConfig)
return (
<MCPConnectionContext.Provider value={{ reconnectMcpServer, toggleMcpServer }}>
{children}
</MCPConnectionContext.Provider>
)
}
useManageMCPConnections(45KB)是最重要的 Hook,负责:
- 启动时批量连接所有配置的 MCP Server
- 监听配置变化,自动重连/断开
- 将连接状态、工具、资源同步到 AppState
- 处理重连逻辑(指数退避)
23.7 名称规范化
MCP 工具名称需要符合 API 模式 ^[a-zA-Z0-9_-]{1,64}$:
function normalizeNameForMCP(name: string): string {
let normalized = name.replace(/[^a-zA-Z0-9_-]/g, '_')
// Claude.ai 服务器名称做额外清理
if (name.startsWith('claude.ai ')) {
normalized = normalized.replace(/_+/g, '_').replace(/^_|_$/g, '')
}
return normalized
}
示例:"My Cool Server" → "My_Cool_Server"
课后练习
练习 1:传输层对比
画一个表格,对比 Stdio、SSE、HTTP、InProcess 四种传输层的适用场景、性能特点、安全边界。思考为什么 Chrome MCP Server 选择了 InProcess 而不是 Stdio。
练习 2:连接状态追踪
阅读 types.ts 中的 5 种连接状态类型,画出完整的状态转换图。标注每种转换的触发条件(如认证失败、网络超时、用户操作)。
练习 3:配置合并模拟
给定以下配置:
.mcp.json(local):{ "slack": { "command": "npx slack-mcp" } }~/.claude/settings.json(user):{ "mcpServers": { "slack": { "command": "npx old-slack" }, "github": { ... } } }
根据层级优先级规则,推导最终的服务器列表。
练习 4:InProcessTransport 扩展
InProcessTransport.send() 使用 queueMicrotask 进行异步投递。尝试解释为什么不能直接同步调用 this.peer?.onmessage?.(message),会导致什么问题。
本课小结
| 要点 | 内容 |
|---|---|
| MCP 定位 | AI 模型访问外部工具/数据的统一协议标准 |
| 传输层 | Stdio(本地)、SSE/HTTP(远程)、WebSocket、InProcess(进程内)、SDK Bridge |
| 连接状态 | 5 种状态的判别联合:connected / failed / needs-auth / pending / disabled |
| client.ts | 119KB,连接管理(memoized)、工具注册(MCPTool)、资源注册、Session 管理 |
| config.ts | 7 层配置优先级,原子写入 .mcp.json,签名去重 |
| 名称规范化 | normalizeNameForMCP 确保符合 API 模式 |
下一课预告
第 24 课:MCP 进阶 — OAuth、沙箱与官方注册表 — 深入 89KB 的 auth.ts,理解 OAuth 认证流程、Token 刷新机制、Elicitation 交互式认证、MCP 工具的权限继承、注入防护以及官方注册表的安全审计。