1.5 MCP协议规范
🎯 学习目标:掌握 MCP 协议的分层结构、消息格式与无状态请求模型,能对照官方规范读懂并实现 MCP 通信
⏱️ 预计时间:40分钟
📊 难度等级:⭐⭐⭐
⚠️ 版本说明:本节内容对齐 MCP 官方规范 2026-07-28 版本。这一版是一次破坏性重构——协议从"有状态 + 初始化握手"改为无状态 + 每请求自带元数据。如果你看到"必须先 initialize 握手""WebSocket 传输""Mcp-Session-Id 会话"这类说法,都属于 2025-11-25 及更早的旧模型。旧版差异见 11.2 迁移到 2026-07-28。
📋 协议规范概览
MCP 协议建立在 JSON-RPC 2.0 之上。官方规范把它拆成七个部分:
规范把「MUST 支持」和「MAY 支持」分得很清楚:
| 部分 | 要求 | 说明 |
|---|---|---|
| 基础协议、版本与兼容性、消息模式 | MUST | 所有实现都必须支持 |
| 授权 | SHOULD(仅 HTTP 传输) | stdio 传输不应用它,凭据从环境变量取 |
| 服务器功能(工具/资源/提示词) | MAY | 按需实现 |
| 客户端功能(信息征询) | MAY | 按需实现 |
| 通用工具(日志/补全/分页/缓存) | MAY | 按需实现 |
🔑 一句话理解这次重构
每个请求都是自解释的:协议版本、客户端身份、客户端能力,全部塞在请求自己的
_meta里。服务器不再"记住连接",也不再有"连接建立"这个概念。
🔧 1. 基础协议层
📡 JSON-RPC 2.0 消息类型
MCP 完全兼容 JSON-RPC 2.0,定义了三种消息。
📝 请求(Request)
{
jsonrpc: "2.0";
id: string | number;
method: string;
params?: { [key: string]: unknown };
}关键约束(与基础 JSON-RPC 不同):
id必须是字符串或整数,不允许为nullid不得与发送方已发出但尚未收到响应的其他请求重复- 方向是单向的:请求只能由客户端发往服务器
✅ 结果响应(Result Response)
{
jsonrpc: "2.0";
id: string | number;
result: {
resultType: string; // 必需
[key: string]: unknown;
};
}resultType 是 2026-07-28 新增的多态结果标记,取值:
resultType | 含义 |
|---|---|
"complete" | 请求已正常完成,result 里是最终内容 |
"input_required" | 请求尚未完成,需要客户端补充输入(见 MRTR) |
两条兼容规则很重要:
- 扩展可以增加新的
resultType取值,但必须来自核心协议与已通过能力声明的扩展 - 客户端遇到无法识别的
resultType,必须视为非法 - 对实现旧版协议的服务器(不带
resultType),客户端必须按"complete"处理
❌ 错误响应(Error Response)
{
jsonrpc: "2.0";
id?: string | number;
error: {
code: number;
message: string;
data?: unknown;
};
}📢 通知(Notification)
{
jsonrpc: "2.0";
method: string;
params?: { [key: string]: unknown };
}通知不得包含 id,接收方不得回复。
🔢 错误码分区(2026-07-28 重新划分)
JSON-RPC 2.0 把 -32000 ~ -32099 留给实现自定义。MCP 把它切成了两段:
| 区间 | 归属 | 说明 |
|---|---|---|
-32000 ~ -32019 | 历史遗留 | 规范前时代各实现自行分配的码。不得再新增,新实现不应使用 |
-32020 ~ -32099 | MCP 规范保留 | 规范独占,实现不得发出未定义的码 |
MCP 当前定义的错误码:
| 错误码 | 名称 | 触发场景 |
|---|---|---|
-32020 | HeaderMismatch | Streamable HTTP 头部与请求体元数据不一致 |
-32021 | MissingRequiredClientCapability | 请求依赖了客户端未声明的能力,data.requiredCapabilities 列出缺失项 |
-32022 | UnsupportedProtocolVersion | 服务器不支持请求声明的协议版本,data.supported 列出它支持的版本 |
两个已停止使用但客户端仍应容忍的码:
-32002—— 资源不存在(2025-11-25 及更早),已被-32602(Invalid Params)取代。客户端仍应接受旧服务器返回的-32002-32042—— 需要 URL 征询(仅 2025-11-25)
💡 实现提示:本地错误(比如 SDK 内部的超时)目前没有规范分配的码。如果你要把本地错误包装成 JSON-RPC 结构,务必让它不会被误认为是对端发来的错误。
🌐 2. 传输层规范
🔗 只有两种标准传输
这是被中文资料误传最多的一点。MCP 官方只定义两种标准传输:
❗ 没有 WebSocket 传输。官方规范里不存在
new WebSocket('ws://...')这种用法,也没有{"transport": {"type": "http", "host": ..., "port": ...}}这种配置格式。这些都是早期社区文章的臆测。
一个传输(transport)在 MCP 里是一种绑定(binding):它只定义消息如何分帧、如何投递、如何携带元数据、如何取消与终止。协议语义与传输无关——消息模式属于核心协议,在任何传输上表现一致。
📟 stdio 传输
通过客户端启动的子进程的标准流通信,消息按换行符分隔。
适用场景:本地工具、命令行程序、IDE 集成——这也是绝大多数 MCP 服务器(文件系统、Git、数据库)用的方式。
# 输入(客户端写入子进程 stdin)
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}
# 输出(子进程写到 stdout)
{"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","tools":[]}}要点:
- 消息必须是 UTF-8 编码,必须以换行符结尾,不得内嵌换行
- 只有
stdout用于协议消息;日志一律写stderr - 取消请求:客户端发送
notifications/cancelled
🌊 Streamable HTTP 传输
每条消息都是一个 HTTP POST,发往同一个 MCP 端点;回复以单个 JSON 对象、或请求作用域的 SSE 流返回。
POST /mcp HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "北京" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}要点:
- 请求必须带
Mcp-Method(方法名)和Mcp-Name(资源名/工具名/提示词名,适用时)头部,让中间层无需解析请求体即可路由与鉴权 - 工具参数可以通过
x-mcp-header提升为自定义 HTTP 头 MCP-Protocol-Version头部镜像请求体里的io.modelcontextprotocol/protocolVersion;请求体才是真相来源,两者不一致时返回HeaderMismatch(-32020)- 取消请求:客户端关闭该请求的响应流
- 没有会话:
Mcp-Session-Id头部已在 2026-07-28 中移除
🔌 自定义传输
客户端与服务器可以实现额外传输。要求:
- 必须保留 JSON-RPC 消息格式、消息模式、以及每请求元数据模型
- 应该说明自己的连接建立、分帧、取消方式
- 若跑在可靠的双向字节流上(Unix domain socket、TCP),应该直接复用 stdio 的分帧格式,而不是另造一套
🧭 该选哪个传输?
| 场景 | 推荐 | 原因 |
|---|---|---|
| 本地工具、IDE 插件、CLI | stdio | 零网络配置,生命周期由客户端管理 |
| 远程/多租户服务、SaaS | Streamable HTTP | 走标准 HTTP 生态(网关、鉴权、可观测) |
| 内网高性能进程间通信 | 自定义传输 | 复用 stdio 分帧,需自行处理发现与鉴权 |
📨 3. 无状态请求模型
🚀 没有初始化握手了
旧模型(≤ 2025-11-25):
新模型(2026-07-28):
规范原文很直白:
MCP 是一个无状态协议:处理一个请求所需的全部信息都包含在这个请求自身之中。服务器独立处理每个请求,不应从先前的请求(哪怕是在同一条连接或流上)推断任何状态。
这意味着几条硬约束:
- 服务器不得依赖同连接上的先前请求来建立上下文(能力、协议版本、客户端身份)——每个请求都在
_meta里自带 - 服务器不应要求客户端复用同一条连接或同一个进程
- 客户端不应把单个任务/会话当作 stdio 进程的生命周期边界
- 需要跨请求延续的状态(长时间任务、应用级句柄),必须用显式标识符在每次请求里传递
⚠️ 常见误解:stdio 进程不等于会话。客户端可以在同一条 stdio 上交错发起互不相关的请求,服务器不得把进程身份当作会话连续性的代理。
🏷️ _meta:每请求元数据
_meta 是 MCP 用来挂载协议级元数据的字段。键名格式为「可选前缀 + 名称」,前缀用反域名写法。第二段标签为 modelcontextprotocol 或 mcp 的前缀一律保留给 MCP。
规范的保留键:
| 键 | 位置 | 必需 | 说明 |
|---|---|---|---|
io.modelcontextprotocol/protocolVersion | 请求 | 是 | 本请求使用的协议版本,如 "2026-07-28" |
io.modelcontextprotocol/clientCapabilities | 请求 | 是 | 与本次请求相关的客户端能力 |
io.modelcontextprotocol/clientInfo | 请求 | 否 | 客户端名称与版本(SHOULD 每次携带) |
io.modelcontextprotocol/logLevel | 请求 | 否 | 服务器对该请求应输出的最低日志级别 |
io.modelcontextprotocol/serverInfo | 响应 | 否 | 服务器名称与版本(SHOULD 每个结果都带) |
io.modelcontextprotocol/subscriptionId | 通知 | 是(订阅流) | 把通知关联回它的订阅请求 |
progressToken | 请求 | 否 | 让该请求进入进度通知模式 |
traceparent / tracestate / baggage | 任意 | 否 | OpenTelemetry 链路上下文(W3C 格式) |
两条拒绝规则:
- 请求缺少任一必需字段即为畸形 → 返回
-32602(Invalid params),HTTP 上状态码必须是400 Bad Request - 服务器不得依赖客户端未声明的能力。若处理请求需要的能力不在
clientCapabilities里 → 返回MissingRequiredClientCapabilityError(-32021),data.requiredCapabilities列出缺失项;HTTP 上状态码必须是400
一个完整的请求长这样:
{
"jsonrpc": "2.0",
"id": "req-1",
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "北京" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {},
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"
}
}
}🔒
clientInfo与serverInfo都是自报的,协议不做校验。它们只用于展示、日志和调试——不要用它们做安全决策。
🔍 server/discover:一次性问清服务器
服务器必须实现 server/discover。它让客户端在发任何业务请求之前,一次拿到服务器支持的协议版本、能力与身份。
请求:
{
"jsonrpc": "2.0",
"id": "discover-1",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}响应:
{
"jsonrpc": "2.0",
"id": "discover-1",
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {
"tools": {},
"resources": {}
},
"_meta": {
"io.modelcontextprotocol/serverInfo": { "name": "ExampleServer", "version": "1.0.0" }
},
"instructions": "本服务器提供天气与资源工具。",
"ttlMs": 3600000,
"cacheScope": "public"
}
}对客户端来说调用它是可选的——你完全可以直接发业务请求,遇到 UnsupportedProtocolVersionError 再处理。但两种场景下它很有用:
- 展示服务器信息:一次请求拿到服务器身份、能力和支持版本,而不用分别探测
tools/list、prompts/list、resources/list - stdio 上的向后兼容探测:stdio 没有 HTTP 状态码可用,需要同时兼容新旧两代服务器的客户端应该先发
server/discover
🔄 版本协商与新旧互操作
术语先统一(官方原文):
- Modern(现代):把版本、身份、能力作为每请求元数据传递的版本,即
2026-07-28及之后 - Legacy(旧版):用
initialize握手建立会话的版本,即2025-11-25及之前 - Dual-era(双代):同时支持两者的实现
版本不匹配时,服务器必须返回:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32022,
"message": "Unsupported protocol version",
"data": {
"supported": ["2026-07-28", "2025-11-25"],
"requested": "1900-01-01"
}
}
}客户端应该从 supported 里挑一个双方都支持的版本重试;如果不存在,就把错误抛给用户。
客户端/服务器两代组合的结果(官方兼容矩阵的浓缩版):
| 客户端 | 服务器 | 结果 |
|---|---|---|
| 现代 | 现代 | ✅ 正常 |
| 现代 | 旧版 | ❌ 失败。stdio 上应该先发 server/discover 以便确定性失败 |
| 双代 | 现代 | ✅ 正常,保持现代路径 |
| 双代 | 旧版 | ✅ 回退到 initialize |
| 旧版 | 现代 | ❌ 失败。旧客户端没有向前回退机制 |
| 旧版 | 双代 | ✅ 正常,按协商的旧版语义服务 |
双代服务器怎么决定走哪条路:
- 请求带现代
_meta→ 按本版规范无状态处理 - 收到
initialize→ 进入旧版语义
🧩 扩展协商
能力对象里的 extensions 字段用来协商可选扩展。它是一张「扩展标识符 → 该扩展的设置对象」的映射表,标识符遵循 _meta 的键名规则(必须带前缀)。
{
"capabilities": {
"tools": {},
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}空对象表示「支持该扩展,但没有额外设置」。一方支持而另一方不支持时,支持方必须要么退回核心协议行为,要么返回恰当的错误。
🔀 4. 消息模式
核心协议定义了三种交互模式,所有传输上表现一致:
| 模式 | 方向 | 用途 |
|---|---|---|
| 请求与响应 | 客户端 → 服务器 | 常规调用 |
| 多轮请求 MRTR | 客户端 ↔ 服务器(通过重试) | 服务器需要客户端补充输入 |
| 订阅与通知 | 服务器 → 客户端 | 变更推送 |
🔁 多轮请求(MRTR)
这是 2026-07-28 的另一个关键变更:服务器不再主动发起 JSON-RPC 请求。
规范原文:服务器不得发起 JSON-RPC 请求,客户端不得发送 JSON-RPC 响应。
旧模型里服务器要"反过来问客户端"(roots/list、sampling/createMessage、elicitation/create),这在无状态的 HTTP 世界里很别扭。新模型改成:服务器返回一个中间结果,客户端带着补充信息重试原请求。
服务器返回的中间结果:
{
"jsonrpc": "2.0",
"id": 55,
"result": {
"resultType": "input_required",
"inputRequests": {
"ask-1": {
"method": "elicitation/create",
"params": {
"message": "请选择要部署的环境",
"requestedSchema": {
"type": "object",
"properties": {
"environment": { "type": "string", "enum": ["staging", "production"] }
},
"required": ["environment"]
}
}
}
}
}
}客户端重试时带上 inputResponses:
{
"jsonrpc": "2.0",
"id": 56,
"method": "tools/call",
"params": {
"name": "deploy_service",
"arguments": { "service": "api" },
"inputResponses": {
"ask-1": {
"action": "accept",
"content": { "environment": "staging" }
}
},
"_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {} }
}
}要点:
- 重试是一个全新的 JSON-RPC 请求(新的
id) - 服务器要跨重试关联状态时,把自定义标识放在结果的
requestState里,客户端原样带回 - 需要用到的客户端能力,必须已经在
clientCapabilities里声明,否则服务器返回-32021
📡 订阅与通知
subscriptions/listen 取代了旧的 HTTP GET 端点和 resources/subscribe / resources/unsubscribe 两个方法。它开一条长生命周期的通知流。
请求(带订阅过滤器):
{
"jsonrpc": "2.0",
"id": 1,
"method": "subscriptions/listen",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
},
"notifications": {
"toolsListChanged": true,
"resourceSubscriptions": ["file:///project/config.json"]
}
}
}订阅过滤器字段:
| 字段 | 类型 | 说明 |
|---|---|---|
toolsListChanged | boolean | 接收 notifications/tools/list_changed |
promptsListChanged | boolean | 接收 notifications/prompts/list_changed |
resourcesListChanged | boolean | 接收 notifications/resources/list_changed |
resourceSubscriptions | string[] | 接收这些资源 URI 的 notifications/resources/updated |
服务器不得发送客户端未显式请求的通知类型。流的第一条消息必须是确认:
{
"jsonrpc": "2.0",
"method": "notifications/subscriptions/acknowledged",
"params": {
"_meta": { "io.modelcontextprotocol/subscriptionId": 1 },
"notifications": {
"toolsListChanged": true,
"resourceSubscriptions": ["file:///project/config.json"]
}
}
}notifications 字段反映服务器实际同意的那部分——不支持的类型会被省略。客户端应该核对确认结果与自己的请求,优雅处理被拒的类型。
之后每条通知都带订阅 ID:
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"_meta": { "io.modelcontextprotocol/subscriptionId": 1 },
"uri": "file:///project/config.json"
}
}几个容易踩坑的点:
- 请求作用域的通知不走这条流。
notifications/progress、notifications/message走的是它们所关联请求的响应流 - SSE 流的可恢复性与消息重投已被移除(
Last-Event-ID头和 SSE event ID 都没了)。响应流断了,在途请求就丢了,客户端必须用新的请求 ID 重新发起 - 订阅结束的三种方式:客户端取消(HTTP 关流 / stdio 发
notifications/cancelled)、服务器主动关闭(应该先返回一个完成结果表示优雅结束)、底层传输断开 - stdio 上重连后必须重发
subscriptions/listen——服务器不跨重连保存订阅状态
🚫 已被删除的方法
下面这些方法在旧教程里还作为"标准方法"出现,但 2026-07-28 已经删除:
| 方法 | 现状 | 替代方案 |
|---|---|---|
ping | 已删除 | 无(无状态协议不需要保活) |
logging/setLevel | 已删除 | 请求级 _meta 里的 io.modelcontextprotocol/logLevel |
notifications/roots/list_changed | 已删除 | 目录改由工具参数或资源 URI 传递 |
resources/subscribe / resources/unsubscribe | 已删除 | subscriptions/listen |
initialize / notifications/initialized | 已删除 | 每请求 _meta + server/discover |
tasks/result / tasks/list | 移出核心 | Tasks 扩展:tasks/get 轮询 + tasks/update |
服务器不得为没有携带
logLevel的请求发送notifications/message。
🛠️ 5. 服务器功能
🔨 工具(Tools)
服务器支持工具就必须声明 tools 能力:
{
"capabilities": {
"tools": { "listChanged": true }
}
}📌 确定性与缓存:
tools/list的结果不应随连接变化,也不应被同连接上其他请求的副作用影响。服务器应该以确定顺序返回工具,这样客户端能可靠缓存、也能提高 LLM 的提示缓存命中率。(它可以随请求携带的授权变化——比如只返回调用方 scope 允许的工具,因为凭据是每请求输入。)
列出工具
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": { "cursor": "optional-cursor-value" }
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"tools": [
{
"name": "get_weather",
"title": "天气信息提供者",
"description": "获取指定位置的当前天气",
"inputSchema": {
"type": "object",
"properties": {
"location": { "type": "string", "description": "城市名或邮编" }
},
"required": ["location"]
},
"icons": [
{ "src": "https://example.com/weather-icon.png", "mimeType": "image/png", "sizes": ["48x48"] }
]
}
],
"nextCursor": "next-page-cursor",
"ttlMs": 300000,
"cacheScope": "public"
}
}注意结果里的 ttlMs 与 cacheScope——2026-07-28 要求 tools/list、prompts/list、resources/list、resources/read、resources/templates/list 的结果实现 CacheableResult 接口:
ttlMs:新鲜度提示(毫秒),让客户端放心缓存、减少轮询cacheScope:"public"或"private",决定共享中间层能否缓存
调用工具
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "北京" }
}
}{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "北京当前天气:\n温度:25°C\n天气:晴"
}
],
"isError": false
}
}结构化输出
工具可以声明 outputSchema,并在结果里返回 structuredContent。2026-07-28 已放宽限制:inputSchema / outputSchema 允许使用任意 JSON Schema 2020-12 关键字,structuredContent 允许任意 JSON 值。
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"content": [{ "type": "text", "text": "{\"temperature\":25,\"conditions\":\"晴\"}" }],
"structuredContent": { "temperature": 25, "conditions": "晴" },
"isError": false
}
}📁 资源(Resources)
资源用 URI 标识,供客户端读取上下文数据。resources/list 列出静态资源,resources/templates/list 列出带参数模板,resources/read 读取内容。
// 请求
{ "jsonrpc": "2.0", "id": 4, "method": "resources/read", "params": { "uri": "file:///docs/readme.md" } }// 响应
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"resultType": "complete",
"contents": [
{
"uri": "file:///docs/readme.md",
"mimeType": "text/markdown",
"text": "# 项目说明\n\n这是一个示例项目..."
}
],
"ttlMs": 60000,
"cacheScope": "private"
}
}⚠️ 资源不存在时的错误码已经从
-32002改为-32602(Invalid Params),以对齐 JSON-RPC 规范。客户端仍应接受旧服务器返回的-32002。
💬 提示词(Prompts)
提示词是服务器暴露的模板,客户端可以带参数取回渲染好的消息序列。
// 请求
{
"jsonrpc": "2.0",
"id": 7,
"method": "prompts/get",
"params": {
"name": "code_reviewer",
"arguments": { "language": "Python", "focus": "安全性" }
}
}// 响应
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"resultType": "complete",
"description": "代码审查助手提示词",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "你是一个专业的 Python 代码审查专家,重点关注安全性..."
}
}
]
}
}📊 通用工具
| 能力 | 方法 | 说明 |
|---|---|---|
| 分页 | 各 */list 的 cursor / nextCursor | 大列表分批返回 |
| 缓存 | ttlMs + cacheScope | 列表与读取结果可缓存 |
| 参数补全 | completion/complete | 为提示词/资源模板参数提供自动补全 |
| 日志 | _meta.io.modelcontextprotocol/logLevel + notifications/message | 请求级日志级别 |
👤 6. 客户端功能
💬 信息征询(Elicitation)
这是 2026-07-28 里客户端唯一可提供的核心能力。服务器需要用户补充信息时,通过 MRTR 返回 input_required,客户端负责向用户提问并把答案带回。
{
"capabilities": {
"elicitation": {}
}
}⚰️ 已废弃:roots 与 sampling
这两个曾经的核心客户端能力,在 2026-07-28 被 SEP-2577 标记为废弃。它们在废弃窗口内仍能工作,但新实现不应再支持。
官方给出的迁移方向:
| 废弃特性 | 官方建议的替代方案 |
|---|---|
| Roots | 通过工具参数、资源 URI 或服务器配置传递目录/文件 |
| Sampling | 直接对接 LLM 提供方 API |
| Logging | stdio 上写 stderr,或使用 OpenTelemetry |
🔒 7. 授权
MCP 为 HTTP 传输提供了授权框架(基于 OAuth 2.1)。要点:
- 使用 HTTP 传输的实现 SHOULD 遵循该框架
- 使用 stdio 传输的实现 SHOULD NOT 使用它——而应从环境变量获取凭据
- 双方 MAY 协商自定义的鉴权方案
规范里的关键安全要求(2026-07-28 更新):
- 授权服务器应该在授权响应中带上
iss参数(RFC 9207),客户端在兑换授权码前必须校验iss与记录的颁发者一致 - 客户端在做动态客户端注册时必须指定合适的
application_type,避免与 OpenID Connect 的重定向 URI 冲突 - 客户端凭据绑定在签发它的授权服务器上:必须按颁发者标识存储,不得跨授权服务器复用,授权服务器变更时必须重新注册
完整实现细节见 6.2 身份认证与授权。
📊 8. 内容类型
MCP 定义了若干标准内容类型,用于工具结果、提示词消息和资源内容:
| 类型 | 用途 | 关键字段 |
|---|---|---|
text | 纯文本 | text |
image | 图像(base64) | data、mimeType |
audio | 音频(base64) | data、mimeType |
resource_link | 指向资源的链接 | uri、name、mimeType |
resource | 内嵌资源内容 | resource.uri、resource.text 或 resource.blob |
{ "type": "text", "text": "这是一段文本内容" }{
"type": "image",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
"mimeType": "image/png"
}{
"type": "resource",
"resource": {
"uri": "file:///path/to/file.txt",
"mimeType": "text/plain",
"text": "文件内容..."
}
}⚠️ 常见误区速查
中文资料里流传最广的几条过时说法,对照表如下:
| 常见说法 | 实际情况(2026-07-28) |
|---|---|
| "MCP 支持 HTTP / WebSocket / Unix Socket / stdio 四种传输" | ❌ 只有 stdio 和 Streamable HTTP 两种标准传输,另允许自定义传输 |
| "每个连接必须先完成 initialize 初始化握手" | ❌ 握手已删除,改为无状态 + 每请求 _meta |
"用 Mcp-Session-Id 维持会话" | ❌ 会话与请求头已移除,跨调用状态改用显式句柄 |
"服务器可以主动发 sampling/createMessage" | ❌ 改为 MRTR,服务器返回 input_required,客户端重试 |
"用 SSE 的 Last-Event-ID 续传断掉的流" | ❌ 可恢复性与重投已移除,必须用新请求 ID 重发 |
"用 resources/subscribe 订阅资源变更" | ❌ 改为 subscriptions/listen + 过滤器 |
"用 logging/setLevel 调整日志级别" | ❌ 改为请求级 _meta.io.modelcontextprotocol/logLevel |
"用 ping 做心跳保活" | ❌ ping 已删除 |
"工具结果只有 content" | ❌ 每个结果必须带 resultType;列表类结果还需 ttlMs + cacheScope |
🎯 协议规范总结
📚 核心要点
🏗️ 协议结构
- ✅ JSON-RPC 2.0 基础:请求 / 结果响应 / 错误响应 / 通知四种消息,结果必须带
resultType - ✅ 错误码分区:
-32020~-32099为规范保留;历史码不再新增 - ✅ 传输层:只有 stdio 与 Streamable HTTP 两种标准传输
📨 无状态模型
- ✅ 无握手:每请求在
_meta里自带协议版本与客户端能力 - ✅
server/discover:服务器必须实现,用于一次性获取版本、能力与身份 - ✅ 版本协商:不匹配返回
-32022,客户端从supported里重选
🔀 消息模式
- ✅ 请求与响应:客户端单向发起
- ✅ MRTR:服务器返回
input_required,客户端带inputResponses重试 - ✅ 订阅与通知:
subscriptions/listen长流 + 过滤器 + 订阅 ID
🛠️ 功能规范
- ✅ 工具:
tools/list(可缓存、确定性顺序)、tools/call、结构化输出 - ✅ 资源:
resources/list、resources/read、模板 - ✅ 提示词:
prompts/list、prompts/get - ✅ 客户端能力:只剩 Elicitation;roots / sampling / logging 已废弃
🔗 规范地图
🤔 实践思考
💭 理解检测
- 无状态的意义:为什么把会话从协议层拿掉,反而让 MCP 更容易做水平扩展?
- MRTR 的取舍:相比"服务器主动发起请求",MRTR 牺牲了什么、换来了什么?
- 传输选择:什么情况下你会选择自定义传输而不是 Streamable HTTP?
- 错误码设计:为什么规范要把
-32000~-32019冻结为"历史遗留"?
🛠️ 实践挑战
写一个最小但符合 2026-07-28 规范的交互流程:
- 发现:客户端发
server/discover,拿到服务器能力与支持版本 - 列表:调
tools/list,检查结果里的ttlMs/cacheScope - 调用:调
tools/call,处理resultType: "input_required"并用inputResponses重试 - 订阅:开一条
subscriptions/listen,正确处理确认消息与订阅 ID
📖 扩展学习
🔗 参考资料
- 📋 MCP 官方规范 · 2026-07-28
- 🔄 官方变更日志(Key Changes)
- 🧭 Versioning and Compatibility
- 🔍 Discovery(
server/discover) - 📋 JSON-RPC 2.0 规范
- 🔐 OAuth 2.1 草案
🎓 下一步
掌握了协议规范后,你就可以:
- 📝 实现符合 2026-07-28 规范的 MCP 客户端
- 🔧 开发标准的 MCP 服务器(记得实现
server/discover) - 🐛 迅速识别旧教程里的过时写法
- 🔒 设计安全的 MCP 部署方案
📚 想继续深入规范外围:12.1 OAuth 2.1 授权体系(协议级授权)、12.3 官方扩展体系(Tasks / MCP Apps / Skills)、12.4 官方注册表 Registry(发布与发现)。
协议清楚了,接下来补齐架构视图——从组件和数据流的角度把 MCP 串起来。