第 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 与外部工具的集成提供了统一的语言:
- 设计目标:标准化、安全性、互操作性三位一体,解决 AI 工具集成的碎片化问题
- Client-Server 架构:Host 管理多个 Client,每个 Client 对应一个 Server,清晰的职责划分
- 三种 Transport:Stdio(本地进程)、SSE(远程流式)、Streamable HTTP(云端无状态)
- 三种能力:Tool(执行动作)、Resource(提供数据)、Prompt(提示模板)各有专属语义
- JSON-RPC 2.0:成熟的消息格式,统一的错误处理,清晰的请求/响应/通知三种消息类型
- vs OpenAPI:MCP 专为 AI-Native 场景设计,不是 OpenAPI 的替代品而是补充
思考题
- MCP 的 Stdio Transport 要求 Server 和 Client 在同一台机器上。在 Kubernetes 集群中,如何实现真正的跨节点 MCP Server 部署?
- MCP Server 的
initialize阶段返回了所有 Tool 列表。如果一个 Server 有 500 个 Tool,LLM 的上下文窗口是否能容纳所有 Tool 的描述?如何解决这个问题? - MCP 消息使用 JSON 编码,对于返回大型二进制数据(如图片、PDF)的工具,JSON 是最优选择吗?有什么改进方案?
- 如果两个 MCP Server 提供了名称相同的 Tool(都叫
search),Hermes 应该如何处理?