MCP 2026-07-28 改成无状态核心:协议为什么在向 HTTP 靠拢
先问一个问题:如果你的 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 这类请求内通知,仍然只走它所属请求的那条响应流。
flowchart LR
C[客户端 Agent] --> G[网关 鉴权 限流 路由]
G --> S1[MCP Server 副本 A]
G --> S2[MCP Server 副本 B]
G --> S3[MCP Server 副本 C]
S1 --> D[外部状态存储 Redis 或 DB]
S2 --> D
S3 --> D
S1 --> T[链路追踪 采集]
S2 --> T
S3 --> T
G --> L[统一访问日志]
这张图想让你看见的是:网关和数据面之间多了一道"任意副本都能接住"的假设。旧模型里,客户端与副本 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,慢查询能直接定位到具体工具。
sequenceDiagram
autonumber
participant C as 客户端
participant S as 服务端
Note over C,S: 旧模型 有状态会话
C->>S: initialize 握手
S-->>C: 下发 Mcp-Session-Id 与能力
C->>S: tools/call 携带会话头
S-->>C: 结果依赖实例内存中的会话
Note over C,S: 新模型 无状态核心
C->>S: POST tools/call 带 _meta 版本与能力
S-->>C: result 带 serverInfo 与 ttlMs
C->>S: 再次调用 显式携带 basket_id
S-->>C: 任意副本均可处理
这张时序图想让你看见的是:旧模型里"能力协商"是一次性投资,付一次、用很久;新模型里它变成了"每次调用都要付的税"。省掉的是会话状态与粘滞路由,多出来的是每请求的元数据、可能的 server/discover 往返,以及客户端自己承担的幂等与重试。
四、一段真实报文,比十页文档清楚
下面这段取自官方 Streamable HTTP 传输文档的请求示例,我原样保留(只删了无关注释):
1 | POST /mcp |
这段报文在说明什么:同一个 /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),用日志统计"到底有多少调用真的读写了会话状态",通常这个比例远低于你的直觉。把真正依赖会话的那几个工具改造成句柄式,剩下的请求天然就是无状态的,迁移面会突然变小。
七、今天就能动手的五件事
- 在 MCP Server 入口加一列日志:
method+_meta里的协议版本 + 是否读写了进程内状态,先量化"会话依赖度"。 - 把服务里所有会话作用域的字典列出来(购物车、事务、浏览器实例、临时文件),逐个改造成
create_xxx返回句柄 + 显式过期策略。 - 网关侧先把
MCP-Protocol-Version、Mcp-Method、Mcp-Name做成透传 + 按工具名限流,这一步不依赖协议升级就能做。 - 给写操作工具加
idempotency_key,因为"断流后重发"在无状态模型里是常态而不是异常。 - 对照官方 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 原文。





