跳到主内容

MCP 能力协商与版本兼容:2026-07-28 之后已经没有握手了

100%
MCP 能力协商与版本兼容:2026-07-28 之后已经没有握手了

结论:MCP 从 2026-07-28 修订起取消了握手式协商——每个请求在 _meta 里自带协议版本、客户端身份与能力,服务端逐条接受或拒绝。想提前知道服务端支持什么,就调一次强制实现的 server/discover;不想多一次往返,直接发请求、收到 UnsupportedProtocolVersionError 再从返回的 supported 列表里挑一个版本重试即可。

理解新版 MCP 的关键心智模型:能力不再是”连上之后谈一次就锁定”,而是”每个请求各自声明”。这直接换来了无状态、可横向扩展的服务端——任何请求都能落到负载均衡后面的任意实例。

两个时代:legacy 与 modern

按官方术语,协议被分成两类行为族:

时代版本范围连接方式关键特征
Legacy2025-11-25 及更早(含 2024-10-07)initialize 握手 + Mcp-Session-Id有会话状态、双向流
Modern2026-07-28 及之后无握手,server/discover 可选探测每请求自描述、可无状态
Dual-era两者都支持按探测结果切换兼容期主流实现

版本号是 YYYY-MM-DD 格式,表示”最后一次不兼容变更的日期”;向后兼容的改动不会递增版本号。当前版本是 2026-07-28。

版本协商:没有协商握手

这是最容易误解的一点。新版里不存在版本协商握手,流程是这样的:

  1. 客户端在请求的 _meta 里带上 io.modelcontextprotocol/protocolVersion;HTTP 传输同时用 MCP-Protocol-Version 头承载同一个值。
  2. 服务端逐条判断:支持就处理,不支持就返回错误码 -32022 的 UnsupportedProtocolVersionError,并在 data.supported 里列出自己支持的版本。
  3. 客户端从 supported 里选一个双方都支持的版本重试;一个都没有就向用户报错。
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32022,
    "message": "Unsupported protocol version",
    "data": {
      "supported": ["2026-07-28", "2025-11-25"],
      "requested": "1900-01-01"
    }
  }
}

server/discover:可选的提前探测

如果不想”先撞墙再重试”,可以在发业务请求前调一次 server/discover——这是服务端必须实现的 RPC,一次返回支持的协议版本、能力和身份信息:

// 请求
{ "jsonrpc": "2.0", "id": 0, "method": "server/discover", "params": {} }

// 响应(示意)
{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersions": ["2026-07-28", "2025-11-25"],
    "capabilities": { "tools": {}, "resources": {} },
    "serverInfo": { "name": "my-server", "version": "2.1.0" }
  }
}

调用它是可选的,但有几个实际好处:一次性拿到能力清单、避免第一次业务请求的失败往返、把探测结果缓存下来供后续复用。

扩展协商:capabilities 里的 extensions 字段

核心协议之外的可选能力,通过 capabilities.extensions 这个 map 声明,键是扩展标识符(必须带前缀),值是该扩展的设置对象。空对象表示”支持但无额外配置”。

// 客户端声明支持 MCP Apps 扩展
{
  "capabilities": {
    "roots": {},
    "extensions": {
      "io.modelcontextprotocol/ui": {
        "mimeTypes": ["text/html;profile=mcp-app"]
      }
    }
  }
}

// 服务端声明支持 Tasks 扩展
{
  "capabilities": {
    "tools": {},
    "extensions": { "io.modelcontextprotocol/tasks": {} }
  }
}

规则很明确:一方支持、另一方不支持时,支持方必须回退到核心协议行为,或者用合适的错误拒绝请求。扩展规范应当写明预期回退行为。这意味着你在实现扩展时必须想清楚”对面不支持时怎么办”,否则对面会直接报错。

目前已正式化的扩展包括 MCP Apps(UI)、 Tasks 、 Enterprise Managed Authorization 等。

客户端侧怎么选时代

TypeScript SDK 提供三种模式,对应不同的兼容性策略:

  • mode: 'legacy'(默认):直接走 2025 握手,零探测,行为与旧版一致。
  • mode: 'auto':先用 server/discover 探测,探测不到现代服务端就回退到 initialize 握手(多一次往返,但不报错)。
  • mode: { pin: '2026-07-28' }:只认这个版本,探测失败直接抛 ERA_NEGOTIATION_FAILED,绝不回退。
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';

const client = new Client(
  { name: 'my-client', version: '1.0.0' },
  { versionNegotiation: { mode: 'auto' } }
);
await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')));
console.log(client.getProtocolEra()); // 'modern' | 'legacy'

四个实现要点

  1. 时代判定属于服务端,不属于单次请求:客户端应在进程生命周期内缓存探测结论,不要每个请求都探一次。
  2. 鉴权状态不能当作时代证据:401/403 必须当成鉴权失败处理,不能据此回退到 legacy 。同理 5xx 是服务端故障,也不是时代证据。
  3. 只支持现代版的服务端,应在返回给 initialize 的错误里列出支持版本——老客户端没有前推机制,这可能是用户能看到的唯一诊断信息。
  4. 会话状态要显式化:新版不再有传输层会话,如果业务需要跨调用状态,应由工具自己签发一个 handle,让模型作为参数传回来。这样做的好处是模型能”看见”这个句柄,比藏在传输层里更可控。
兼容性矩阵:谁连谁会怎样
  • Modern 客户端 → Modern 服务端:正常。server/discover 可选,版本不匹配时按错误重试。
  • Modern 客户端 → Legacy 服务端:失败。服务端可能拒绝、静默,甚至用 legacy 语义处理同名方法。 stdio 上建议先发 server/discover 以确定性失败。
  • Dual-era → Modern:正常,探测返回 DiscoverResult 。
  • Legacy 客户端 → Modern 服务端:失败,且老客户端无法前推——这就是为什么现代服务端要在错误里写清支持的版本。

实践建议:如果你是服务端作者且需要照顾存量客户端,做 dual-era 支持;如果是全新项目,直接 pin 现代版本,省掉一整套兼容分支。

和旧版实现怎么共存

传输层的选择会影响探测方式:MCP 传输方式 stdio 与 Streamable HTTP 的差异在这里很关键——stdio 上用 server/discover 探测,失败即回退;Streamable HTTP 上先发现代请求,再检查 400 响应的 body 判断要不要回退。

错误处理层面,MCP 错误处理 区分了协议错误与工具执行错误两类,UnsupportedProtocolVersionError 属于前者,必须在传输层就处理掉,不能冒泡到业务逻辑。验证实现是否正确,用 MCP Inspector 与协议一致性测试 是最快的方式;如果你在写客户端,可以参考 Python MCP 客户端开发 的骨架。


MCP 还需要 initialize 握手吗?
2026-07-28 起不再需要。官方正式移除了 initialize/initialized 交换和 Mcp-Session-Id 头,改为每个请求自描述。旧版(2025-11-25 及更早)仍然使用握手,所以客户端需要判断服务端属于哪个时代。

server/discover 是必须调用的吗?
对客户端是可选的,对服务端是必须实现的。客户端可以直接发业务请求,靠 UnsupportedProtocolVersionError 拿到支持列表再重试;想省掉这次失败往返,就先调一次 server/discover 。

能力协商和版本协商是一回事吗?
不是。版本协商决定”说哪个协议版本”,通过 _meta 里的 protocolVersion 和错误重试完成;能力协商决定”启用哪些可选功能”,通过 capabilities(含 extensions)声明。新版里两者都是逐请求声明,没有集中式握手。

对方不支持我声明的扩展怎么办?
按规范,支持方必须回退到核心协议行为,或者用合适的错误拒绝请求。因此实现扩展时必须明确定义回退行为,否则对端会直接报错而不是降级。

新项目应该支持旧版协议吗?
如果不需要服务存量客户端,直接 pin 到 2026-07-28,可以省掉整套兼容分支、并享受无状态带来的水平扩展能力。需要照顾生态的现状,则实现 dual-era,用探测结果切换行为。

对比 MCP 传输方式 stdio 与 HTTP
这篇有帮助吗?
云上的幻象
云上的幻象查看主页

七彩云博客,分享 WordPress 建站实战与 AI 工具测评,覆盖服务器运维、站长工具、软件资源与电商运营干货,专注原创实用的主题插件、网站加速与安全优化教程。

942文章4评论

相关文章

评论 (0)

欢迎你,新朋友,感谢参与互动!文明发言,理性交流 · 首次评论将在审核后展示