上一篇我们分析了子 Agent 架构。今天深入第五个主题:MCP 协议集成。MCP(Model Context Protocol)是 Anthropic 的开放协议,让外部工具可以接入 Claude。
MCP 是什么?
MCP 是一个标准化协议,定义了:
- Resources:文件、数据库记录等数据源
- Tools:可执行的函数
- Prompts:预定义的提示模板
- Sampling:请求 LLM 生成内容
Claude Code 主要使用 Tools 和 Resources。
Transport 类型
type Transport =
| 'stdio' // 标准输入输出(本地进程)
| 'sse' // Server-Sent Events
| 'sse-ide' // IDE SSE
| 'http' // HTTP
| 'ws' // WebSocket
| 'sdk' // SDK 内部(进程内)
| Transport | 用途 |
|---|---|
| stdio | 本地命令行工具(如 uvx mcp-server-git) |
| sse | 远程服务器(如 api.example.com/mcp) |
| http | HTTP API |
| ws | WebSocket 连接 |
| sdk | 内部 SDK 集成 |
Server Config Schema
// stdio 配置
type McpStdioServerConfig = {
type: 'stdio'
command: string // 如 'uvx', 'npx'
args: string[] // 如 ['mcp-server-git']
env?: Record<string, string>
cwd?: string
}
// SSE 配置
type McpSSEServerConfig = {
type: 'sse'
url: string
headers?: Record<string, string>
oauth?: OAuthConfig
}
// HTTP 配置
type McpHTTPServerConfig = {
type: 'http'
url: string
headers?: Record<string, string>
oauth?: OAuthConfig
}
// SDK 配置(内部)
type McpSdkServerConfig = {
type: 'sdk'
name: string
}
Config Scope
配置来源有多个层级:
type ConfigScope =
| 'user' // ~/.claude/settings.json
| 'project' // .claude/settings.json
| 'local' // .claude/settings.local.json
| 'enterprise' // MDM/企业配置
| 'claudeai' // Claude.ai 配置
| 'managed' // 远程管理配置
| 'dynamic' // 动态添加
优先级:local > project > user > enterprise。
Connection States
type MCPServerConnection =
| ConnectedMCPServer
| FailedMCPServer
| NeedsAuthMCPServer
| PendingMCPServer
| DisabledMCPServer
type ConnectedMCPServer = {
type: 'connected'
client: Client
name: string
capabilities: ServerCapabilities
serverInfo?: { name: string; version: string }
instructions?: string
tools: MCPTool[]
resources?: ServerResource[]
}
type FailedMCPServer = {
type: 'failed'
name: string
error: string
config: ScopedMcpServerConfig
}
type NeedsAuthMCPServer = {
type: 'needs-auth'
name: string
config: ScopedMcpServerConfig
}
连接流程
async function connectMcpServer(config: McpServerConfig, scope: ConfigScope): MCPServerConnection {
const name = config.name ?? generateServerName(config)
// === 阶段 1: 创建 Client ===
const client = new Client({ name: 'claude-code', version: CLI_VERSION })
// === 阶段 2: 选择 Transport ===
let transport: Transport
if (config.type === 'stdio') {
// 启动子进程
const childProcess = spawn(config.command, config.args, {
env: { ...process.env, ...config.env },
cwd: config.cwd,
stdio: ['pipe', 'pipe', 'pipe'],
})
transport = new StdioClientTransport({
stdin: childProcess.stdin,
stdout: childProcess.stdout,
})
}
if (config.type === 'sse') {
transport = new SSEClientTransport({
url: config.url,
headers: config.headers,
})
}
if (config.type === 'http') {
transport = new HTTPClientTransport({
url: config.url,
headers: config.headers,
})
}
// === 阶段 3: 连接 ===
try {
await client.connect(transport, {
timeout: 30_000, // 30 秒超时
})
} catch (error) {
return {
type: 'failed',
name,
error: error.message,
config: { config, scope },
}
}
// === 阶段 4: 获取 Capabilities ===
const capabilities = client.getServerCapabilities()
// === 阶段 5: 获取 Tools ===
let tools: MCPTool[] = []
if (capabilities?.tools) {
const toolsResult = await client.listTools()
tools = toolsResult.tools.map(normalizeMcpTool)
}
// === 阶段 6: 获取 Resources ===
let resources: ServerResource[] = []
if (capabilities?.resources) {
const resourcesResult = await client.listResources()
resources = resourcesResult.resources
}
// === 阶段 7: 获取 Server Info ===
const serverInfo = client.getServerInfo()
// === 阶段 8: OAuth 检查 ===
if (config.oauth && needsAuthentication(client)) {
return {
type: 'needs-auth',
name,
config: { config, scope },
}
}
return {
type: 'connected',
client,
name,
capabilities,
serverInfo,
tools,
resources,
config: { config, scope },
cleanup: async () => {
await client.close()
if (childProcess) {
childProcess.kill()
}
},
}
}
8 个阶段,完整的连接生命周期。
OAuth 认证
type OAuthConfig = {
clientId?: string
callbackPort?: number
authServerMetadataUrl?: string
xaa?: boolean // Cross-App Access
}
async function authenticateMcpServer(server: NeedsAuthMCPServer): ConnectedMCPServer {
const config = server.config.config
// === 阶段 1: 获取 OAuth 元数据 ===
const metadata = await fetchOAuthMetadata(config.oauth.authServerMetadataUrl)
// === 阶段 2: 生成授权 URL ===
const authUrl = generateAuthUrl({
authorizationEndpoint: metadata.authorization_endpoint,
clientId: config.oauth.clientId,
redirectUri: `http://localhost:${config.oauth.callbackPort}/callback`,
scope: metadata.scopes_supported ?? ['openid'],
state: generateState(),
})
// === 阶段 3: 启动回调监听 ===
const codePromise = waitForAuthCode(config.oauth.callbackPort)
// === 阶段 4: 打开浏览器 ===
openBrowser(authUrl)
// === 阶段 5: 获取 Code ===
const code = await codePromise
// === 阶段 6: 交换 Token ===
const tokens = await exchangeCodeForTokens({
tokenEndpoint: metadata.token_endpoint,
clientId: config.oauth.clientId,
code,
redirectUri: `http://localhost:${config.oauth.callbackPort}/callback`,
})
// === 阶段 7: 保存 Token ===
saveMcpOAuthToken(server.name, tokens)
// === 阶段 8: 重新连接 ===
const newConfig = {
...config,
headers: {
...config.headers,
Authorization: `Bearer ${tokens.access_token}`,
},
}
return connectMcpServer(newConfig, server.config.scope)
}
完整的 OAuth 流程——浏览器授权 + 本地回调。
MCP Tool 动态创建
function createMcpTool(mcpTool: MCPToolDefinition, server: ConnectedMCPServer): Tool {
const toolName = `mcp__${server.name}__${mcpTool.name}`
return buildTool({
name: toolName,
isMcp: true,
mcpInfo: {
serverName: server.name,
toolName: mcpTool.name,
},
// Schema 转换
inputSchema: convertMcpSchemaToZod(mcpTool.inputSchema),
// 执行
async call(args, context) {
const result = await server.client.callTool({
name: mcpTool.name,
arguments: args,
})
return processMcpToolResult(result)
},
// 权限
async checkPermissions(args, context) {
// MCP 工具权限检查
return checkMcpPermission(server, mcpTool, args, context)
},
// 属性
isConcurrencySafe: () => mcpTool.annotations?.safeToRunConcurrently ?? false,
isReadOnly: () => mcpTool.annotations?.readOnly ?? false,
isDestructive: () => mcpTool.annotations?.destructive ?? false,
// 描述
description: mcpTool.description,
// 最大结果大小
maxResultSizeChars: 100_000,
})
}
每个 MCP tool 被转换为 Claude Code 的 Tool 接口。
Schema 转换
function convertMcpSchemaToZod(mcpSchema: MCPInputSchema): z.ZodType {
// MCP 使用 JSON Schema
// Claude Code 使用 Zod
if (mcpSchema.type === 'object') {
const shape: Record<string, z.ZodType> = {}
for (const [key, prop] of Object.entries(mcpSchema.properties ?? {})) {
shape[key] = convertMcpPropertyToZod(prop)
if (mcpSchema.required?.includes(key)) {
// 必须字段
} else {
// 可选字段
shape[key] = shape[key].optional()
}
}
return z.object(shape)
}
// 其他类型...
}
function convertMcpPropertyToZod(prop: MCPProperty): z.ZodType {
if (prop.type === 'string') {
let schema = z.string()
if (prop.description) schema = schema.describe(prop.description)
return schema
}
if (prop.type === 'number') {
return z.number()
}
if (prop.type === 'boolean') {
return z.boolean()
}
if (prop.type === 'array') {
return z.array(convertMcpPropertyToZod(prop.items))
}
// ...
}
JSON Schema → Zod 转换。
Resources 访问
// MCP Resources 可以作为文件读取
async function readMcpResource(uri: string, server: ConnectedMCPServer): MCPResourceContent {
const result = await server.client.readResource({ uri })
return {
uri,
mimeType: result.contents[0].mimeType,
text: result.contents[0].text,
blob: result.contents[0].blob,
}
}
// 某些 Resources 可以订阅更新
async function subscribeMcpResource(uri: string, server: ConnectedMCPServer): void {
if (server.capabilities?.resources?.subscribe) {
await server.client.subscribeResource({ uri })
// 监听更新
server.client.on('resource_updated', (event) => {
if (event.uri === uri) {
notifyResourceChanged(uri)
}
})
}
}
Resources 作为数据源——可以读取、订阅更新。
Channel Permissions
// MCP 工具权限控制
type ChannelPermission = {
allowedTools?: string[] // 允许的工具列表
deniedTools?: string[] // 禁止的工具列表
allowedResources?: string[] // 允许的资源列表
readPermission?: 'allow' | 'ask' | 'deny'
writePermission?: 'allow' | 'ask' | 'deny'
}
function checkMcpPermission(
server: ConnectedMCPServer,
tool: MCPTool,
args: unknown,
context: ToolUseContext,
): PermissionResult {
// 检查 channel permissions
const permissions = getChannelPermissions(server.name)
if (permissions.deniedTools?.includes(tool.name)) {
return { behavior: 'deny', message: `Tool ${tool.name} is denied for ${server.name}` }
}
if (permissions.allowedTools && !permissions.allowedTools.includes(tool.name)) {
return { behavior: 'ask', message: `Tool ${tool.name} is not in allowed list` }
}
// 检查读写权限
if (tool.annotations?.destructive && permissions.writePermission === 'deny') {
return { behavior: 'deny', message: 'Write operations denied' }
}
// 默认通过 MCP 权限系统
return { behavior: 'passthrough' }
}
MCP server 可以配置工具白名单/黑名单。
Cross-App Access (XAA)
XAA 是 SEP-990 规范,允许 MCP servers 通过 IdP 认证访问跨应用资源:
// XAA 配置
type XaaIdpConfig = {
issuer: string
clientId: string
callbackPort: number
}
// Server 配置中启用 XAA
const mcpConfig = {
type: 'sse',
url: 'https://api.example.com/mcp',
oauth: {
xaa: true, // 启用 XAA
},
}
// XAA 认证流程
async function authenticateWithXaa(server: NeedsAuthMCPServer): ConnectedMCPServer {
// 1. 获取 IdP 配置(全局配置)
const idpConfig = getXaaIdpConfig()
// 2. 使用 IdP 认证
const tokens = await authenticateWithIdp(idpConfig)
// 3. Token 可用于所有 XAA-enabled servers
saveXaaToken(tokens)
// 4. 重新连接
return connectMcpServerWithXaaToken(server.config, tokens)
}
一次 IdP 认证,所有 XAA server 共享。
环境变量展开
// MCP 配置支持环境变量
const mcpConfig = {
type: 'stdio',
command: 'uvx',
args: ['mcp-server-git'],
env: {
API_KEY: '${API_KEY}', // 引用环境变量
CUSTOM_URL: 'https://${HOST}', // 组合使用
},
}
function expandEnvVars(config: McpServerConfig): McpServerConfig {
if (config.env) {
const expandedEnv: Record<string, string> = {}
for (const [key, value] of Object.entries(config.env)) {
expandedEnv[key] = expandEnvVarString(value)
}
return { ...config, env: expandedEnv }
}
return config
}
function expandEnvVarString(value: string): string {
// ${VAR} 格式
return value.replace(/\$\{(\w+)\}/g, (_, name) => {
return process.env[name] ?? ''
})
}
配置中引用环境变量——安全地传递敏感信息。
MCP Reconnection
// MCP server 断开后自动重连
type PendingMCPServer = {
type: 'pending'
name: string
config: ScopedMcpServerConfig
reconnectAttempt?: number
maxReconnectAttempts?: number
}
async function handleMcpDisconnection(server: ConnectedMCPServer): MCPServerConnection {
const maxAttempts = server.config.config.reconnect?.maxAttempts ?? 3
const delayMs = server.config.config.reconnect?.delayMs ?? 5000
// 尝试重连
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
await sleep(delayMs * attempt) // 递增延迟
try {
const reconnected = await connectMcpServer(
server.config.config,
server.config.scope,
)
if (reconnected.type === 'connected') {
return reconnected
}
} catch (error) {
// 继续尝试
}
}
// 重连失败
return {
type: 'failed',
name: server.name,
error: 'Reconnection failed after max attempts',
config: server.config,
}
}
自动重连,递增延迟。
In-Process Transport
// SDK 内部传输(进程内)
class InProcessTransport implements Transport {
private server: MCPServer
private client: Client
async connect(): void {
// 直接连接,无网络
this.server = createInProcessServer()
await this.client.connect(this.server.transport)
}
async send(message: JSONRPCMessage): void {
this.server.handleMessage(message)
}
onMessage(handler: (message: JSONRPCMessage) => void): void {
this.server.transport.onMessage(handler)
}
}
进程内传输——无网络开销,适合内置 MCP servers。
VSCode SDK MCP
// VSCode SDK MCP 集成
type VscodeSdkMcpConfig = {
type: 'sdk'
name: 'vscode'
}
async function connectVscodeSdkMcp(): ConnectedMCPServer {
// VSCode 提供内置 MCP server
// 通过 SDK transport 连接
const client = new Client({ name: 'claude-code', version: CLI_VERSION })
const transport = new SdkControlTransport()
await client.connect(transport)
return {
type: 'connected',
client,
name: 'vscode',
tools: extractVscodeTools(client),
capabilities: client.getServerCapabilities(),
}
}
VSCode SDK 提供内置 MCP server——无需配置,自动连接。
Elicitation Handler
// MCP server 可以请求用户输入
type ElicitationRequest = {
type: 'elicitation'
message: string
buttons?: Array<{ text: string; action: string }>
input?: { type: 'text' | 'select'; options?: string[] }
}
async function handleElicitation(
request: ElicitationRequest,
server: ConnectedMCPServer,
): ElicitationResponse {
// 显示用户提示
const response = await askUserQuestion({
message: request.message,
buttons: request.buttons,
input: request.input,
})
// 执行 ElicitationResult Hook
await executeElicitationResultHooks({
serverName: server.name,
request,
response,
})
return response
}
MCP server 可以请求用户输入——Elicitation 协议。
Summary
MCP 协议集成展示了开放协议设计:
| 设计点 | 实现 |
|---|---|
| Transport | stdio/sse/http/ws/sdk 五种传输 |
| Config Scope | user/project/local/enterprise/claudeai |
| Connection States | connected/failed/needs-auth/pending/disabled |
| OAuth | 浏览器授权 + 本地回调 |
| Tool Creation | MCP tool → Claude Tool 转换 |
| Schema | JSON Schema → Zod 转换 |
| Permissions | channel permissions + read/write 控制 |
| XAA | Cross-App Access,一次认证多处使用 |
| Reconnection | 自动重连,递增延迟 |
| Elicitation | MCP server 请求用户输入 |
核心设计:多 Transport + OAuth + 动态 Tool + 权限控制。
下一篇,我们将深入对话压缩——Token 超限时如何压缩信息。