第 37 章

MCP 协议深度解析

第37章:MCP 协议深度解析

Model Context Protocol(MCP)是 Anthropic 于 2024 年末发布的开放协议标准,旨在解决 AI 系统与外部工具、数据源集成的碎片化问题。在 MCP 出现之前,每个 AI 应用都需要为每种工具写定制的集成代码;MCP 的出现,类似于 HTTP 对 Web 通信的统一——一个协议,连接所有工具。本章深入解析 MCP 的设计原理、架构和完整的消息规范。


37.1 MCP 设计目标与背景

为什么需要 MCP?

MCP 出现之前的碎片化现状:

┌───────────────────────────────────────────────────────┐
│ AI 应用(Claude/GPT/Hermes)                           │
├──────────┬──────────┬──────────┬──────────┬───────────┤
│ 自定义   │ 自定义   │ 自定义   │ 自定义   │ 自定义    │
│ 搜索集成 │ DB集成   │ 文件集成 │ API集成  │ ...       │
└──────────┴──────────┴──────────┴──────────┴───────────┘

MCP 之后(统一接口):

┌───────────────────────────────────────────────────────┐
│ AI 应用(Claude/GPT/Hermes)                           │
└──────────────────────────┬────────────────────────────┘
                           │ MCP 协议(统一)
              ┌────────────┼────────────┐
              ↓            ↓            ↓
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │MCP搜索   │ │MCP数据库 │ │MCP文件   │
        │Server    │ │Server    │ │Server    │
        └──────────┘ └──────────┘ └──────────┘

MCP 的三大设计目标

目标 具体含义 类比
标准化 统一工具调用格式,消除碎片化 HTTP 统一了 Web 请求
安全性 Server 明确声明能力,Client 按需调用 OAuth 的最小权限原则
互操作 任何 MCP Client 可以使用任何 MCP Server USB 接口标准

协议规范版本历史

版本 发布时间 主要变化
0.1.0 2024-11 初始版本,基本 Tool/Resource 支持
0.2.0 2024-12 添加 Prompt 类型,改进错误处理
1.0.0 2025-Q1 稳定版,添加 SSE Transport
1.1.0 2025-Q2 OAuth 认证、批量请求支持

37.2 Client-Server 架构

核心架构

┌─────────────────────────────────────────────────────────┐
│                    MCP 整体架构                           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │              MCP Host(宿主应用)                │    │
│  │     例如:Claude Desktop / Hermes Agent          │    │
│  │                                                  │    │
│  │   ┌─────────────┐     ┌─────────────┐           │    │
│  │   │ MCP Client 1 │     │ MCP Client 2 │           │    │
│  │   │(每个Server  │     │(每个Server  │           │    │
│  │   │  一个实例)  │     │  一个实例)  │           │    │
│  │   └──────┬───────┘     └──────┬───────┘           │    │
│  └──────────┼──────────────────┼────────────────────┘    │
│             │  MCP 协议         │  MCP 协议              │
│             ↓                  ↓                        │
│  ┌───────────────────┐  ┌───────────────────┐           │
│  │   MCP Server 1    │  │   MCP Server 2    │           │
│  │  (数据库工具)    │  │  (文件系统工具)   │           │
│  │                   │  │                   │           │
│  │  Tools:           │  │  Tools:           │           │
│  │  - query_db       │  │  - read_file      │           │
│  │  - write_db       │  │  - write_file     │           │
│  │                   │  │                   │           │
│  │  Resources:       │  │  Resources:       │           │
│  │  - db://tables    │  │  - file:///docs   │           │
│  └───────────────────┘  └───────────────────┘           │
└─────────────────────────────────────────────────────────┘

关键角色定义

MCP Host:
  - 创建和管理多个 MCP Client 实例
  - 协调 LLM 推理和工具调用
  - 管理用户交互界面

MCP Client:
  - 与单个 MCP Server 建立一对一连接
  - 维护协议状态机
  - 转发 Host 的工具调用请求

MCP Server:
  - 提供具体的工具、资源或 Prompt
  - 独立进程(安全隔离)
  - 可以是 stdio、SSE 或 HTTP 传输

37.3 Transport 层:三种传输方式

Transport 1:Stdio(标准输入输出)

最简单的传输方式,Client 启动 Server 作为子进程,通过 stdin/stdout 通信。

Client
  |
  |--- spawn → Server(子进程)
  |
  |--- stdin (JSON-RPC 请求) ──→ Server
  |←── stdout (JSON-RPC 响应) ─── Server
  |
  |   stderr:Server 日志(不参与协议)
# 启动 stdio Server
import subprocess
import json

class StdioTransport:
    def __init__(self, command: list):
        self.process = subprocess.Popen(
            command,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
    
    def send(self, message: dict):
        line = json.dumps(message) + "\n"
        self.process.stdin.write(line.encode())
        self.process.stdin.flush()
    
    def receive(self) -> dict:
        line = self.process.stdout.readline()
        return json.loads(line.decode())

适用场景:本地开发、CLI 工具集成、进程级安全隔离

Transport 2:SSE(Server-Sent Events)

服务端通过 HTTP SSE 流式推送消息,Client 通过 HTTP POST 发送请求。

Client                          Server(HTTP 服务)
  |                                    |
  |-- GET /sse ─────────────────────→  |
  |  (建立 SSE 持久连接)                |
  |←─ text/event-stream ─────────────  |
  |                                    |
  |-- POST /message ─────────────────→ |
  |   {"jsonrpc":"2.0", "method":"..."}|
  |←─ 200 OK ────────────────────────  |
  |                                    |
  |←─ SSE event: {"result":...} ─────  |

适用场景:远程 Server、Web 集成、需要服务端主动推送

Transport 3:Streamable HTTP(MCP 1.1+)

POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","method":"tools/call",...}

# 服务端可以返回普通 JSON 或 SSE 流
HTTP/1.1 200 OK
Content-Type: text/event-stream  # 或 application/json

data: {"jsonrpc":"2.0","result":...}

Transport 对比

特性 Stdio SSE Streamable HTTP
部署难度 简单(本地进程) 中等(需 HTTP 服务) 中等
网络穿透 不支持 支持 支持
实时推送 不支持 支持 支持
状态管理 进程内 Session 无状态
安全隔离 进程级 网络级 网络级
适用场景 本地工具 远程服务 云端 API

37.4 三种能力类型:Tool、Resource、Prompt

MCP Server 可以提供三种类型的能力:

能力类型 1:Tool(工具)

Tool 是可执行的函数,LLM 可以调用它来完成动作(类似函数调用)。

{
  "name": "search_database",
  "description": "在数据库中按关键词搜索记录",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "搜索关键词"
      },
      "table": {
        "type": "string",
        "enum": ["users", "products", "orders"],
        "description": "要搜索的表名"
      },
      "limit": {
        "type": "integer",
        "default": 10,
        "description": "最多返回记录数"
      }
    },
    "required": ["query", "table"]
  }
}

能力类型 2:Resource(资源)

Resource 是 Server 可以提供的数据或内容(只读),LLM 可以将其包含在上下文中。

{
  "uri": "db://customers/recent",
  "name": "最近的客户列表",
  "description": "过去 30 天新增的客户记录",
  "mimeType": "application/json"
}

Resource URI 支持模板语法:

{
  "uriTemplate": "file:///documents/{category}/{filename}",
  "name": "文档资源",
  "description": "按类别和文件名访问文档"
}

能力类型 3:Prompt(提示模板)

Prompt 是 Server 提供的预定义提示词模板,帮助 LLM 以最优方式使用该 Server 的功能。

{
  "name": "analyze_sales_data",
  "description": "分析销售数据并生成洞察报告的最优提示",
  "arguments": [
    {
      "name": "time_period",
      "description": "分析时间段(如:Q4 2024)",
      "required": true
    },
    {
      "name": "focus_area",
      "description": "重点分析领域(如:地区、产品线)",
      "required": false
    }
  ]
}

三种能力对比

能力 语义 LLM 作用 是否执行动作
Tool 可执行函数 决定何时/如何调用 是(有副作用)
Resource 只读数据 将内容加入上下文 否(纯读取)
Prompt 提示模板 获取优化的提示词 否(仅文本)

37.5 消息格式完整规范(JSON-RPC 2.0)

MCP 基于 JSON-RPC 2.0 协议,所有消息都是 JSON 对象。

JSON-RPC 2.0 基本结构

// 请求(Request)
{
  "jsonrpc": "2.0",
  "id": 1,              // 请求 ID(用于匹配响应)
  "method": "方法名",
  "params": { }         // 参数(可选)
}

// 成功响应(Success Response)
{
  "jsonrpc": "2.0",
  "id": 1,              // 与请求 ID 对应
  "result": { }         // 结果
}

// 错误响应(Error Response)
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,     // 错误码
    "message": "错误描述",
    "data": { }         // 额外错误信息(可选)
  }
}

// 通知(Notification)— 不需要响应
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": { }
  // 注意:无 "id" 字段
}

MCP 完整生命周期消息流

Client                                    Server
  |                                          |
  |── initialize ──────────────────────────→ |
  |   {method: "initialize",                 |
  |    params: {                             |
  |      protocolVersion: "2024-11-05",      |
  |      capabilities: {roots:{}, sampling:{}},  |
  |      clientInfo: {name:"hermes",version:"1.7"} |
  |    }}                                    |
  |                                          |
  |←── initialize result ─────────────────── |
  |   {result: {                             |
  |     protocolVersion: "2024-11-05",       |
  |     capabilities: {tools:{}, resources:{}, prompts:{}},  |
  |     serverInfo: {name:"db-server",version:"1.0"}  |
  |   }}                                     |
  |                                          |
  |── initialized (notification) ──────────→ |
  |                                          |
  |── tools/list ─────────────────────────→  |
  |                                          |
  |←── tools/list result ─────────────────── |
  |   {result: {tools: [...]}}               |
  |                                          |
  |── tools/call ─────────────────────────→  |
  |   {method: "tools/call",                 |
  |    params: {                             |
  |      name: "search_database",            |
  |      arguments: {query:"...", table:"users"}  |
  |    }}                                    |
  |                                          |
  |←── tools/call result ─────────────────── |
  |   {result: {                             |
  |     content: [                           |
  |       {type:"text", text:"查询结果..."}  |
  |     ],                                   |
  |     isError: false                       |
  |   }}                                     |

完整的工具调用请求/响应

// 工具调用请求
{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "search_database",
    "arguments": {
      "query": "VIP 客户",
      "table": "users",
      "limit": 5
    }
  }
}

// 工具调用成功响应
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "找到 3 条 VIP 客户记录:\n1. 张三 (ID: 1001)\n2. 李四 (ID: 1002)\n3. 王五 (ID: 1003)"
      },
      {
        "type": "resource",
        "resource": {
          "uri": "db://users/vip-list",
          "mimeType": "application/json",
          "text": "[{\"id\":1001,\"name\":\"张三\",...}]"
        }
      }
    ],
    "isError": false
  }
}

// 工具调用错误响应(工具执行失败,但协议层面成功)
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "数据库查询失败:连接超时,请稍后重试。"
      }
    ],
    "isError": true   // 标记为工具执行错误
  }
}

错误码规范

JSON-RPC 标准错误码:
  -32700  Parse error       消息格式错误(无效 JSON)
  -32600  Invalid Request   请求格式不符合 JSON-RPC 规范
  -32601  Method not found  方法不存在
  -32602  Invalid params    参数错误
  -32603  Internal error    服务器内部错误

MCP 扩展错误码(-32000 以上为应用层):
  -32001  Resource not found  资源不存在
  -32002  Tool execution error  工具执行失败
  -32003  Unauthorized          未授权
  -32004  Rate limit exceeded   超过速率限制

37.6 MCP 与 OpenAPI 的对比

维度 MCP OpenAPI
设计目标 AI-Native 工具调用 RESTful API 文档化
传输协议 JSON-RPC 2.0 HTTP REST
Schema 格式 JSON Schema(子集) JSON Schema(完整)
能力类型 Tool+Resource+Prompt Endpoint
状态管理 有状态(Session) 无状态
流式支持 原生支持(SSE) 需额外扩展
AI 集成 一等公民(专为 LLM 设计) 后期适配
生态系统 新兴(2024年) 成熟(2011年)
现有 API 转换 可通过适配器 原生

为什么不用 OpenAPI 做工具调用?

OpenAPI 的问题:
1. 面向人类开发者设计,LLM 难以理解复杂的 REST 语义
2. 无法表达"这个工具有什么副作用"
3. 缺乏 LLM 专属的 Prompt 模板机制
4. 认证和能力发现标准不统一

MCP 的优势:
1. 语义明确(Tool vs Resource vs Prompt 各司其职)
2. 内置能力发现(initialize 时返回所有能力)
3. 为流式 LLM 输出设计(SSE 原生支持)
4. 包含 isError 等 LLM 友好的错误处理

37.7 Hermes 中的 MCP 集成

# hermes/mcp/client.py
import asyncio
import json
from typing import Any, Dict, List, Optional

class HermesMCPClient:
    """Hermes 的 MCP Client 实现"""
    
    def __init__(self, transport):
        self.transport = transport
        self._request_id = 0
        self._pending: Dict[int, asyncio.Future] = {}
        self._tools: List[Dict] = []
        self._resources: List[Dict] = []
    
    async def initialize(self) -> Dict:
        """执行 MCP 握手"""
        result = await self._request("initialize", {
            "protocolVersion": "2024-11-05",
            "capabilities": {
                "roots": {"listChanged": True},
                "sampling": {}
            },
            "clientInfo": {
                "name": "hermes-agent",
                "version": "1.7.0"
            }
        })
        
        # 发送 initialized 通知
        await self._notify("notifications/initialized")
        
        # 加载工具列表
        tools_result = await self._request("tools/list")
        self._tools = tools_result.get("tools", [])
        
        return result
    
    async def call_tool(self, name: str, arguments: Dict) -> Dict:
        """调用 MCP 工具"""
        return await self._request("tools/call", {
            "name": name,
            "arguments": arguments
        })
    
    async def list_tools(self) -> List[Dict]:
        """获取所有可用工具"""
        return self._tools
    
    async def read_resource(self, uri: str) -> Dict:
        """读取 MCP 资源"""
        return await self._request("resources/read", {"uri": uri})
    
    async def _request(self, method: str, params: Dict = None) -> Any:
        """发送 JSON-RPC 请求并等待响应"""
        request_id = self._next_id()
        future = asyncio.get_event_loop().create_future()
        self._pending[request_id] = future
        
        message = {
            "jsonrpc": "2.0",
            "id": request_id,
            "method": method,
        }
        if params:
            message["params"] = params
        
        await self.transport.send(message)
        result = await asyncio.wait_for(future, timeout=30.0)
        
        if "error" in result:
            raise MCPError(result["error"]["message"], result["error"]["code"])
        return result.get("result")
    
    async def _notify(self, method: str, params: Dict = None):
        """发送 JSON-RPC 通知(无需响应)"""
        message = {"jsonrpc": "2.0", "method": method}
        if params:
            message["params"] = params
        await self.transport.send(message)
    
    def _next_id(self) -> int:
        self._request_id += 1
        return self._request_id


class MCPError(Exception):
    def __init__(self, message: str, code: int):
        super().__init__(message)
        self.code = code

本章小结

MCP 协议通过标准化的 JSON-RPC 2.0 消息格式,为 AI Agent 与外部工具的集成提供了统一的语言:

  1. 设计目标:标准化、安全性、互操作性三位一体,解决 AI 工具集成的碎片化问题
  2. Client-Server 架构:Host 管理多个 Client,每个 Client 对应一个 Server,清晰的职责划分
  3. 三种 Transport:Stdio(本地进程)、SSE(远程流式)、Streamable HTTP(云端无状态)
  4. 三种能力:Tool(执行动作)、Resource(提供数据)、Prompt(提示模板)各有专属语义
  5. JSON-RPC 2.0:成熟的消息格式,统一的错误处理,清晰的请求/响应/通知三种消息类型
  6. vs OpenAPI:MCP 专为 AI-Native 场景设计,不是 OpenAPI 的替代品而是补充

思考题

  1. MCP 的 Stdio Transport 要求 Server 和 Client 在同一台机器上。在 Kubernetes 集群中,如何实现真正的跨节点 MCP Server 部署?
  2. MCP Server 的 initialize 阶段返回了所有 Tool 列表。如果一个 Server 有 500 个 Tool,LLM 的上下文窗口是否能容纳所有 Tool 的描述?如何解决这个问题?
  3. MCP 消息使用 JSON 编码,对于返回大型二进制数据(如图片、PDF)的工具,JSON 是最优选择吗?有什么改进方案?
  4. 如果两个 MCP Server 提供了名称相同的 Tool(都叫 search),Hermes 应该如何处理?
本章评分
4.7  / 5  (3 评分)

💬 留言讨论