先问一个问题:如果你的 MCP Server 明天必须挂到公司网关后面、跑 20 个副本、由 K8s 随机调度、每次发布都滚动重启——你今天这份代码还能活吗?

2025 年那份规范的回答是:不能。2026-07-28 这一版的回答是:所以我改。

这篇文章只做三件事:把 2026-07-28 这一版相对 2025-11-25 的真实改动核清楚;解释"无状态核心"到底把哪些东西搬了家;再给出我的判断——什么团队现在就该跟进,什么场景继续用旧版反而更省。

一、先说清楚这一版改了什么

我不打算凭印象写。判断依据是官方 changelog 与它引用的 SEP 原文。这一版的主线其实只有三条,其余都是配套。

  • SEP-2575 Make MCP Stateless:删除 initialize / notifications/initialized 握手,把协议版本与能力协商改成"每个请求自带"。SEP 状态 Final,创建于 2025-06-18。
  • SEP-2567 Sessionless MCP via Explicit State Handles:删除会话概念与 Mcp-Session-Id 头,跨调用状态改为服务端自签的显式句柄,作为普通工具参数由模型传递。创建于 2026-03-11,与 SEP-2575 互为补集。
  • SEP-2322 Multi Round-Trip Requests:从"服务端反向发起 sampling/createMessage、roots/list、elicitation/create"改成"结果里携带问题、客户端重试原请求"。所有结果新增必填 resultType。

其余配套改动,我认为同样值得记住,因为它们直接决定了迁移成本:

变更点 旧 2025-11-25 新 2026-07-28 SEP 或 PR
握手 三次握手建立会话 无握手,每请求 _meta 带版本与能力 SEP-2575
会话标识 Mcp-Session-Id 头 取消,改用业务句柄参数 SEP-2567
服务端主动请求 连接内反向请求 结果 resultType: input_required + 重试 SEP-2322
服务端推送通道 HTTP GET 流 + resources/subscribe 单条长 POST 流 subscriptions/listen SEP-2575
断线续传 SSE Last-Event-ID 重投递 取消,断流即丢请求,客户端重新发起 SEP-2575
列表结果缓存 无字段 ttlMs + cacheScope 必填 SEP-2549
网关识别 需解析 body Mcp-Method / Mcp-Name 请求头 SEP-2243
链路追踪 无约定 _meta 内 traceparent / baggage SEP-414
后台任务 核心协议内 tasks/* 移出为 io.modelcontextprotocol/tasks 扩展 SEP-2663

还有一条容易被忽略但影响长期的:这一版同时确立了特性生命周期与弃用策略,弃用窗口不短于 12 个月,并把 Roots、Sampling、Logging 正式标记为废弃(SEP-2577、SEP-2596)。也就是说,这三个你熟悉的特性不是不能用,而是"新实现不该再加支持"。

二、无状态化意味着什么:四个落点搬家

"无状态"这三个字很抽象,落到代码上其实只有四个地方换了位置。

连接:从"先握手再干活"变成"每次请求自带上下文"。没有 initialize,也就没有"连接建立中"这个状态机。

会话 ID:以前 Mcp-Session-Id 是隐式的状态容器,购物车、浏览器实例、数据库事务都挂在它上面。SEP-2567 说得很直白:会话在生态里从未收敛出统一语义——有人按工具调用划分,有人按应用启动划分,几乎没人真的去恢复它,所以列表端点无法跨会话缓存。新做法是 create_basket() 返回 basket_id,后续 add_item(basket_id, ...) 自己带。

能力协商:以前一次握手定终身,现在每个请求的 params._meta 里带 io.modelcontextprotocol/protocolVersion、clientCapabilities,客户端宜带 clientInfo,服务端宜在结果 _meta 里回 serverInfo。版本不匹配返回 UnsupportedProtocolVersionError(错误码 -32022)。另外新增 server/discover,服务端必须实现,用来声明自己支持的版本、能力与身份——它是"提前选版本"和"stdio 上探测旧服务端"的双重入口。

流式传输:这是改动最狠的一块。HTTP GET 端点没了,resources/subscribe 也没了,取而代之的是 subscriptions/listen:一条长 POST 响应流,客户端显式声明订阅类型(toolsListChanged、promptsListChanged、resourcesListChanged、resourceSubscriptions),服务端确认后给每条通知打上 subscriptionId。而 notifications/progress、notifications/message 这类请求内通知,仍然只走它所属请求的那条响应流。

这张图想让你看见的是:网关和数据面之间多了一道"任意副本都能接住"的假设。旧模型里,客户端与副本 A 绑死,网关必须做会话粘滞;新模型里,请求只要自带版本、能力和业务句柄,副本是谁都无所谓,状态被推到外部存储,链路追踪也从"一条长连接里混着 N 个请求"变成"一次请求一条 trace"。

三、为什么向 HTTP 靠拢:四件在长连接里做不好的事

我不认为这是设计者偏爱 HTTP。更准确地说,是企业基础设施里的四件事,在有状态长连接上都很难做。

网关。网关的世界观是"无状态请求进、无状态响应出",它靠路径、方法、头做路由和限流。会话模型下,你只能上会话粘滞或一致性哈希,副本负载不均、扩缩容时连接被打断,全是运维事故。SEP-2243 要求 POST 必带 Mcp-Method 与 Mcp-Name,本质是把"这次调用是什么"从 JSON body 提升到 HTTP 头——网关不用解析 body 就能按工具名限流、审计、熔断。

鉴权。会话 ID 不是身份凭证,它只是"我之前来过"。企业里要做的是每个请求校验 OAuth token、按 scope 授权、按租户隔离,并在中间件里统一拒绝。无状态后 MCP 才真正像一支普通 API 那样接入现有鉴权体系,这一版还顺手把动态客户端注册往 Client ID Metadata Documents 方向推。

水平扩容。状态在进程内存里,就意味副本不可随意杀;状态挪到 Redis 或数据库,副本才是"一次性"的。这一点对 Serverless 和弹性伸缩几乎是硬门槛。

可观测性。以前一条长连接上跑着几十个请求,日志要靠会话 ID 拼,采样器也分不清一次工具调用从哪到哪。现在 _meta 里带 traceparent、tracestate、baggage,一次调用天然就是一条 trace,慢查询能直接定位到具体工具。

这张时序图想让你看见的是:旧模型里"能力协商"是一次性投资,付一次、用很久;新模型里它变成了"每次调用都要付的税"。省掉的是会话状态与粘滞路由,多出来的是每请求的元数据、可能的 server/discover 往返,以及客户端自己承担的幂等与重试。

四、一段真实报文,比十页文档清楚

下面这段取自官方 Streamable HTTP 传输文档的请求示例,我原样保留(只删了无关注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather

{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "Seattle, WA"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}

这段报文在说明什么:同一个 /mcp 端点,同时服务 2025-11-25 和 2026-07-28 两代客户端,区分靠的不是连接状态,而是请求里的 MCP-Protocol-Version 头和 _meta 里的版本字段。三条原本属于"会话"的信息——协议版本、客户端身份、客户端能力——现在全部落在这一个请求体里。而 Mcp-Method、Mcp-Name 让网关在不读 body 的情况下就知道"这次要调用 get_weather",可以直接按工具维度做配额。注意 clientCapabilities 是空对象也不影响调用,这正是无状态化的收益:缺什么能力就在这次请求里说清楚,服务端当场拒绝(MissingRequiredClientCapability,错误码 -32021),而不是等到某个后续调用才炸。

五、我的判断:谁该跟进,谁可以继续等

我的判断是:判断标准不是"你是不是 AI 团队",而是"你的 MCP Server 有没有第二个副本、有没有第三个调用方(尤其是浏览器或第三方)"。

场景 建议 理由
面向外部开放、有网关与多租户的 MCP 平台 尽快跟进 粘滞会话与统一鉴权天然冲突,越晚改成本越高
多副本部署的工具服务(搜索、数据库、内部 API) 优先跟进 无状态化后可以直接上普通负载均衡与滚动发布
单个长任务型服务(构建、爬取、批处理) 谨慎跟进 取消断线重投递后,可靠性要自己做,收益与风险并存
本机 stdio 个人工具(IDE 插件、CLI) 可以继续用旧版 没有网关、没有扩容,握手成本反而是净损失
强会话语义的交互式工具(浏览器自动化、REPL) 先改造工具设计再跟进 句柄即状态,模型得学会传 session_handle,这不是协议能替你解决的

我的理解是,这一版真正卖的是一句话:协议不替你保管状态。它把"状态"从协议层赶到了应用层,同时把应用层的自由度也还给了你——你可以用 Redis、可以用数据库事务、也可以干脆什么都不存。代价是,那些原本靠会话"白嫖"的东西(列表结果、订阅生命周期、断点续传)现在都要你自己显式建模。

六、我踩过的坑,和我不太同意的地方

我踩过的坑:最容易犯的错是把 handle 当成"新名字的 session"。句柄在协议里没有生命周期语义——它只是服务端签发的字符串,什么时候过期、谁负责清理、能不能跨租户复用,全是你自己的事。如果照旧用一个全局 dict 存句柄,内存泄漏会原封不动地回来,只是换了个字段名。更隐蔽的问题是:句柄要作为工具参数进模型上下文,basket_id 这类短 ID 无所谓,但如果你把一坨 base64 编码的会话快照塞进去,token 成本和提示注入面都会上升。这一点协议没有给答案,属于工具设计者的取舍。

这里我不太同意:取消 SSE Last-Event-ID 与消息重投递,把"流断了怎么办"完全推给应用层。规范的说法很干脆——响应流断了,这次请求就丢了,客户端必须用新的 request id 重新发起。对只读查询这没问题;但对一个跑了十分钟的写入型工具,这是真实风险:断流意味着你可能既不知道它成没成功,又要再发一次。所以我的结论是,写操作工具必须自带幂等键,这不该是可选项。

另一处我保留意见的是 subscriptions/listen 的落地形态。它把推送收敛成"一条长 POST 响应流",语义上比 GET 流清晰得多,但它终究还是一条长连接,仍然要过企业网关的空闲超时。很多网关默认 60 秒无数据就断,而列表变更通知可能几分钟才来一条。这条在规范层面无法解决,只能靠部署侧调参或加心跳——这也是我认为本版最难落地的一点。

我的判断(关于迁移节奏):不要一次性切。先让服务端同时支持两代(规范里叫 dual-era),用日志统计"到底有多少调用真的读写了会话状态",通常这个比例远低于你的直觉。把真正依赖会话的那几个工具改造成句柄式,剩下的请求天然就是无状态的,迁移面会突然变小。

七、今天就能动手的五件事

  1. 在 MCP Server 入口加一列日志:method + _meta 里的协议版本 + 是否读写了进程内状态,先量化"会话依赖度"。
  2. 把服务里所有会话作用域的字典列出来(购物车、事务、浏览器实例、临时文件),逐个改造成 create_xxx 返回句柄 + 显式过期策略。
  3. 网关侧先把 MCP-Protocol-Version、Mcp-Method、Mcp-Name 做成透传 + 按工具名限流,这一步不依赖协议升级就能做。
  4. 给写操作工具加 idempotency_key,因为"断流后重发"在无状态模型里是常态而不是异常。
  5. 对照官方 changelog 逐条确认你的 SDK 是否支持 dual-era;在下结论前先看 SDK 自己的 release notes,不要只看博客。

八、待核清单

写技术文章最怕把"我记得"当"我知道",以下三条我明确没核实完,先标出来:

  • 各官方 SDK 的具体落地版本:我只核了规范文本与 SEP 状态,没有逐个核对 TypeScript、Python、Java、C#、Go SDK 的 release notes,因此"某个 SDK 从哪个版本开始支持 2026-07-28"待核。
  • 这一版是否有独立的发布公告与确切发布日期:我核到的是以日期命名的 revision 2026-07-28 及其 changelog 与 schema 路径,未核到独立公告文,故不写"某月某日发布"。
  • 主流云厂商托管 MCP 网关对 subscriptions/listen 的支持情况:未核,任何"已经支持"的说法都需要落到具体产品文档。

事实来源:modelcontextprotocol.io 的 2026-07-28 changelog、versioning 页、Streamable HTTP 传输页,以及 SEP-2575、SEP-2567 原文。