02 · 版本协商与服务器发现
旧版 MCP 靠一次
initialize握手敲定“我们说哪个版本、各自支持什么”。draft 取消了握手——这一章讲它用什么替代:每请求声明版本 + 可选的server/discover+ 一套新旧时代互探规则。
2.1 没有握手了,怎么协商版本
核心一句话:没有协商握手;每个请求自带版本,服务器逐条接受或拒绝(docs/specification/draft/basic/versioning.mdx:12)。
Client ──请求(_meta 里带 protocolVersion)──► Server
│ │
│ ┌── 服务器支持该版本 ──► 正常返回 result │
│ └── 不支持 ──► UnsupportedProtocolVersionError
│ (附带 supported 列表)
◄── 客户端从 supported 里挑一个双方都支持的版本,重发
服务器不支持请求里的版本时,必须回 UnsupportedProtocolVersionError(-32022),并在 data.supported 里列出自己支持的版本(docs/specification/draft/basic/versioning.mdx:50):
{
"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 里挑一个双方都支持的版本重试,挑不出就报错给用户。
2.2 server/discover:一次拿齐版本、能力、身份
虽然没了握手,但客户端常常想在动手前先知道“这服务器支持啥”。这就是 server/discover——服务器必须实现它(docs/specification/draft/server/discover.mdx:8)。
请求体没有业务参数,只带标准 _meta;响应一口气给齐(docs/specification/draft/server/discover.mdx:38):
{
"jsonrpc": "2.0", "id": "discover-1",
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": { "tools": {}, "resources": {} },
"serverInfo": { "name": "ExampleServer", "version": "1.0.0" },
"instructions": "This server provides weather and resource utilities.",
"ttlMs": 3600000, "cacheScope": "public"
}
}
它对客户端是可选的(docs/specification/draft/server/discover.mdx:60)——你完全可以直接发 tools/call,万一版本不对就处理 UnsupportedProtocolVersionError。但两种场景下它很有用:
- 展示服务器信息 — 一次请求拿到身份、能力、版本,不必分别探
tools/list/prompts/list/resources/list。 - stdio 向后兼容探测 — stdio 没有 HTTP 状态码可借力,先发
server/discover能让“对面是不是老服务器”确定性地暴露出来(见 §2.5)。
注意 instructions 字段:给 LLM 看的自然语言用法指引,相当于服务器的“说明书摘要”。
2.3 能力协商:声明了才能用
MCP 是基于能力的协议:双方各自声明支持哪些特性,谁都不能用对方没声明的能力(docs/specification/draft/architecture/index.mdx:116)。
- 服务器在
server/discover的响应里声明tools/resources/prompts等。 - 客户端在每个请求的
_meta.io.modelcontextprotocol/clientCapabilities里声明sampling/elicitation/roots等。
能力对象里还能带子选项,比如工具的 listChanged(清单变动会发通知)、资源的 subscribe(支持订阅更新)。
2.4 扩展协商:核心之外的可选能力
核心协议刻意做小,更多能力走**扩展(extensions)**机制(docs/specification/draft/basic/versioning.mdx:81)。扩展在 capabilities 的 extensions 字段里声明——一个“扩展 id → 设置对象”的 map,id 必须带前缀(遵守 _meta 命名规则):
{
"capabilities": {
"tools": {},
"extensions": { "io.modelcontextprotocol/tasks": {} }
}
}
一方支持、另一方不支持时,支持方必须退回核心行为或明确报错(docs/specification/draft/basic/versioning.mdx:120)。
值得一提:draft 把原先实验性的 tasks(长任务)从核心协议挪进了官方扩展 io.modelcontextprotocol/tasks(changelog docs/specification/draft/changelog.mdx:22)——这正是“核心极简、能力靠扩展”原则的体现。
2.5 新旧时代互通(dual-era)
现实里会同时存在两代实现,规范给了精确的术语和互探规则(docs/specification/draft/basic/versioning.mdx:29):
| 术语 | 含义 |
|---|---|
| Modern(现代) | 版本/身份/能力放每请求 _meta(2026-07-28 起) |
| Legacy(传统) | 靠 initialize 握手建会话(2025-11-25 及更早) |
| Dual-era(双时代) | 同时支持两种的实现 |
客户端怎么判断对面是哪一代?靠传输层特征(docs/specification/draft/basic/versioning.mdx:132):
- stdio:先发
server/discover探测。返回DiscoverResult或某个已知的现代错误 → 现代服务器;返回其它任意错误或超时 → 传统服务器,回退到initialize(docs/specification/draft/basic/transports/stdio.mdx:121)。 - Streamable HTTP:先试一个现代请求,碰到
400 Bad Request时先看响应体——是不是已知的现代 JSON-RPC 错误。是 → 现代;否 → 回退initialize。
关键纪律(docs/specification/draft/basic/transports/stdio.mdx:139):回退判断不能只盯一个错误码,因为老服务器对未知方法的报错是“实现自定义”的(常见 -32601/-32602,也可能干脆不回)。
时代判定是服务器的属性、不是单条请求的:客户端应缓存结果(stdio 按进程、HTTP 按 origin),失效再重探(docs/specification/draft/basic/versioning.mdx:148)。完整的客户端×服务器组合结局见 docs/specification/draft/basic/versioning.mdx:159 的兼容矩阵。
代码地图
| 主题 | 文件路径 | 符号 / 锚点 |
|---|---|---|
| 版本协商规则 | docs/specification/draft/basic/versioning.mdx | Protocol Version Negotiation / Compatibility Matrix |
| 服务器发现 | docs/specification/draft/server/discover.mdx | DiscoverResult |
| 发现请求/结果类型 | schema/draft/schema.ts | DiscoverRequest、DiscoverResult |
| 版本不支持错误 | schema/draft/schema.ts | UnsupportedProtocolVersionError、UNSUPPORTED_PROTOCOL_VERSION |
| 客户端/服务器能力 | schema/draft/schema.ts | ClientCapabilities、ServerCapabilities |
| stdio 兼容探测 | docs/specification/draft/basic/transports/stdio.mdx | Backward Compatibility |