2026 年 7 月 28 日,MCP 发布了它诞生以来最大的一次规范改版。删掉了
initialize握手,删掉了Mcp-Session-Id,删掉了服务器主动发起的请求,废弃了 Roots、Sampling、Logging 三个特性。核心维护者 Nick Cooper 对这次改版的评价只有一句话:它”吸收了几十年 Web 协议设计的教训”(incorporates lessons from decades of web protocol design)。这篇文章要做的事,就是把这”几十年的教训”对应的论文找出来,一篇一篇对照着读。
先说结论。如果你只带走一句话,就带走这个:
状态不会消失,只会搬家。协议设计的根本问题从来不是”要不要状态”,而是”状态住在哪、跟谁共命运”。MCP 2026-07-28 的全部改动,本质上是同一个动作:把状态从传输层的暗处,搬到消息和端点的明处。而这个答案,1981、1988、2000 年的三篇论文各自独立地给过一次。
一、先看事实:删掉了什么,搬去了哪
这次改版(官方发布文、完整 changelog)动作很多,但每一刀都可以整理成”状态从 A 搬到 B”:
| 被删掉的(状态藏在暗处) | 替代品(状态搬到明处) | SEP |
|---|---|---|
initialize/initialized 握手,协商结果存在连接里 | 每个请求在 _meta 里自带协议版本和能力声明 | SEP-2575 |
Mcp-Session-Id,会话状态存在某台服务器内存里 | 服务器签发显式 handle,作为普通工具参数由客户端带回 | SEP-2567 |
服务器通过长连接反向发请求(elicitation/create 等) | MRTR:返回 input_required,客户端补上信息重发原请求 | SEP-2322 |
SSE 断流重续(Last-Event-ID),流的进度存在两端 | 删除。断了就丢,客户端用新 ID 重发请求 | SEP-2575 |
| 网关要解析 JSON body 才知道请求是什么 | Mcp-Method/Mcp-Name 提升为 HTTP 头 | SEP-2243 |
| 列表结果随连接不同而不同,无法缓存 | tools/list 等结果自带 ttlMs + cacheScope | SEP-2549 |
| 实验性 Tasks 藏在核心协议里 | 移出为显式扩展,改为轮询 tasks/get | SEP-2663 |
官方给的直接动机很工程化:无状态之后,“任何请求可以落在轮询负载均衡器后面的任何实例上”,不再需要 sticky session 和共享会话存储。这是真的,但只是表层。往下挖一层,你会发现这张表的每一行,都是一篇经典论文的论点在 2026 年重新上演。下面按时间顺序讲三篇。
二、1981:功能应该放在两端,而不是通信系统里
《End-to-End Arguments in System Design》(Saltzer, Reed & Clark,1981 年提出,1984 年刊于 ACM TOCS)大概是系统设计史上被引用最多的一篇原则性论文。它的核心论证一句话可以说完:
一个功能,只有站在通信两端的应用才有足够的知识把它完整、正确地实现;所以把它做进通信系统内部,注定是不完整的——最多只能作为性能优化存在。
论文的原始例子是文件传输的可靠性:就算网络层保证了每个数据包不丢不错,文件仍然可能在读盘时就坏了、在写盘时才坏——所以端到端的校验无论如何都省不掉,网络层的可靠性保证只是锦上添花。既然两端反正要做,通信系统里那份就别做成”必选项”。
现在把这个透镜对准 MCP 的会话。Mcp-Session-Id 是什么?是通信系统(传输层)替应用管理的状态。它有端到端争论预言的全部毛病:
- 它不完整。传输层不知道这个会话对应用意味着什么——哪些数据真正需要跨调用存活、存活多久、丢了怎么办。它只能一刀切地”全都存着”,于是每台服务器都背上了它其实不理解的负担。
- 它挡不住两端反正要做的事。真正需要跨调用状态的服务器(比如一个数据库查询工具要维持游标),最终还是要自己设计状态的生命周期。传输层的 session 没有免除这个工作,只是把它藏起来了。
SEP-2567 的替代方案,就是端到端争论的标准答案:需要跨调用状态的服务器,自己签发一个显式 handle,作为普通工具参数让客户端带回来。 状态的定义权、生命周期、失效语义,全部回到了唯一理解它的那一端——服务器应用自己手里。官方发布文里那句话说得很直白:显式 handle “比藏在传输层里的会话状态工作得更好”(works better than session state hidden in the transport)。
1981 年的论文管这叫”把功能放到端点”;2026 年的 MCP 管这叫 stateless core。同一个论证,四十五年。
三、1988:Fate-sharing——你的状态跟谁一起死?
第二篇是 David Clark 的 《The Design Philosophy of the DARPA Internet Protocols》(SIGCOMM 1988)。这篇论文回答的问题是:互联网为什么长成数据报(datagram)的样子,而不是像电话网那样的虚电路(X.25)?
Clark 给出的第一设计目标是生存性:部分网络设备损毁时,通信不能死。由此推出一个漂亮的原则,他称之为 fate-sharing(命运共享):
描述一段通信的状态信息,应该聚集在通信的端点上——只有当端点本身消失时,丢失这份状态才是可以接受的。
TCP 的连接状态存在两台主机里,而不是存在沿途的交换机里。所以中间路由器随便挂、随便换路径,连接照活——因为状态的”命运”只跟真正关心这段通信的两端绑在一起。X.25 走了反路:虚电路状态存在电话网的交换机里,交换机一挂,途经它的所有连接陪葬。历史已经宣判了这两种选择的胜负。
现在看 Mcp-Session-Id 的命运绑定关系:会话状态存在某一台特定的服务器实例的内存里。这台实例是什么?它既不是客户端(真正发起对话的端点),也不是”服务”这个抽象本身——它只是负载均衡器后面 N 个可互换副本中的一个,一个随时会被扩容、缩容、滚动更新、OOM 杀掉的进程。把状态的命运和一个设计上就随时会死的东西绑在一起,这正是 fate-sharing 原则点名反对的结构。 于是整个行业发明了 sticky session、共享 Redis 会话存储这一堆补丁,来对抗一个本可以不存在的问题。
这里有一个值得单独说的历史细节:MCP 出生时是有状态的,不是因为设计者没读过 Clark,而是因为它出生在另一个世界。 2024 年底的 MCP 是为本地场景设计的——一个客户端 spawn 一个服务器子进程,通过 stdio 通信。在那个世界里,“会话”是免费的:进程活着,会话就活着;进程死了,客户端也知道。stdio 天然满足 fate-sharing——状态和进程共命运,而进程就是端点。 问题出在协议原样搬进远程 HTTP 世界的那一刻:拓扑变了(端点后面变成了可互换的副本群),而状态模型没变。这和 X.25 的故事同构——电路交换的思维方式,搬进了数据报的世界。2026-07-28 不是修 bug,是补上这次”世界观迁移”欠下的账。
四、2000:REST——这次改版几乎是 Fielding 论文的逐字执行
第三篇最有名:Roy Fielding 的博士论文 《Architectural Styles and the Design of Network-based Software Architectures》(UC Irvine, 2000),REST 的出处。第五章给 REST 加的第二条约束就是无状态,值得原文引用:
“每个从客户端发往服务器的请求,必须包含理解该请求所需的全部信息,不能利用任何存储在服务器上的上下文。会话状态因此完整地保存在客户端。”
把这句话和 SEP-2575 并排放:MCP 请求现在在 _meta 里携带 protocolVersion、clientCapabilities、clientInfo——每个请求自带理解它所需的全部信息。这不是”精神上相似”,是逐字执行。
更有说服力的是,Fielding 连代价都算好了。他在同一节明确写道,无状态的交换条件是”重复数据带来的每次交互开销上升,会降低网络性能”——你把上下文从服务器内存里拿出来,就得在每个请求里重新背一遍。MCP 每个请求里那坨 _meta 就是这笔账。而 Fielding 给出的补偿手段,是紧接着的第三条约束:缓存——既然响应不再依赖服务器端隐藏状态,同样的请求就有了同样的答案,就可以被缓存。对照 MCP:SEP-2567 特意强调”列表端点不再随连接不同而不同”,SEP-2549 随即给 tools/list、resources/read 加上 ttlMs 和 cacheScope。约束和补偿是成对搬运的,顺序都没变。
这里还有一个 2026 年才会出现的新注脚,我觉得是整份 changelog 里最有时代感的一行小字:规范建议服务器以确定性的顺序返回工具列表,理由是”提高 LLM 的 prompt cache 命中率”。Fielding 在 2000 年论证缓存时,想的是浏览器和 CDN;同一条原理在 2026 年落到了一个他不可能预见的缓存层——大模型的 KV cache。可缓存性作为无状态的红利,跨越了两代完全不同的基础设施,原理一个字没改。
顺带一提,Web 自己其实也没守住 Fielding 的约束——Cookie 和服务器端 session 就是 Web 世界的 Mcp-Session-Id,sticky session 这个词本来就是那个生态发明的。所以说 MCP”吸收了 Web 的教训”很精确:吸收的既有 Web 做对的部分(HTTP 的无状态请求模型),也有 Web 做错之后花二十年才还清的部分。
五、两个精妙细节:状态推给对端,交互变成重试
改版里有两个机制,单看像小设计,放进上面的框架里看会发现它们是同一个思想的两次应用——服务器拒绝持有状态时,可以把状态托运给对端保管。
细节一:requestState 是应用层的 SYN cookie。 MRTR 模式下,服务器中途需要用户输入时返回 input_required,客户端补上答案后重发原请求。那服务器怎么把”我处理到哪了”跨越这两次请求?答案是把它编码进 requestState 字段,让客户端原样带回来。这个手法有个著名的先例:1996 年 D. J. Bernstein 的 SYN cookies。当年 SYN flood 攻击利用的正是”服务器在握手期间要为每个半开连接持有状态”这个弱点,Bernstein 的解法是服务器不存任何东西,把连接状态加密编码进 TCP 序列号,对端第三次握手时自然会把它带回来。三十年后,MCP 用同一招把多轮交互的中间状态从服务器内存里搬进了消息本身。
细节二:MRTR 把”双向通信”降维成了”单向重试”。 旧协议里服务器能主动向客户端发请求(elicitation/create、sampling/createMessage),这要求一条一直开着的双向流——而”一直开着的流”本身就是一份藏在传输层的状态,正是要消灭的对象。MRTR 的替代结构你其实每天都在用:HTTP 401 挑战-应答。服务器说”缺东西,缺的是这些”,客户端补齐了从头再来。整个交互的推进状态由谁持有?由请求本身持有。协议里没有任何一方需要”记住”对话进行到哪一步。
这两个细节合起来回答了一个自然的质疑:“无状态协议怎么可能支持多轮交互?“答案是:多轮交互需要的从来不是有状态的传输,而是能自我描述的消息。
六、代价与边界:无状态不是免费的,也不是普适的
按灰度原则,这一节讲这次改版付出了什么、以及什么时候你仍然应该要状态。
付出的代价是真实的:
- 每请求开销。
_meta里的版本、能力、身份信息每个请求重复一遍——Fielding 预言的那笔账,一分不少。 - 可靠性语义变糙了。SSE 断流重续被整个删除:流断了,正在进行的请求就丢了,客户端必须换个新 ID 重发。这实际上是把可靠性责任推回给端点(又是端到端争论),但也意味着长耗时工具面临新的压力——重发安全(幂等)从良好实践变成了近乎硬性的要求。
- 有些特性没能活着完成搬家。Roots、Sampling、Logging 被直接废弃(SEP-2577),官方建议分别用工具参数、直连 LLM 提供商 API、stderr/OpenTelemetry 替代。这三个特性的共同点是它们在结构上依赖服务器主动找客户端——无状态化不是无损重构,有些抽象和”持久双向连接”绑得太深,只能一起下葬。
什么时候状态仍然是对的:
- 本地 stdio 场景。一个进程对一个客户端,状态和进程 fate-sharing,无状态化在这里解决的问题根本不存在。规范也没有强迫 stdio 场景做任何牺牲——
server/discover在 stdio 上只是个兼容性探针。 - 真正的长时任务。需要跨请求存活几分钟到几小时的工作,正确答案不是”消灭状态”而是”把状态显式化”:Tasks 被移出核心、做成独立扩展,用服务器签发的 task handle + 客户端轮询
tasks/get。注意这个结构——状态存在,但它有名字、有 handle、有明确的属主和查询接口。被消灭的从来不是状态本身,而是隐式的状态。
所以边界可以画得很清楚:协议核心无状态,应用状态显式化。 这不是”状态有害”的极端立场,而是给状态立规矩:想存在,就得亮明身份。
七、带走的模型:设计任何协议前先问三个问题
把三篇论文压缩成一个可复用的检查清单。下次你设计任何跨网络的接口——不限于 MCP,一个内部 RPC、一个 webhook、一个 agent 之间的通信协议——对每一份你打算持有的状态,问三个问题:
- 谁真正理解这份状态?(End-to-End Arguments, 1981)如果答案是”应用两端”,就别把它做进中间层——中间层那份注定不完整,而两端那份反正省不掉。
- 它跟谁共命运?(Fate-sharing, 1988)如果它存活的地方(某个副本、某条连接、某个中间盒)会比关心它的端点先死,你就是在给自己预订 sticky session 和会话存储的账单。
- 消息能不能自含?(REST, 2000)愿不愿意付”每请求重复数据”的代价,换可见性、可缓存、任意副本可应答?大多数场景下这笔交易是赚的——而且缓存会把成本补回来一部分。
以及一句总结性的判断,它是这三个问题的公因子:
藏起来的状态是负债,显式的状态是资产。
Mcp-Session-Id和显式 handle 持有的可能是同一份数据,区别只在前者藏在传输层里没有名字,后者是一个有属主、有生命周期、有失效语义的一等公民。MCP 花了一年半、用一次伤筋动骨的大改版赎回这个区别——而论文里,答案从 1981 年起就一直摆在那里。
一手材料:MCP 2026-07-28 发布文 · 官方 changelog · SEP-2567(移除会话) · SEP-2322(MRTR)
论文:Saltzer, Reed & Clark, End-to-End Arguments in System Design (1981/1984) · Clark, The Design Philosophy of the DARPA Internet Protocols (SIGCOMM 1988) · Fielding, Architectural Styles and the Design of Network-based Software Architectures (2000) · Bernstein, SYN cookies (1996)