乐于分享
好东西不私藏

Claude Code 源码揭秘:MCP 协议,让 AI 长出无限多的手

Claude Code 源码揭秘:MCP 协议,让 AI 长出无限多的手

💡 阅读前记得关注+星标,及时获取更新推送

「Claude Code 源码揭秘」系列的第十三篇,上一篇《Claude Code 源码揭秘:Opus 干重活 Haiku 打杂,模型调度的精打细算》,说的是 Opus、Sonnet、Haiku 怎么分工协作。但模型再强,能力还是有限的——查数据库、访问 GitHub、操作浏览器,这些事情模型本身做不了。

最近看到一个消息挺有意思,阿里的通义千问 App 可以直接点外卖了。你跟它说「饿了,想吃黄焖鸡」,它真能帮你下单。这背后就是 MCP 协议在起作用——千问通过 MCP 接入了饿了么的能力,AI 就能帮你操作外卖系统了。

这让我想到一个问题。我们现在做的那些企业系统——ERP、CRM、OA、各种业务中台,如果后续不能通过 MCP 协议把接口开放出来给大模型调用,会怎样?

很可能就成了数据孤岛,甚至是数据黑洞。

想想看,用户以后的工作入口可能不再是打开一个个系统的网页,而是直接跟 AI 对话:「帮我查一下上个月华东区的销售数据」「把这个合同走一下审批流程」「给张三请三天年假」。如果你的系统不支持 MCP,AI 就调不动你,用户也就绕过你了。

这不是危言耸听。就像移动互联网时代,那些没做 App、没开放 API 的系统,慢慢就被边缘化了。AI 时代,MCP 可能就是新的「API」。

所以今天这篇,我们不光要搞懂 Claude Code 是怎么实现 MCP 的,更要理解这套协议的设计思路——将来你的系统要接入 AI 生态,这些东西都用得上。


回到正题。你有没有想过,Claude Code 是怎么连接数据库、访问 GitHub、操作浏览器的?

这些能力不是写死在代码里的,而是通过 MCP(Model Context Protocol)协议「接」进来的。MCP 就像是 AI 世界的 USB 接口——只要符合协议规范,任何外部工具都能「插」上来,让 AI 多一种能力。

翻 src/mcp/ 目录,十几个文件,近万行代码。这套实现不是玩具级别的 demo,而是一个完整的、生产级别的协议栈。

我之前做过微服务网关,也设计过类似的协议层。最头疼的就是各种边界情况——超时、重试、断连、版本兼容… Claude Code 这套 MCP 实现把这些都考虑到了,看完之后学到不少东西。

MCP 的三个核心概念

在聊实现之前,先搞清楚三个核心概念。

Server(服务器) —— 提供能力的一方。比如一个 PostgreSQL MCP 服务器,能让 AI 查询数据库。

interface ServerInfo {  name: string;        // 服务器名称  version: string;     // 版本号}interface ServerCapabilities {  tools?: { listChanged?: boolean };       // 支持工具  resources?: { subscribe?: boolean };     // 支持资源  prompts?: { listChanged?: boolean };     // 支持提示词模板}

Tool(工具) —— 服务器暴露的具体功能。比如 PostgreSQL 服务器可能暴露 queryinsertdelete 这些工具。

interface Tool {  name: string;  description?: string;  inputSchema: {    type: 'object';    properties?: Record<string, unknown>;    required?: string[];  };}

每个工具都有名字、描述和输入参数的 JSON Schema。AI 看到这些信息,就知道怎么调用这个工具了。

Resource(资源) —— 服务器管理的数据。比如文件系统服务器的「资源」就是文件,数据库服务器的「资源」就是表。

interface Resource {  uri: string;          // 资源标识,如 file:///path/to/file  name: string;  description?: string;  mimeType?: string;}

资源可以被读取、订阅变更。这让 AI 能感知外部数据的变化。

消息格式:JSON-RPC 2.0

MCP 用的是 JSON-RPC 2.0 协议。三种消息类型:

请求(有 id,期望响应):

{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": { "name": "query", "arguments": { "sql": "SELECT * FROM users" } }}

响应(对应某个请求 id):

{  "jsonrpc": "2.0",  "id": 1,  "result": { "content": [{ "type": "text", "text": "查询结果..." }] }}

通知(没有 id,不期望响应):

{  "jsonrpc": "2.0",  "method": "notifications/resources/list_changed"}

这套消息格式很标准,我之前做 RPC 框架的时候也用过 JSON-RPC。好处是简单、通用,各种语言都有现成的库可以用。

三种传输方式

MCP 支持三种传输类型:

stdio —— 进程通信。启动一个子进程,通过标准输入输出通信。

{  "type": "stdio",  "command": "node",  "args": ["./server.js"],  "env": { "API_KEY": "xxx" }}

这是最常用的方式。MCP 服务器通常是一个独立的程序,Claude Code 通过 spawn 启动它,然后通过 stdin/stdout 交换消息。

SSE —— Server-Sent Events。单向流,服务器主动推送。

{  "type": "sse",  "url": "https://api.example.com/mcp",  "headers": { "Authorization": "Bearer xxx" }}

适合服务器需要主动推送通知的场景。

HTTP —— 传统的 REST API。

{  "type": "http",  "url": "https://api.example.com/mcp",  "headers": { "Authorization": "Bearer xxx" }}

最简单,但每次都是一问一答,不支持服务器主动推送。

我之前做网关的时候,也遇到过类似的选型问题。最后的结论是:本地工具用 stdio,远程服务用 HTTP/SSE。Claude Code 的选择是一样的。

连接生命周期

一个 MCP 连接的完整生命周期:

MCP 连接生命周期

握手阶段(initialize)很重要——双方交换能力信息,确认协议版本兼容。不兼容的话,直接断开,不会出现奇怪的问题。

async initialize(transport, params) {  // 验证协议版本  if (!SUPPORTED_VERSIONS.includes(params.protocolVersion)) {    throw new Error(`Unsupported protocol version: ${params.protocolVersion}`);  }  // 发送初始化请求  const result = await this.sendRequest(transport, 'initialize', params);  return result;}

工具调用的完整流程

假设 AI 想查询数据库,调用 PostgreSQL 服务器的 query 工具:

1. AI 生成工具调用   { tool: "mcp__postgres__query", input: { sql: "SELECT * FROM users" } }2. Claude Code 解析 MCP 工具名   服务器: postgres   工具: query3. 发送 tools/call 请求   { method: "tools/call", params: { name: "query", arguments: { sql: "..." } } }4. 等待响应(30秒超时)   { result: { content: [{ type: "text", text: "..." }] } }5. 返回给 AI

工具名的格式是 mcp__服务器名__工具名。这个前缀让 Claude Code 能区分 MCP 工具和内置工具。

错误处理

MCP 定义了 20 多种错误码:

// JSON-RPC 标准错误PARSE_ERROR = -32700           // 解析错误INVALID_REQUEST = -32600       // 无效请求METHOD_NOT_FOUND = -32601      // 方法不存在INVALID_PARAMS = -32602        // 参数无效INTERNAL_ERROR = -32603        // 内部错误// MCP 自定义错误CONNECTION_FAILED = -1001      // 连接失败CONNECTION_TIMEOUT = -1002     // 连接超时SERVER_NOT_FOUND = -1004       // 服务器未找到TOOL_NOT_FOUND = -1005         // 工具未找到PERMISSION_DENIED = -1007      // 权限拒绝RATE_LIMITED = -1008           // 限流

错误处理器会根据错误类型决定是否重试:

class McpErrorHandler {  maxRetries = 3;  baseDelay = 1000;  shouldRetry(error, attempt) {    // 这些错误可以重试    const retryable = [      CONNECTION_TIMEOUT,      RATE_LIMITED,      INTERNAL_ERROR,    ];    return retryable.includes(error.code) && attempt < this.maxRetries;  }  getRetryDelay(error, attempt) {    // 指数退避 + 随机抖动    let delay = this.baseDelay * Math.pow(2, attempt);    delay += delay * (Math.random() * 0.6 - 0.3);  // ±30% 抖动    return Math.min(delay, 30000);  // 最多 30 秒  }}

随机抖动是个重要的细节。如果所有客户端都在同一时刻重试,可能会把服务器打崩。加点随机性,让重试分散开。

我之前做分布式系统的时候,就因为没加抖动吃过亏。服务恢复的瞬间,所有客户端同时重连,直接把服务又打挂了。后来加了抖动才解决。

超时控制

每个请求都有超时:

async sendRequest(transport, method, params) {  return new Promise((resolve, reject) => {    // 30 秒超时    const timeout = setTimeout(() => {      this.pendingRequests.delete(request.id);      reject(new Error(`Request timed out after 30000ms`));    }, 30000);    this.pendingRequests.set(request.id, { resolve, reject, timeout });    transport.send(request).catch((error) => {      clearTimeout(timeout);      this.pendingRequests.delete(request.id);      reject(error);    });  });}

超时了就直接拒绝,不会无限等待。这是生产环境必须有的防护。

内置 MCP 服务器

Claude Code 知道这些常用的 MCP 服务器:

@modelcontextprotocol/server-filesystem    // 文件系统访问@modelcontextprotocol/server-github        // GitHub API@modelcontextprotocol/server-gitlab        // GitLab API@modelcontextprotocol/server-postgres      // PostgreSQL 数据库@modelcontextprotocol/server-sqlite        // SQLite 数据库@modelcontextprotocol/server-puppeteer     // 浏览器自动化@modelcontextprotocol/server-brave-search  // 搜索引擎@modelcontextprotocol/server-memory        // 长期记忆@modelcontextprotocol/server-fetch         // HTTP 请求

这些是官方或社区维护的 MCP 服务器,可以直接用 npx 启动。

怎么配置 MCP 服务器?

配置文件在 ~/.claude/settings.json(全局)或 .claude/settings.json(项目级):

{  "mcpServers": {    "postgres": {      "type": "stdio",      "command": "npx",      "args": ["@modelcontextprotocol/server-postgres"],      "env": {        "POSTGRES_URL": "postgresql://user:pass@localhost/db"      },      "enabled":true,      "timeout": 30000,      "retries": 3    },    "github": {      "type": "stdio",      "command": "npx",      "args": ["@modelcontextprotocol/server-github"],      "env": {        "GITHUB_TOKEN": "ghp_xxx"      }    }  }}

配置完之后,Claude Code 启动时会自动连接这些服务器,AI 就能用它们提供的工具了。

怎么写自己的 MCP 服务器?

如果你想让 AI 访问你自己的系统,可以写一个 MCP 服务器。最小化实现:

// server.jsconst readline = require('readline');const rl = readline.createInterface({  input: process.stdin,  output: process.stdout,  terminal: false});rl.on('line', async (line) => {  const message = JSON.parse(line);  const { id, method, params } = message;  let result;  switch (method) {    case 'initialize':      result = {        protocolVersion: '2024-11-05',        capabilities: { tools: {} },        serverInfo: { name: 'my-server', version: '1.0.0' }      };      break;    case 'tools/list':      result = {        tools: [{          name: 'hello',          description: '打个招呼',          inputSchema: {            type: 'object',            properties: { name: { type: 'string' } },            required: ['name']          }        }]      };      break;    case 'tools/call':      if (params.name === 'hello') {        result = {          content: [{ type: 'text', text: `Hello, ${params.arguments.name}!` }]        };      }      break;  }  if (result) {    console.log(JSON.stringify({ jsonrpc: '2.0', id, result }));  }});

就这么简单。监听 stdin,解析 JSON-RPC 消息,返回结果。

然后配置到 Claude Code:

{  "mcpServers": {    "my-server": {      "type": "stdio",      "command": "node",      "args": ["./server.js"]    }  }}

AI 就能调用 mcp__my-server__hello 工具了。

采样协议(Sampling)

这是个有意思的功能——让 MCP 服务器也能调用 AI。

正常情况下是 AI 调用工具。但有些场景下,工具本身也需要 AI 的能力。比如一个代码分析工具,想让 AI 帮它总结分析结果。

interface CreateMessageParams {  messages: SamplingMessageContent[];  systemPrompt?: string;  maxTokens: number;  temperature?: number;  modelPreferences?: {    costPriority?: number;         // 0-1,越高越不在乎成本    speedPriority?: number;        // 0-1,越高越不在乎速度    intelligencePriority?: number; // 0-1,越高越需要高智能  };}

MCP 服务器可以通过 sampling/createMessage 方法请求 AI 生成内容。modelPreferences 让服务器可以表达对模型的偏好——要便宜的还是要聪明的。

这是个很前沿的设计,让 AI 和工具之间形成双向的协作关系。

整个 MCP 系统的架构

MCP 系统架构

每一层都有清晰的职责:连接管理、协议处理、传输层、具体服务器。这种分层设计让系统易于扩展和维护。

我之前做微服务网关的时候,也是类似的分层思路。协议层处理消息格式,传输层处理网络通信,业务层处理具体逻辑。分清楚了,改一层不会影响其他层。

为什么 MCP 重要?

MCP 的意义在于标准化

以前让 AI 访问外部系统,每个系统都要单独写接口,没有统一的规范。MCP 提供了一个标准的协议,任何系统只要实现这个协议,就能被任何支持 MCP 的 AI 使用。

这就像 USB 之于外设。有了 USB,任何设备都能「即插即用」。有了 MCP,任何工具都能「即接即用」。

Claude Code 的 MCP 实现是目前最完整的参考实现之一。如果你想了解 MCP 协议的细节,翻这套代码是个很好的学习路径。

下一篇聊 CLAUDE.md 规则系统。这个文件是怎么被解析的?支持哪些指令?怎么用它来定制 AI 的行为?

本文基于 Claude Code 2.0.76 版本源码分析,主要文件:src/mcp/protocol.tssrc/mcp/connection.tssrc/mcp/errors.tssrc/mcp/tools.ts

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Claude Code 源码揭秘:MCP 协议,让 AI 长出无限多的手

评论 抢沙发

2 + 9 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮