MCP 工具族(上)

📑 目录

在 AI Agent 工具生态的演进史中,2024 年 11 月 Anthropic 发布的 Model Context Protocol(MCP) 是一个标志性事件。它试图回答一个根本问题:当大语言模型需要与外部世界交互时,能否存在一种开放、标准、去中心化的协议,让任何工具提供商都能被任何 AI 应用调用?

Claude Code 作为 Anthropic 官方的终端原生 Agent,是 MCP 协议最完整的客户端实现之一。它不仅消费外部 MCP Server 的工具和资源,还能通过 claude --mcp-server 命令自身作为 MCP Server 运行,向其他 Agent 暴露能力。本篇文章将深入解析 Claude Code 中 MCP 工具族的实现架构,重点聚焦 MCPToolMcpAuthTool 以及 MCPServerApprovalDialog 的设计哲学与代码细节。


一、MCP 协议简介

1.1 Model Context Protocol 是什么

MCP 是一个基于 JSON-RPC 2.0 的开放协议,定义了 AI 应用(Host)与外部工具/资源(Server)之间的标准通信方式。从协议分层来看,MCP 由两个概念层组成:

  • 数据层(Data Layer):定义核心 JSON-RPC 通信协议,包括消息格式、生命周期管理、能力协商,以及工具(Tools)、资源(Resources)、提示(Prompts)等原语的语义。
  • 传输层(Transport Layer):抽象底层通信机制,支持标准输入输出(Stdio)用于本地进程通信,以及基于 HTTP + Server-Sent Events 的流式传输用于远程服务。

MCP 的架构遵循客户端-主机-服务器三元模型:

flowchart LR
    subgraph Host["MCP Host(如 Claude Code)"]
        LLM["LLM 编排器"]
        Client1["MCP Client A"]
        Client2["MCP Client B"]
    end
    Server1["MCP Server A
(GitHub / Postgres)"] Server2["MCP Server B
(文件系统 / 搜索)"] LLM --> Client1 LLM --> Client2 Client1 <-->|"JSON-RPC 2.0"| Server1 Client2 <-->|"JSON-RPC 2.0"| Server2
  • Host:宿主应用,负责管理用户界面、LLM 编排和安全策略。一个 Host 可以协调多个 Client。
  • Client:嵌入在 Host 内部的轻量级协议组件,与特定的 MCP Server 保持一对一的有状态会话,负责能力协商和消息路由。
  • Server:独立的进程,以标准化方式暴露特定能力(工具、资源、提示),可以本地或远程运行。

这种设计的精妙之处在于解耦:AI 模型不需要知道工具的具体实现细节,只需理解工具的名称、描述和 JSON Schema 参数规范;工具提供商也不需要为每个 AI 平台定制集成,一次实现,处处可用。

1.2 为什么 Anthropic 要推 MCP

在 MCP 出现之前,AI 工具的集成方式是高度碎片化的。GitHub Copilot 有自己的扩展 API,Cursor 有自己的插件系统,Claude Code 有自己的工具定义格式。这意味着为一个 AI 平台开发的工具集成,无法直接迁移到另一个平台。

Anthropic 推动 MCP 的动机可以从三个维度理解:

第一,生态扩张。MCP 将 Claude Code 的能力边界从"内置的 40 多个工具"扩展到了"整个互联网上的服务"。通过连接 GitHub MCP Server,Claude 可以读写 Issues 和 PR;通过连接 PostgreSQL MCP Server,Claude 可以直接查询数据库。这种可组合性让 Claude Code 从一个代码助手进化为通用 Agent 平台。

第二,标准化安全边界。MCP Server 运行在独立进程中,与 AI 模型的主进程隔离。每个 Server 的权限由用户显式配置,不由 AI 模型自行决定。这种架构天然支持最小权限原则(Principle of Least Privilege)。

第三,商业模式铺垫。MCP 本质上是 AI 时代的"应用商店协议"。Anthropic 通过开放协议吸引开发者构建 MCP Server 生态,类似于苹果通过 App Store 吸引 iOS 开发者。不同的是,MCP 是开放协议,任何 AI 应用都可以实现 MCP Client。

1.3 MCP 与 LSP 的对比

MCP 的设计明显受到了 Language Server Protocol(LSP) 的启发。LSP 由微软在 2016 年推出,解决了 IDE 与语言分析工具之间的碎片化集成问题——在此之前,每个 IDE 都需要为每种编程语言单独实现语法高亮、跳转定义、错误检查等功能。

维度LSPMCP
解决的问题IDE 与语言服务器的碎片化集成AI 应用与外部工具的碎片化集成
通信协议JSON-RPC 2.0JSON-RPC 2.0
传输方式Stdio / Socket / PipeStdio / HTTP+SSE
核心原语Completion、Definition、DiagnosticTool、Resource、Prompt
状态模型无状态请求-响应有状态会话(生命周期管理)
能力协商静态声明(initialize)动态协商(initialize + 运行时更新)
认证机制通常无内置 OAuth 支持

两者的核心区别在于状态性能力动态性。LSP 的连接通常是相对静态的——语言服务器启动后,能力集基本不变。而 MCP 支持运行时动态发现:Server 可以在连接建立后新增或移除工具,Client 需要持续监听这些变化。此外,MCP 内置的认证机制(特别是 OAuth 2.0 集成)使其更适合跨网络的分布式部署场景。


二、MCPTool 架构

2.1 文件位置与设计定位

在 Claude Code 的源码树中,MCP 相关的工具主要分布在以下位置:

src/
├── tools/
│   ├── MCPTool/              # MCP 工具调用入口
│   │   └── MCPTool.tsx
│   ├── McpAuthTool/          # MCP 认证处理
│   │   └── McpAuthTool.tsx
│   ├── ListMcpResourcesTool/ # 列出 MCP 资源
│   └── ReadMcpResourceTool/  # 读取 MCP 资源
├── services/mcp/             # MCP 客户端核心服务
│   ├── MCPClient.ts          # MCP 客户端实现
│   └── ...
├── components/
│   └── MCPServerApprovalDialog.tsx  # 安全审批对话框
└── entrypoints/mcp.ts        # MCP Server 模式入口

MCPTool 是 Claude Code 调用外部 MCP Server 工具的通用入口。当用户通过自然语言请求执行某个外部能力(如"帮我创建 GitHub Issue")时,Claude 的 LLM 编排器会判断是否需要调用 MCP 工具,然后通过 MCPTool 发起实际的 JSON-RPC 调用。

2.2 动态工具注册机制

MCP Server 提供的工具不会静态编译进 Claude Code,而是在运行时动态注册。这一机制的核心在于 wrapMCPTool 函数(逻辑位于 src/services/mcp/ 相关的工具发现流程中),它将 MCP 协议层的工具描述转换为 Claude Code 内部工具系统的标准格式:

// src/services/mcp/ 工具包装逻辑(概念还原)
function wrapMCPTool(mcpTool: MCPTool, server: MCPServerConnection): Tool {
  return {
    // 命名空间隔离:mcp__<服务器名>__<工具名>
    name: `mcp__${server.name}__${mcpTool.name}`,
    description: mcpTool.description,
    inputSchema: mcpTool.inputSchema,

    async execute(input, context) {
      // 通过 MCP 协议调用远端工具
      const result = await server.callTool(mcpTool.name, input)
      return { type: 'tool_result', content: result }
    }
  }
}

命名空间设计是这一机制的关键安全特性。通过 mcp__<server>__<tool> 的前缀约定,Claude Code 确保了不同 MCP Server 之间的工具名不会冲突。例如,两个不同的 Server 都可能提供名为 search 的工具,但在 Claude Code 内部它们会被映射为 mcp__ brave__searchmcp__ tavily__search

工具注册的完整数据流如下:

sequenceDiagram
    participant U as 用户
    participant H as Claude Code Host
    participant C as MCP Client
    participant S as MCP Server

    U->>H: 启动并配置 MCP Server
    H->>C: 建立连接(initialize 握手)
    C->>S: initialize 请求(协议版本 + 能力)
    S-->>C: initialize 响应(服务器能力)
    C->>S: notifications/initialized
    C->>S: tools/list 请求
    S-->>C: 工具列表(name/description/schema)
    C->>H: 注册工具:wrapMCPTool()
    H->>H: 将工具注入 LLM 上下文
    U->>H: "帮我查天气"
    H->>H: LLM 选择 mcp__weather__get_weather
    H->>C: 调用 MCPTool.execute()
    C->>S: tools/call 请求
    S-->>C: 执行结果
    C-->>H: tool_result
    H-->>U: 返回结果

2.3 参数映射

MCP 工具的参数遵循 JSON Schema 规范定义。当 Claude Code 的 LLM 决定调用某个 MCP 工具时,它会根据 inputSchema 生成符合规范的 JSON 对象。MCPTool 的核心职责之一就是确保参数正确传递:

// MCPTool 执行流程中的参数处理
async execute(input: Record<string, unknown>, context: ToolContext) {
  const { server_name, tool_name, ...toolArgs } = input

  // 1. 查找对应的 MCP Client 连接
  const server = context.mcp.clients.find(c => c.name === server_name)
  if (!server) {
    throw new Error(`MCP Server "${server_name}" not found`)
  }

  // 2. 验证工具存在
  const tool = server.tools.find(t => t.name === tool_name)
  if (!tool) {
    throw new Error(`Tool "${tool_name}" not found on server "${server_name}"`)
  }

  // 3. 调用 MCP 协议层(JSON-RPC)
  const result = await server.callTool(tool_name, toolArgs)

  // 4. 返回标准化结果
  return {
    type: 'tool_result',
    content: result.content
  }
}

参数映射中有几个值得注意的设计细节:

Schema 透传:Claude Code 不会修改或重新解释 MCP Server 提供的 inputSchema,而是直接将其透传给 LLM。这保证了 LLM 看到的参数定义与 Server 的期望完全一致。

结果标准化:MCP 协议允许工具返回多种内容类型(text、image、resource 等)。MCPTool 会将这些结果统一转换为 Claude Code 内部的 tool_result 格式,供后续处理流水线消费。

错误码映射:当 MCP Server 返回错误时,特定的错误码会触发不同的处理逻辑。例如错误码 -32042 表示"需要认证",这会触发 McpAuthTool 的认证流程。


三、McpAuthTool:认证与授权

3.1 认证流程概览

MCP 协议设计了完整的认证机制,支持从简单的 API Key 到完整的 OAuth 2.0 流程。当 MCPTool 调用某个需要认证但尚未获取有效凭据的 Server 时,Server 会返回特定的错误响应,Claude Code 随后激活 McpAuthTool 处理认证。

McpAuthTool 的核心执行逻辑涉及对应用状态的精细操作。从源码分析可以看到,它在处理认证结果时会直接操作嵌套的应用状态结构:

// src/tools/McpAuthTool/McpAuthTool.tsx(逻辑还原)
// 注意:该工具深度耦合 AppState 的 mcp 字段结构
McpAuthTool.execute = async (input, context) => {
  const { server_name, auth_url } = input

  // 触发 OAuth 流程或 Token 刷新
  const result = await performAuthFlow(auth_url)

  // 更新全局状态中的 MCP 工具列表
  setAppState(prev => ({
    ...prev,
    mcp: {
      ...prev.mcp,
      clients: prev.mcp.clients.map(client =>
        client.name === server_name
          ? { ...client, authenticated: true }
          : client
      ),
      // 重新注册该 Server 的工具(认证后可能暴露更多工具)
      tools: [
        ...prev.mcp.tools.filter(t => t.serverName !== server_name),
        ...result.tools
      ]
    }
  }))
}

上述代码揭示了一个重要的架构细节:McpAuthTool 不仅处理认证握手,还负责认证后工具集的动态刷新。某些 MCP Server 在认证前只暴露有限的能力(如只读查询),认证后才开放完整能力(如写入操作)。因此,认证成功后需要重新执行 tools/list 并更新注册表。

3.2 OAuth 集成

对于需要 OAuth 的 MCP Server,Claude Code 实现了标准的三方 OAuth 2.0 授权码流程:

  1. 授权请求:Claude Code 检测到需要认证时,向用户展示授权链接(或通过内置浏览器打开)。
  2. 用户授权:用户在浏览器中登录并授权 Claude Code 访问其资源。
  3. 回调处理:授权服务器将授权码回调到 Claude Code 的本地回调端点(通常是 http://localhost:某个端口)。
  4. Token 交换:Claude Code 使用授权码向 Token 端点交换 Access Token 和 Refresh Token。
  5. 状态更新:Token 存储到安全存储区(如系统钥匙串),并更新 MCP Client 的认证状态。
// OAuth 流程中的 Token 管理(概念还原)
async function exchangeCodeForToken(
  serverConfig: MCPServerConfig,
  authCode: string
): Promise<AuthTokens> {
  const response = await fetch(serverConfig.tokenEndpoint, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: serverConfig.clientId,
      code: authCode,
      redirect_uri: 'http://localhost:8080/callback'
    })
  })

  const tokens = await response.json()

  // 安全存储 Token
  await secureStorage.set(`mcp_token_${serverConfig.name}`, {
    accessToken: tokens.access_token,
    refreshToken: tokens.refresh_token,
    expiresAt: Date.now() + tokens.expires_in * 1000
  })

  return tokens
}

3.3 Token 生命周期管理

McpAuthTool 还需要处理 Token 的自动刷新机制。当 Access Token 即将过期时,Claude Code 会在后台自动使用 Refresh Token 获取新的 Access Token,避免在工具调用过程中因 Token 过期而中断用户体验。

Token 管理的另一个关键点是作用域隔离。Claude Code 为每个 MCP Server 维护独立的 Token 存储命名空间,确保 Server A 的 Token 不会被误发给 Server B。这种隔离在 src/services/mcp/ 的 Client 管理模块中通过 server.name 作为存储键的前缀来实现。


四、MCP Server 管理

4.1 services/mcp/ 目录架构

src/services/mcp/ 是 Claude Code MCP 功能的核心实现目录,负责客户端连接管理、能力协商和生命周期维护。该目录通常包含以下关键模块:

  • MCPClient.ts:单个 MCP Server 连接的客户端实现,封装 JSON-RPC 通信细节。
  • 连接管理器:维护多个 MCPClient 实例,处理连接的建立、断开和重连。
  • 能力协商器:处理 initialize 握手和运行时能力更新。

4.2 Server 生命周期

MCP 连接的完整生命周期包含四个阶段:初始化(Initialization)、发现(Discovery)、运行(Operation)、终止(Termination)。

初始化阶段:Client 向 Server 发送 initialize 请求,携带协议版本(如 2025-06-18)、Client 能力(是否支持 sampling、elicitation 等)和 Client 元数据(名称、版本)。Server 响应其支持的协议版本和能力集。若版本不兼容,Client 应立即断开连接。

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "sampling": {},
      "roots": { "listChanged": true }
    },
    "clientInfo": {
      "name": "claude-code",
      "version": "1.0.0"
    }
  }
}

Server 响应示例:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": { "subscribe": true },
      "prompts": {}
    },
    "serverInfo": {
      "name": "github-mcp-server",
      "version": "2.1.0"
    }
  }
}

初始化成功后,Client 必须发送 notifications/initialized 通知,此后连接进入活跃状态。

发现阶段:Client 发送 tools/listresources/listprompts/list 等请求,获取 Server 暴露的完整能力清单。对于支持动态更新的 Server(listChanged: true),Client 还需订阅变更通知。

运行阶段:Client 和 Server 按照协商后的能力集交换请求和响应。此阶段支持双向通信——不仅 Client 可以调用 Server 的工具,Server 也可以通过 sampling/createMessage 请求让 Host LLM 生成文本,或通过 roots/list 获取项目根目录信息。

终止阶段:Client 或 Server 任何一方关闭底层传输连接即可终止会话。Claude Code 在退出时会优雅地关闭所有 MCP 连接,释放子进程资源。

4.3 连接健康监控

Claude Code 使用 useMcpConnectivityStatus Hook(位于 React 组件层)实时监控 MCP 连接的健康状态。当某个 MCP Server 进程意外退出或网络连接中断时,该 Hook 会触发状态更新,UI 层可以据此向用户展示连接断开提示,并尝试自动重连。

重连策略通常采用指数退避(Exponential Backoff):首次断开立即尝试重连,若失败则等待 1 秒、2 秒、4 秒……最多到某个上限。对于 Stdio 类型的本地 Server,重连意味着重新启动子进程;对于 HTTP 类型的远程 Server,重连则是重新建立 HTTP 长连接。

4.4 能力协商的动态性

MCP 的能力协商不仅发生在初始化阶段,还支持运行时动态更新。当 Server 的能力发生变化时(例如用户通过 Web 界面为 Server 启用了新功能),Server 可以向 Client 发送 notifications/tools/list_changed 通知。Client 收到通知后,重新调用 tools/list 获取最新工具列表,并更新内部注册表。

这种动态性对 Claude Code 的架构提出了特殊要求:LLM 的工具选择上下文必须能够被热更新。当新工具注册或旧工具注销时,Claude Code 需要将这些变化同步到当前对话的 LLM 上下文中,使 LLM 在下一次请求时知晓最新的可用工具集。


五、安全审批机制

5.1 为什么需要审批

MCP 协议的设计理念之一是用户拥有最终控制权。当 Claude Code 首次检测到项目目录中存在 .mcp.json 配置文件(或用户通过 /mcp 命令新增 Server)时,它不会立即自动连接并启用该 Server,而是向用户展示审批对话框,请求显式授权。

这一机制至关重要,因为:

  1. 供应链安全.mcp.json 可能随代码仓库分发给所有团队成员。如果仓库被植入恶意的 MCP Server 配置,自动启用将导致所有团队成员暴露于风险中。
  2. 权限透明:用户需要明确知道某个 MCP Server 被启用后,Claude Code 将获得哪些额外能力(如访问生产数据库、操作 GitHub 仓库等)。
  3. 合规要求:企业部署中,安全策略通常要求所有外部工具集成必须经过审批流程。

5.2 MCPServerApprovalDialog 实现解析

MCPServerApprovalDialog.tsx 是 Claude Code 中处理 MCP Server 审批的 React 组件。以下是从源码中提取的核心逻辑(经过 React Compiler 优化后的代码,保留了原始语义):

// src/components/MCPServerApprovalDialog.tsx(关键逻辑)
import React from 'react'
import { logEvent } from 'src/services/analytics/index.js'
import { getSettings_DEPRECATED, updateSettingsForSource } from '../utils/settings/settings.js'
import { Dialog } from './design-system/Dialog.js'

type Props = {
  serverName: string
  onDone(): void
}

export function MCPServerApprovalDialog({ serverName, onDone }: Props) {
  const onChange = (value: 'yes' | 'yes_all' | 'no') => {
    // 记录分析事件
    logEvent('tengu_mcp_dialog_choice', { choice: value })

    switch (value) {
      case 'yes':
      case 'yes_all': {
        // 将当前 Server 加入启用列表
        const currentSettings = getSettings_DEPRECATED() || {}
        const enabledServers = currentSettings.enabledMcpjsonServers || []
        if (!enabledServers.includes(serverName)) {
          updateSettingsForSource('localSettings', {
            enabledMcpjsonServers: [...enabledServers, serverName]
          })
        }
        // "全部允许"模式:自动启用该项目未来发现的所有 MCP Server
        if (value === 'yes_all') {
          updateSettingsForSource('localSettings', {
            enableAllProjectMcpServers: true
          })
        }
        onDone()
        break
      }
      case 'no': {
        // 将当前 Server 加入禁用列表
        const currentSettings = getSettings_DEPRECATED() || {}
        const disabledServers = currentSettings.disabledMcpjsonServers || []
        if (!disabledServers.includes(serverName)) {
          updateSettingsForSource('localSettings', {
            disabledMcpjsonServers: [...disabledServers, serverName]
          })
        }
        onDone()
      }
    }
  }

  return (
    <Dialog
      title={`New MCP server found in .mcp.json: ${serverName}`}
      onDismiss={() => onChange('no')}
    >
      {/* 三个选项的 Select 组件 */}
      <Select
        options={[
          {
            label: 'Use this and all future MCP servers in this project',
            value: 'yes_all'
          },
          {
            label: 'Use this MCP server',
            value: 'yes'
          },
          {
            label: 'Continue without using this MCP server',
            value: 'no'
          }
        ]}
        onChange={onChange}
      />
    </Dialog>
  )
}

5.3 用户确认机制的设计

审批对话框提供了三个粒度级别的授权选项,这是安全 UX 设计的精妙之处:

  • "Use this and all future MCP servers in this project"(yes_all):适合高度信任的项目环境。设置 enableAllProjectMcpServers: true 后,该项目中所有未来发现的 MCP Server 都将自动启用,无需逐一审批。
  • "Use this MCP server"(yes):仅启用当前这一个 Server。这是最安全的默认选择,用户需要对每个 Server 单独审批。
  • "Continue without using this MCP server"(no):明确拒绝。Server 被加入 disabledMcpjsonServers 列表,后续不再提示。

这些选择被持久化存储在用户设置中(通过 updateSettingsForSource('localSettings', ...)),确保审批决策跨会话生效。

5.4 权限范围控制

除了审批对话框的显式授权外,Claude Code 还通过多层权限控制来限制 MCP Server 的运行时行为:

进程隔离:Stdio 类型的 MCP Server 作为独立子进程运行,与 Claude Code 主进程隔离。即使 Server 进程崩溃或被攻击,也不会直接影响主应用。

环境变量沙箱:每个 MCP Server 的环境变量独立配置。Server A 的 GITHUB_TOKEN 不会泄露给 Server B。

工具命名空间隔离:前文提到的 mcp__<server>__<tool> 命名空间不仅防止名称冲突,也防止了跨 Server 的权限混淆。LLM 无法通过工具名称猜测来绕过命名空间限制调用未授权 Server 的工具。

运行时 Hook 系统:Claude Code 的 src/services/tools/toolHooks.ts 提供了 PreToolUsePostToolUse Hook,允许企业部署在工具执行前后注入自定义安全检查。例如,可以在 PreToolUse Hook 中检查目标 MCP Server 是否在白名单中,或记录所有 MCP 工具调用的审计日志。


六、小结与展望

MCP 协议正在快速重塑 AI Agent 的工具集成范式。Claude Code 作为 MCP 最成熟的客户端实现之一,其代码为我们展示了一个生产级 MCP 集成应有的架构考量:

  • 动态注册:MCP 工具不是静态编译的,而是在运行时根据 Server 的能力协商结果动态注入工具系统。
  • 命名空间隔离:通过 mcp__<server>__<tool> 的命名约定,解决了多 Server 环境下的名称冲突和权限隔离问题。
  • 认证集成:完整的 OAuth 2.0 支持,包括授权码流程、Token 刷新和认证后能力重新发现。
  • 安全审批:三层授权粒度(全部允许 / 仅当前 / 拒绝),结合进程隔离和 Hook 系统,构建了纵深防御体系。
  • 生命周期管理:严格的初始化握手、能力协商、健康监控和优雅终止,确保连接的可靠性。

在下一篇文章中,我们将继续深入 MCP 工具族,重点分析 ListMcpResourcesToolReadMcpResourceTool 的资源访问机制,以及 ToolSearchTool 的延迟发现能力,探讨 Claude Code 如何在 MCP Server 模式下向其他 Agent 暴露自身工具。


参考与延伸阅读