Skip to content

A06: MCP 协议详解 ​

相关章节: 第 7 章 MCP -- 连接 Claude 与一切、第 13 章 Agent SDK

模型上下文协议(Model Context Protocol, MCP)是一个开放标准,定义了 AI Agent 如何与外部工具、数据源和服务通信。第 7 章涵盖了 MCP 在 Claude Code 中的实际使用。本附录更深入地介绍协议本身:规范、传输机制、消息生命周期,以及在构建或评估 MCP 服务器时需要理解的设计决策。

MCP 规范概述 ​

目的与设计目标 ​

MCP 的创建是为了解决碎片化问题。在 MCP 之前,每个 AI 工具集成都是定制的:每个 IDE 插件、CLI 工具或 Agent 框架都定义了自己与外部服务连接的方式。MCP 提供了 AI 客户端与工具提供者之间的通用接口,类似于 USB 标准化了外设连接。

核心设计目标:

  1. 简洁 --- 基于 JSON-RPC 2.0,使用广泛理解的传输协议
  2. 可组合 --- 多个服务器可以同时连接
  3. 可发现 --- 服务器在启动时声明其功能
  4. 安全 --- 客户端控制 AI 模型能看到什么和做什么

架构 ​

MCP 遵循客户端-服务器架构,分为三层:

┌─────────────────────────────────────────┐
│           AI Application                │
│  (Claude Code, IDE plugin, Agent SDK)   │
│                                         │
│  ┌───────────────────────────────────┐  │
│  │         MCP Client                │  │
│  │  - Manages server connections     │  │
│  │  - Routes tool calls             │  │
│  │  - Enforces security policies    │  │
│  └──────────┬────────────────────────┘  │
└─────────────┼───────────────────────────┘
              │  JSON-RPC 2.0
              │  (stdio / SSE / streamable HTTP)
┌─────────────┼───────────────────────────┐
│  ┌──────────┴────────────────────────┐  │
│  │         MCP Server                │  │
│  │  - Declares tools, resources,    │  │
│  │    prompts at startup            │  │
│  │  - Executes tool calls           │  │
│  │  - Returns structured results    │  │
│  └───────────────────────────────────┘  │
│           Tool Provider                 │
│  (Database, API, filesystem, etc.)      │
└─────────────────────────────────────────┘

一个客户端可以同时连接多个服务器。Claude Code 日常就是这样做的 --- 你可能在一个会话中同时启用数据库服务器、文档服务器和部署服务器。

消息格式(JSON-RPC 2.0) ​

所有 MCP 通信使用 JSON-RPC 2.0。有三种消息类型:

请求(客户端到服务器,或服务器到客户端):

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "query_database",
    "arguments": {
      "sql": "SELECT count(*) FROM users WHERE active = true"
    }
  }
}

响应(对请求的回复):

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Query returned: 4,271 active users"
      }
    ]
  }
}

通知(单向,不期望回复):

json
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "abc123",
    "progress": 75,
    "total": 100
  }
}

id 字段区分请求(期望回复)和通知(不期望回复)。

传输类型 ​

MCP 定义了三种传输机制。选择取决于你的部署模型。

stdio(标准 I/O) ​

客户端将服务器作为子进程启动,通过 stdin/stdout 通信。每条 JSON-RPC 消息是以 \n 结尾的一行。

Client                          Server (child process)
  │                                │
  │──── spawn process ────────────>│
  │                                │
  │── {"jsonrpc":"2.0",...}\n ──-->│  (stdin)
  │<── {"jsonrpc":"2.0",...}\n ────│  (stdout)
  │                                │
  │──── close stdin ──────────────>│  (shutdown signal)

适用场景:

  • 本地开发工具
  • 单用户场景
  • Claude Code 本地 MCP 服务器的默认模式
  • 服务器需要访问本地文件系统时

优势:无需网络设置、无端口、无需认证。操作系统进程模型提供自然隔离。

限制:无法服务多个客户端。客户端退出时服务器也会退出。

Claude Code 配置(.claude/settings.json):

json
{
  "mcpServers": {
    "my-db-tool": {
      "command": "node",
      "args": ["./mcp-servers/db-tool/index.js"],
      "env": {
        "DATABASE_URL": "postgresql://localhost:5432/mydb"
      }
    }
  }
}

SSE(Server-Sent Events) ​

客户端通过 HTTP 连接服务器。服务器到客户端的消息通过 SSE 流传递;客户端到服务器的消息作为 HTTP POST 请求发送。

适用场景:

  • 多客户端访问的共享服务器
  • 负载均衡器后的远程服务器
  • 服务器生命周期独立于客户端的场景

优势:工作在标准 HTTP 基础设施上。支持多个并发客户端。服务器可以比任何单个客户端存活更久。

限制:需要 HTTP 基础设施。设置和安全配置更复杂。在较新的实现中正被 streamable HTTP 取代。

Streamable HTTP ​

最新的传输方式,设计为结合 stdio 的简洁性和 HTTP 的灵活性。每个请求-响应对是一次 HTTP 往返,对于长时间运行的操作可选通过 SSE 流式传输。

Client                          Server
  │                                │
  │── POST /mcp (JSON-RPC) ──────>│
  │<── 200 OK (JSON-RPC) ─────────│  (simple case)
  │                                │
  │── POST /mcp (JSON-RPC) ──────>│
  │<── 200 OK (SSE stream) ───────│  (streaming case)
  │    data: {"jsonrpc":"2.0",...} │
  │    data: {"jsonrpc":"2.0",...} │
  │                                │

适用场景:

  • 新的服务器实现(这是推荐的未来传输方式)
  • 需要简洁性和远程访问兼顾时
  • 生产部署

Claude Code 配置:

json
{
  "mcpServers": {
    "remote-tool": {
      "type": "url",
      "url": "https://mcp.example.com/my-tool"
    }
  }
}

服务器生命周期管理 ​

启动与能力协商 ​

当客户端连接到服务器时,第一次交换是 initialize 握手:

Client ──> initialize request (protocol version, client capabilities)
Server <── initialize response (protocol version, server capabilities)
Client ──> initialized notification (handshake complete)

服务器的 initialize 响应声明其支持的功能:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": { "subscribe": true, "listChanged": true },
      "prompts": { "listChanged": true }
    },
    "serverInfo": {
      "name": "my-database-server",
      "version": "1.2.0"
    }
  }
}

listChanged 能力告诉客户端服务器可能在会话期间动态添加或移除工具/资源。

健康检查 ​

对于长时间运行的服务器(SSE 和 streamable HTTP),客户端可以发送定期的 ping 请求。如果服务器在超时内未响应,客户端认为连接已断并可能尝试重连。

json
{ "jsonrpc": "2.0", "id": 99, "method": "ping" }

关闭 ​

对于 stdio 服务器,关闭 stdin 信号服务器关闭。对于基于 HTTP 的服务器,客户端简单地停止发送请求。行为良好的服务器应该优雅地处理 SIGTERM,在退出前完成进行中的操作。

资源和提示原语 ​

MCP 定义了三个核心原语。大多数开发者知道工具(Tools),但资源(Resources)和提示(Prompts)同样重要。

工具 ​

工具是 AI 可以调用的可执行函数。它们是最常见的原语。

json
{
  "name": "query_database",
  "description": "Run a read-only SQL query against the application database",
  "inputSchema": {
    "type": "object",
    "properties": {
      "sql": {
        "type": "string",
        "description": "SQL query to execute (SELECT only)"
      }
    },
    "required": ["sql"]
  }
}

资源 ​

资源是 AI 可以读取的数据。与工具不同,资源不执行代码 --- 它们提供内容。可以把它们想象为服务器暴露的文件或文档。

json
{
  "uri": "db://schema/users",
  "name": "Users table schema",
  "mimeType": "application/json",
  "description": "The current schema of the users table"
}

资源支持订阅:客户端可以请求在资源变化时收到通知。这对实时仪表板、日志追踪或监控很有用。

提示 ​

提示是服务器提供的可重用模板。AI(或用户)可以调用它们来获得预结构化的交互。

json
{
  "name": "explain_query",
  "description": "Explain a SQL query execution plan in plain language",
  "arguments": [
    {
      "name": "sql",
      "description": "The SQL query to explain",
      "required": true
    }
  ]
}

提示对于标准化常见交互特别有用。用户不需要每次写"explain this query",服务器提供一个结构化的提示模板产生一致的结果。

构建生产级 MCP 服务器 ​

错误处理 ​

MCP 使用 JSON-RPC 错误码。你的服务器应该返回结构化错误,而不是崩溃:

typescript
// Good: structured error
return {
  error: {
    code: -32602,  // Invalid params
    message: "Table 'nonexistent' does not exist",
    data: {
      availableTables: ["users", "orders", "products"]
    }
  }
};

// Bad: unhandled exception crashes the server

标准 JSON-RPC 错误码:

  • -32700 解析错误
  • -32600 无效请求
  • -32601 方法未找到
  • -32602 无效参数
  • -32603 内部错误

使用自定义码(正数)表示领域特定的错误。

日志记录 ​

MCP 提供 notifications/message 方法让服务器向客户端发送日志消息:

json
{
  "jsonrpc": "2.0",
  "method": "notifications/message",
  "params": {
    "level": "warning",
    "logger": "db-server",
    "data": "Slow query detected: 3.2s for SELECT * FROM orders"
  }
}

日志级别遵循标准:debug、info、notice、warning、error、critical、alert、emergency。

对于 stdio 服务器,绝不要向 stdout 写日志 --- stdout 是协议通道。使用 stderr 或 MCP 通知机制。

速率限制 ​

如果你的 MCP 服务器包装了有速率限制的外部 API,实现排队:

typescript
class RateLimitedServer {
  private queue: Array<() => Promise<void>> = [];
  private processing = false;
  private requestsPerMinute = 60;
  private requestCount = 0;

  async handleToolCall(params: ToolCallParams): Promise<ToolCallResult> {
    if (this.requestCount >= this.requestsPerMinute) {
      return {
        content: [{
          type: "text",
          text: "Rate limit reached. Please retry in 60 seconds."
        }],
        isError: true
      };
    }
    this.requestCount++;
    // ... execute the actual tool call
  }
}

MCP 安全模型 ​

最小权限原则 ​

服务器应暴露所需的最小工具集。用于代码审查的数据库服务器应暴露 query(只读)而不是 execute(写入)。

通过 Claude Code 权限系统沙箱化 ​

Claude Code 将 MCP 工具调用包装在其权限系统中。即使服务器暴露了危险工具,用户也必须批准使用。这提供了一个人类在环(Human-in-the-Loop)的安全层。

在 .claude/settings.json 中,你可以预批准特定 MCP 工具:

json
{
  "permissions": {
    "allow": [
      "mcp__my-db-tool__query_database"
    ],
    "deny": [
      "mcp__my-db-tool__drop_table"
    ]
  }
}

输入验证 ​

每个 MCP 服务器都应严格验证输入。AI 模型可能生成意外的参数:

typescript
function validateSqlQuery(sql: string): void {
  const normalized = sql.trim().toUpperCase();
  if (!normalized.startsWith("SELECT")) {
    throw new McpError(
      -32602,
      "Only SELECT queries are allowed"
    );
  }
  // Check for SQL injection patterns
  const forbidden = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER"];
  for (const keyword of forbidden) {
    if (normalized.includes(keyword)) {
      throw new McpError(
        -32602,
        `Forbidden SQL keyword: ${keyword}`
      );
    }
  }
}

远程服务器的认证 ​

对于暴露在网络上的 streamable HTTP 服务器,使用标准 HTTP 认证。MCP 不定义自己的认证层 --- 它依赖传输层:

json
{
  "mcpServers": {
    "remote-tool": {
      "type": "url",
      "url": "https://mcp.example.com/my-tool",
      "headers": {
        "Authorization": "Bearer ${MCP_API_TOKEN}"
      }
    }
  }
}

示例:最小 MCP 服务器 ​

TypeScript(stdio 传输) ​

typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Create the server
const server = new McpServer({
  name: "word-counter",
  version: "1.0.0",
});

// Register a tool
server.tool(
  "count_words",
  "Count the number of words in a text string",
  {
    text: z.string().describe("The text to count words in"),
  },
  async ({ text }) => {
    const wordCount = text.trim().split(/\s+/).filter(Boolean).length;
    return {
      content: [
        {
          type: "text",
          text: `Word count: ${wordCount}`,
        },
      ],
    };
  }
);

// Register a resource
server.resource(
  "server-info",
  "info://server",
  async (uri) => ({
    contents: [
      {
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify({
          name: "word-counter",
          uptime: process.uptime(),
          nodeVersion: process.version,
        }),
      },
    ],
  })
);

// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);

Python(stdio 传输) ​

python
from mcp.server.fastmcp import FastMCP

# Create the server
mcp = FastMCP("word-counter")

# Register a tool
@mcp.tool()
def count_words(text: str) -> str:
    """Count the number of words in a text string."""
    word_count = len(text.strip().split())
    return f"Word count: {word_count}"

# Register a resource
@mcp.resource("info://server")
def server_info() -> str:
    """Return server status information."""
    import json, time
    return json.dumps({
        "name": "word-counter",
        "timestamp": time.time(),
    })

# Start the server (stdio transport)
if __name__ == "__main__":
    mcp.run(transport="stdio")

Claude Code 配置(两种服务器皆适用):

json
{
  "mcpServers": {
    "word-counter-ts": {
      "command": "npx",
      "args": ["tsx", "./mcp-servers/word-counter/index.ts"]
    },
    "word-counter-py": {
      "command": "python",
      "args": ["./mcp-servers/word-counter/server.py"]
    }
  }
}

MCP 与其他集成方式的对比 ​

方式范围发现机制多客户端协议
MCP通用标准服务器声明功能是(HTTP 传输)JSON-RPC 2.0
OpenAI Function Calling仅限 OpenAI API客户端定义函数不适用(API 级别)OpenAI API
LangChain Tools仅限 LangChain 框架框架注册否Python 对象
自定义 REST API你的应用手动文档是HTTP/REST

MCP 的优势是一个服务器编写一次就可以与任何 MCP 客户端一起工作 --- Claude Code、Cursor、Windsurf、自定义 Agent,以及任何未来实现该协议的客户端。

核心要点 ​

  1. MCP 标准化了 AI 与工具的通信,使用 JSON-RPC 2.0 通过三种传输方式:本地工具用 stdio,共享服务器用 SSE,生产部署用 streamable HTTP。
  2. 启动时的能力协商让服务器准确声明其支持的功能,在功能缺失时实现优雅降级。
  3. 资源和提示将 MCP 扩展到简单工具调用之外 --- 资源提供只读数据,提示提供可重用的交互模板。
  4. 生产服务器需要结构化错误处理、正确的日志记录(stdio 服务器绝不写 stdout)、输入验证和速率限制。
  5. 安全是分层的:服务器验证输入,传输层处理认证,Claude Code 的权限系统提供人类在环审批。

另见:第 7 章 MCP -- 连接 Claude 与一切 了解实际设置,以及 A05 工具调用内部机制 了解 Claude 如何决定调用哪个 MCP 工具。

基于 MIT 许可发布