Skip to content

S07: MCP (模型上下文协议)

核心洞察:MCP 让 Claude Code 从一个封闭系统变成了开放平台 —— 任何人都可以通过标准协议为 Agent 添加新能力,而不需要修改 Agent 本身的代码。

核心问题

Claude Code 内置了 45+ 工具,但无法覆盖所有场景:

  • 访问 Figma 设计稿
  • 操作 Jira 工单
  • 查询 Grafana 监控
  • 操作飞书多维表格

为每个外部服务写内置工具不现实 —— MCP 提供了标准化的扩展协议。

什么是 MCP

Model Context Protocol (MCP) 是 Anthropic 提出的标准协议,定义了 AI 模型与外部工具/资源的交互方式:

┌─────────────┐     MCP 协议      ┌─────────────────┐
│ Claude Code  │ ←──────────────→ │ MCP Server       │
│ (MCP Client) │   stdio/HTTP/SSE │ (Figma/Jira/...) │
└─────────────┘                   └─────────────────┘

MCP Server 暴露:

  • Tools —— 可以被模型调用的函数
  • Resources —— 可以被读取的数据源
  • Prompts —— 预定义的提示词模板

源码定位

文件职责
src/services/mcp/MCP 核心服务 (25 个文件)
src/services/mcp/MCPConnectionManager.tsx连接管理器
src/services/mcp/client.tsMCP 客户端实现 (~12KB)
src/services/mcp/config.ts配置解析 (~5KB)
src/services/mcp/auth.tsOAuth 认证 (~9KB)
src/tools/MCPTool/MCP 工具包装器
src/tools/ListMcpResourcesTool/MCP 资源列表工具
src/tools/ReadMcpResourceTool/MCP 资源读取工具
src/tools/McpAuthTool/MCP 认证工具

MCP 配置

用户通过配置文件声明 MCP 服务器:

json
// ~/.claude/settings.json 或 .claude/settings.json
{
  "mcpServers": {
    "figma": {
      "command": "npx",
      "args": ["-y", "@anthropic-ai/figma-mcp"],
      "env": { "FIGMA_TOKEN": "..." }
    },
    "jira": {
      "command": "node",
      "args": ["./mcp-servers/jira/index.js"],
      "env": { "JIRA_URL": "https://..." }
    },
    "remote-api": {
      "url": "https://api.example.com/mcp",
      "transport": "sse"
    }
  }
}

配置解析

typescript
// src/services/mcp/config.ts - 简化
interface MCPServerConfig {
  // stdio 传输 (本地进程)
  command?: string
  args?: string[]
  env?: Record<string, string>

  // HTTP/SSE 传输 (远程服务)
  url?: string
  transport?: 'stdio' | 'sse' | 'http'

  // 可选
  timeout?: number
  autoApprove?: string[]  // 自动允许的工具名
}

// 配置来源层级
function loadMCPConfig(): Record<string, MCPServerConfig> {
  return mergeConfigs([
    enterpriseConfig,   // 企业配置 (最高优先级)
    userConfig,         // ~/.claude/settings.json
    projectConfig,      // .claude/settings.json
    localConfig,        // .claude.local.json
    pluginConfig,       // 插件发现的服务器
  ])
}

连接管理

MCPConnectionManager 管理所有 MCP 服务器的生命周期:

typescript
// src/services/mcp/MCPConnectionManager.tsx - 简化
class MCPConnectionManager {
  private connections: Map<string, MCPConnection> = new Map()

  // 连接到配置的所有服务器
  async connectAll(configs: Record<string, MCPServerConfig>) {
    for (const [name, config] of Object.entries(configs)) {
      try {
        const connection = await this.connect(name, config)
        this.connections.set(name, connection)
      } catch (error) {
        // 连接失败不影响其他服务器
        console.warn(`Failed to connect to MCP server ${name}: ${error}`)
      }
    }
  }

  // 连接单个服务器
  private async connect(name: string, config: MCPServerConfig): Promise<MCPConnection> {
    // 选择传输方式
    const transport = config.url
      ? new SSETransport(config.url)       // 远程: HTTP/SSE
      : new StdioTransport(config.command, config.args, config.env)  // 本地: stdio

    // 创建 MCP 客户端
    const client = new MCPClient(transport)
    await client.initialize()

    // 发现可用工具
    const tools = await client.listTools()

    return { name, client, tools, config }
  }
}

传输方式

stdio:  父进程 ←──stdin/stdout──→ MCP Server 子进程
SSE:    HTTP Client ←──SSE Stream──→ MCP Server
HTTP:   HTTP Client ←──REST API──→ MCP Server
SDK:    直接函数调用 (进程内)

MCP 工具注册

连接后,MCP Server 暴露的工具被包装成标准的 Claude Code Tool:

typescript
// MCP 工具的命名规范: mcp__{serverName}__{toolName}
// 例如: mcp__figma__get_design_context

function wrapMCPTool(
  serverName: string,
  mcpTool: MCPToolDefinition,
  client: MCPClient,
): Tool {
  return buildTool({
    // 名称带服务器前缀,避免冲突
    name: `mcp__${serverName}__${mcpTool.name}`,

    inputSchema: jsonSchemaToZod(mcpTool.inputSchema),

    async description() {
      return mcpTool.description
    },

    async checkPermissions(input) {
      // 检查是否在 autoApprove 列表中
      if (isAutoApproved(serverName, mcpTool.name)) {
        return { behavior: 'allow' }
      }
      return { behavior: 'ask', message: `MCP tool: ${mcpTool.name}` }
    },

    async call(input, context) {
      // 通过 MCP 协议调用工具
      const result = await client.callTool(mcpTool.name, input)
      return { data: result }
    },
  })
}

注册后,MCP 工具和内置工具在 Agent Loop 中是完全一样的 —— 统一的 Tool 接口让这成为可能。

MCP 工具调用流程

LLM 返回 tool_use: { name: "mcp__figma__get_design_context", input: {...} }


findToolByName() → 找到 MCP 包装工具


checkPermissions() → 检查 autoApprove / 询问用户


tool.call()

  ├── 序列化参数为 MCP 格式
  ├── 通过传输层发送到 MCP Server
  ├── 等待 MCP Server 执行
  ├── 接收结果
  └── 反序列化为 ToolResult


返回给 Agent Loop → 追加到 messages

OAuth 认证

某些 MCP 服务器需要 OAuth 认证(如 Figma、GitHub):

typescript
// src/services/mcp/auth.ts - 简化
async function authenticateMCPServer(
  serverName: string,
  config: MCPServerConfig,
): Promise<string> {
  // 1. 检查是否已有有效 token
  const cached = await getStoredToken(serverName)
  if (cached && !isExpired(cached)) {
    return cached.accessToken
  }

  // 2. 发起 OAuth 流程
  const { authUrl, codeVerifier } = buildOAuthRequest(config)

  // 3. 打开浏览器让用户授权
  await openBrowser(authUrl)

  // 4. 等待回调获取 code
  const code = await waitForOAuthCallback()

  // 5. 交换 code 获取 token
  const token = await exchangeCodeForToken(code, codeVerifier)

  // 6. 安全存储 token
  await storeToken(serverName, token)

  return token.accessToken
}

对应的 UI 组件是 src/components/ConsoleOAuthFlow.tsx,在终端中引导用户完成 OAuth 授权。

MCP 资源

除了工具,MCP 还支持资源(可读取的数据源):

typescript
// 列出 MCP 服务器的资源
const ListMcpResourcesTool = buildTool({
  name: 'ListMcpResources',
  async call(input) {
    const resources = await client.listResources()
    return { data: resources }
  },
})

// 读取特定资源
const ReadMcpResourceTool = buildTool({
  name: 'ReadMcpResource',
  async call(input) {
    const content = await client.readResource(input.uri)
    return { data: content }
  },
})

服务器审批

首次连接 MCP 服务器时,用户需要审批:

typescript
// MCP 服务器审批逻辑
async function approveMCPServer(
  serverName: string,
  config: MCPServerConfig,
): Promise<boolean> {
  // 检查是否在允许列表中
  if (isInAllowList(serverName)) return true

  // 显示审批对话框
  const approved = await showApprovalDialog({
    title: `Allow MCP server "${serverName}"?`,
    details: [
      `Command: ${config.command} ${config.args?.join(' ')}`,
      `Tools: ${tools.map(t => t.name).join(', ')}`,
    ],
    options: ['Allow once', 'Always allow', 'Deny'],
  })

  return approved
}

server-reminder 注入

MCP 服务器可以提供使用说明,这些说明通过 system-reminder 注入到对话中:

typescript
// 连接后获取服务器的使用指令
const instructions = await client.getInstructions()

// 注入到消息中
if (instructions) {
  messages.push({
    role: 'user',
    content: `<system-reminder>
# MCP Server Instructions
## ${serverName}
${instructions}
</system-reminder>`,
  })
}

简化实现

typescript
// MCP 客户端的简化实现
class SimpleMCPClient {
  private transport: Transport

  constructor(config: MCPServerConfig) {
    this.transport = config.url
      ? new SSETransport(config.url)
      : new StdioTransport(config.command!, config.args, config.env)
  }

  async initialize() {
    await this.transport.connect()
    // MCP 握手
    await this.transport.send({ method: 'initialize', params: { ... } })
  }

  async listTools(): Promise<MCPToolDef[]> {
    const response = await this.transport.send({ method: 'tools/list' })
    return response.tools
  }

  async callTool(name: string, args: object): Promise<any> {
    const response = await this.transport.send({
      method: 'tools/call',
      params: { name, arguments: args },
    })
    return response.content
  }

  async listResources(): Promise<MCPResource[]> {
    const response = await this.transport.send({ method: 'resources/list' })
    return response.resources
  }
}

关键设计决策

为什么 MCP 而不是直接 REST API?

  1. 标准化 —— 所有 MCP 服务器用同一个协议,Claude Code 不需要为每个服务写适配器
  2. 双向通信 —— 服务器可以推送更新,不只是响应请求
  3. 发现能力 —— 自动发现服务器提供的工具和资源
  4. 安全模型 —— 内置审批和权限机制

为什么工具名带服务器前缀?

避免不同 MCP 服务器的工具名冲突。例如两个服务器都可能有 search 工具,前缀区分了来源。

为什么 stdio 是默认传输?

  1. 简单 —— 不需要网络配置
  2. 安全 —— 不暴露端口
  3. 跨平台 —— stdin/stdout 是通用的

本章小结

维度内置工具MCP 工具
注册方式编译时 tools.ts运行时 MCP 协议发现
命名Bash, Readmcp__server__tool
传输进程内函数调用stdio / SSE / HTTP
认证无需OAuth / API Key
审批权限规则首次连接审批
扩展性需修改源码配置文件声明

MCP 把 Claude Code 从"封闭工具集"变成了"开放平台"。接下来看看如何在工具执行前后注入自定义逻辑 —— S08: Hooks

基于 Claude Code v2.1.88 源码分析