MCP 2026-07-28 规范删掉协议层会话:握手、Mcp-Session-Id 与 SSE 断流重投递一并移除。断流即丢请求,幂等与跨调用状态从此由应用和网关兜底。
远程 MCP 服务器过去靠粘性会话加一份共享的会话存储才能水平扩展。2026-07-28 版规范把这两样一起删了,基础设施确实变简单,但三笔账被转到了应用和网关头上。
具体来说:协议层不再持有会话,Mcp-Session-Id 标头取消,SSE 的断流重投递也一并移除。规范落地时行业侧已经有过一轮解读(见协议无状态化与插件标准合流、智能体开发基础设施正在收敛两篇)。这篇不重复新闻,只拆迁移期最容易出问题的三处工程细节。
把 2026-07-28 版与上一版 2025-11-25 逐条对照,差异几乎都集中在「谁持有状态」这一件事上。下面这张表整理自官方变更文档:
| 维度 | 2025-11-25 及更早(握手式) | 2026-07-28(无状态) |
|---|---|---|
| 会话 | 协议层会话 + Mcp-Session-Id 标头 | 协议层不持有,请求彼此独立 |
| 版本协商 | initialize 握手时一次谈定 | 每个请求在 _meta 声明版本,不匹配返回 UnsupportedProtocolVersionError |
| 能力声明 | 握手时交换 | 随每个请求携带,或先调 server/discover 探查 |
| 断流恢复 | SSE 支持 Last-Event-ID 重投递 | 已移除,断流等于丢请求 |
| 跨调用状态 | 由会话隐式承载 | 服务器自铸 handle,当普通工具参数显式传递 |
| 服务端主动请求 | roots / sampling / elicitation 反向调用 | MRTR:返回 input_required,客户端重试时带 inputResponses |
| 日志级别 | logging/setLevel | 逐请求经 _meta 的 logLevel 传递 |
把 initialize 握手、会话标头、SSE 事件 ID 三样东西抽掉之后,单个请求不再依赖「前面发生过什么」——这才是负载均衡器能直接按请求转发的真正原因。
迁移里最容易踩坑的是这一条。变更文档写得很直白:移除 SSE 的可恢复性与消息重投递之后,断掉的响应流会丢掉在途请求,客户端必须以新的请求 ID 重新发一次(SEP-2575)。
问题恰恰出在「新的请求 ID」上。常见做法是把幂等键从请求标识派生,或者干脆绑在会话上:会话层一没,键就失去了锚点;而重发必然换 ID,如果去重表还拿请求 ID 当主键,同一笔下单、同一条消息就会被执行两遍,去重逻辑形同虚设。
可迁移的经验只有一句:幂等键要由业务语义决定,与传输层解耦。订单号、工单号、支付流水号是稳定的,请求 ID 不是。
给工具调用立契约这件事,我们在给工具调用立规矩的工程拆解里写过一套可复用的做法:幂等声明应该和参数校验放在同一层,而不是散落在业务代码里。
旧版里,tools/list、resources/list、prompts/list 的结果会随连接变化,服务器顺手把一些中间状态藏在会话里。新规范把列表接口改成不再随连接变化,需要跨调用状态的服务器要自己铸造 handle,并把它当作普通工具参数传下去(SEP-2567)。
换一个角度看,这是一次「状态上桌」:谁铸造、存在哪、何时失效,从隐性约定变成必须写清楚的接口契约。对分布式部署是利好,代价是设计阶段就要多想一层。这个判断和我们此前讨论治理层为什么必须与模型解耦时是同一个结论——只要状态归属说不清,后续的授权、审计和成本都无从下手。
服务端到客户端的通知也走同一条思路。原来的 HTTP GET 端点与订阅接口被 subscriptions/listen 取代,改成一条长连接流:客户端按类型显式 opt-in(工具列表变化、提示模板变化、资源订阅等),服务端用订阅 ID 给每条通知打标。请求级的进度与消息通知仍走原请求自己的响应流,不和这条通道混在一起。
现在每个请求都要在 _meta 里声明自己使用的协议版本,Streamable HTTP 上还要带 MCP-Protocol-Version 标头,服务端逐请求接受或拒绝。版本不匹配时返回 UnsupportedProtocolVersionError 并列出自己支持的版本,客户端可以换一个双方都支持的版本重试。协议版本号本身用日期表示,只有在出现向后不兼容改动时才会递增,这使增量演进不必打断互通。
新增的 server/discover 是必须实现的 RPC,一次调用返回服务端支持的版本、能力和身份;客户端可选调用它,也可以把它当作 STDIO 上的向后兼容探针。对多租户网关来说,这个改动的实际含义是:版本路由的最小粒度从「连接」降到了「请求」,同一个入口就能按请求把流量分到不同版本的实例集群。
规范允许客户端与服务端同时支持多个版本,2025-11-25 及更早的握手式实现仍要能接上,细节见版本与兼容性说明。InfoQ 对这次变更的报道提到 AWS 侧的实践建议:在网关处追踪协议版本,并在旧版流量消失之前保留会话基础设施。规范本身也给弃用功能留出至少 12 个月(加速移除例外为 90 天)的迁移窗口。
所以更稳妥的顺序是:先给网关加版本识别与 server/discover,再把内部客户端逐个切到无状态模式,最后才拆会话存储。反过来做,等于在没有兜底的情况下先砍掉兼容层。
不是。变的是状态放在哪,不是状态存不存在。协议层不再替你持有,应用需要用显式 handle 自己管理,并且定义清楚生命周期。
不需要一次性切换。新旧协议版本可以并行,建议先让网关按版本分流,再逐步迁移承载实例。
把幂等键建立在业务语义上,重发时携带同一个键;不要用会话标识或请求 ID 去派生幂等键,它们在重发路径上都会变。
是。规范把它列为必须实现的 RPC。调用它是可选的——客户端可以直接发请求并处理版本错误——但服务端实现它是强制的。
改成逐请求在 _meta 里传 logLevel。没有携带这个字段的请求,服务端不得发出 message 通知。
如果你的团队正在把内部工具改造成远程 MCP 服务器,或者要把多个协议版本的流量收进同一个网关,可以带着现有的会话设计来做一次架构评审,看看哪些状态该显式化、哪些幂等约束还没补上——入口在蓝曜炬辉的案例页。