深拆 MCP 2026-07-28:握手与 Session 一起退场,状态去哪了

📅 2026/7/30 18:06:26
深拆 MCP 2026-07-28:握手与 Session 一起退场,状态去哪了
深拆 MCP 2026-07-28:握手与 Session 一起退场,状态去哪了引子:发布以来最大的一次改版2026 年 7 月 28 日——就在昨天——MCP(Model Context Protocol)2026-07-28 版规范正式转 final。两位 Lead Maintainer,David Soria Parra 与 Den Delimarsky,给这次发布的定语是“the largest revision of the protocol since launch”:自 2024 年 11 月协议诞生以来最大的一次修订。按官方口径,四个 Tier 1 SDK 的月下载量合计已接近 5 亿次,TypeScript 与 Python SDK 累计下载跨过 10 亿——这次最大修订动的不是一个小众协议,而是事实上的 agent 工具接入标准。一句话概括:MCP 从有状态的双向协议变成了无状态的请求/响应协议。拆开是四件同时发生的事:initialize/initialized握手删除,协议版本、客户端身份与能力改为每请求自描述(SEP-2575);Mcp-Session-Id头与协议级会话概念删除,跨调用状态改用服务端铸造、模型携带的显式 handle(SEP-2567);服务端反向发起的 Sampling / Elicitation / Roots 交互,改为内联在响应里的多轮往返(MRTR,SEP-2322);Roots、Sampling、Logging 三个特性进入弃用期(SEP-2577),旧版 HTTPSSE 传输一并列入弃用清单。结论先行:这不是修补,是交互模型级的重写。旧 Client 对新 Server、新 Client 对旧 Server 之间不存在透明兼容,只有显式的版本协商与回退。换来的东西也足够大——远程 MCP Server 从此可以像普通 HTTP 微服务一样,跑在普通轮询负载均衡器后面,吃普通的 HTTP 缓存,接普通的 OpenTelemetry。(文中事实与引文均以 2026-07-29 时点的官方规范、SEP 文本与官方博客为准,来源见文末。)一、走到这一步,用了五个版本版本关键内容2024-11-05初版:JSON-RPC 2.0,tools / resources / prompts 三原语,stdio 与 HTTPSSE 双传输2025-03-26OAuth 2.1 授权,Streamable HTTP 取代 HTTPSSE,工具注解,JSON-RPC batching2025-06-18结构化工具输出,elicitation,MCP Server 定位为 OAuth Resource Server,删除 batching2025-11-25OIDC Discovery,Client ID Metadata Documents,实验性 tasks,JSON Schema 2020-12 成默认方言2026-07-28无状态核心(删握手与 session),MRTR,扩展框架转正(MCP Apps、Tasks),授权强化,正式弃用政策三个细节值得注意。其一,无状态化不是突发决定。SEP-2575 开档于 2025-06-18——与当天发布的规范版本同日——从提案到落地走了 13 个月;今年 5 月 21 日 RC 锁定,给 SDK 维护者与客户端实现方留出 10 周验证窗口,7 月 28 日转正。其二,SDK 早把答案写在代码里了。TypeScript SDK 的sessionIdGenerator: undefined、Python SDK 的stateless_httpTrue——无状态模式在官方 SDK 里以可选项形态存在已久(官方口径:除 PHP 外的所有官方 SDK 均已提供)。这次规范做的,是把可选模式扶正为新版协议的唯一模式。其三,协议演进一直在给状态做减法:JSON-RPC batching 于 2025-06-18 被删,断线续传(resumability)始终没有好答案,session 语义两年间从未收敛。2026-07-28 是这条减法线的终点。二、initialize 之死:每个请求自带身份(SEP-2575)2.1 握手到底捆绑了什么旧版 MCP 规定,任何实质通信前必须完成三步握手,一次性协商三件事:协议版本、双方 capabilities、双方身份(clientInfo / serverInfo)。问题不在协商本身,而在协商的产物是一份双方都必须记住的会话状态,后续每个请求都隐式依赖它。这直接顶死了水平扩展:普通 L4/L7 轮询负载均衡器会把同一客户端的请求打到不同实例上,而状态只在其中一台;运维被迫上 sticky session,由此带来负载不均、故障恢复要整套重握手等连锁问题。SEP-2575 的动词选得很准:unbundle——把捆在一次握手里的三件事,拆成三个独立的无状态机制。2.2 每请求自描述版本与能力不再协商一次、缓存整个会话,而是随每个请求携带。_meta新增一组io.modelcontextprotocol命名空间字段:exportinterfaceRequestMetaObjectextendsMetaObject{progressToken?:ProgressToken;io.modelcontextprotocol/protocolVersion:string;// 必填io.modelcontextprotocol/clientInfo:Implementation;// 必填io.modelcontextprotocol/clientCapabilities:ClientCapabilities;// 必填io.modelcontextprotocol/logLevel?:LoggingLevel;// 可选}HTTP 传输上,MCP-Protocol-Version头强制存在且必须与_meta中的值一致,不一致直接 400。能力语义同步收紧:空 capabilities 对象就是什么可选能力都不支持,服务端MUST NOT从历史请求推断能力;缺必填字段按 INVALID_PARAMS 拒绝;处理请求需要客户端未声明的能力时,返回专用错误-32021(MissingRequiredClientCapabilityError),把所需能力清单还给客户端。2.3 版本协商内联化没有了握手,版本协商塌缩成一次普通的请求—报错—重试:客户端带首选版本直接发请求;服务端支持,正常处理;不支持,返回-32022(UnsupportedProtocolVersionError),data.supported列出服务端支持的版本;客户端挑一个双方交集里的版本重发。2.4 server/discover:发现,但不强制新增的server/discoverRPC 承接握手中能力发现的职能,返回四样东西:supportedVersions、capabilities、serverInfo、instructions(自然语言使用说明,客户端可注入 system prompt)。约束关系值得玩味:服务端 MUST 实现,客户端 MAY 调用——客户端完全可以一个招呼不打、直接tools/call。协议不再强制任何通信前仪式,发现从必经流程降格为可选优化。2.5 一并被删的东西被删项替代路径initialize/notifications/initialized每请求_metaserver/discoverlogging/setLevel每请求_meta的logLevel字段roots/list及其变更通知MRTR 按需拉取(见第四节)resources/subscribe/unsubscribesubscriptions/listen的参数化订阅ping(双向)任意 RPC 即活性证明;连接健康交给传输层Streamable HTTP 的 GET 通道subscriptions/listen(POST SSE)Last-Event-ID断线续流tasks 原语每条删除的理由同构:它们要么本身是状态,要么依赖状态。断线续流最典型——续流要求服务端跨连接保留每请求的事件缓冲,与无状态正面冲突,于是整体移除;需要持久性的长任务,规范指路 tasks(本次转正为io.modelcontextprotocol/tasks官方扩展,SEP-2663,轮询式tasks/get)。取消语义随之简化:HTTP 上关闭 SSE 响应流即视为取消,stdio 上发notifications/cancelled。三、Session 之死:状态上移为显式 handle(SEP-2567)3.1 Session 的原罪:作用域从未被定义SEP-2567 对 session 的清算,火力不在有状态不好,而在状态的作用域从未被定义过。规范没说 session 何时开始何时结束,于是每个宿主给出了不同答案:ChatGPT:每次工具调用一个新 session;Claude.ai:直到不久前同样如此;桌面与 IDE 客户端:应用启动建一个,进程存活期内复用;Web 客户端:一次页面加载一个;几乎没有客户端会在断连或重启后恢复旧 session——参考实现 TypeScript SDK 甚至没有公开 API 支持在另一节点重建 session。对 server 作者,这意味着没有可设计的对象。把浏览器实例绑到 session 上的 Playwright server,无从知道这个 session 意味着一轮对话、一个 agent 进程,还是一整个月的聊天窗口。session-scoped 状态在 per-tool-call 客户端下,下一次调用前就被销毁;在 per-app-launch 客户端下,被窗口里所有对话共享、再于重启时整体丢失。3.2 基数问题:一个 session 只有一份状态还有个更根本的表达力缺陷:session 状态的基数被钉死在每会话恰好一份。SEP-2567 的编排反例很有说服力——orchestrator 派多个子代理并行调研商品,期望它们共享同一个购物车(协作凑一个订单),但各自持有独立的浏览器状态(并行逛不同站点):session 方案购物车(要共享)浏览器(要隔离)子代理继承父 session✓ 共享✗ 互相踩踏子代理各开 session✗ 各自为政✓ 隔离session 边界怎么划都无解。显式 ID 下则很自然:orchestrator 调一次create_basket(),把basket_id分发给所有子代理;每个子代理各自create_browser()。状态的作用域由模型按需决定——要几份有几份,要共享就传递。多智能体编排场景里,这一条可能比负载均衡红利更重要。3.3 handle 模式:协议里根本没有 handle替代方案在 wire 上长这样:// → tools/call { name: create_basket, arguments: {} } // ← result { content: [{ type: text, text: Created basket bsk_a1b2c3 }], structuredContent: { basket_id: bsk_a1b2c3 } } // → tools/call { name: add_item, arguments: { basket_id: bsk_a1b2c3, sku: shoes } } // → tools/call { name: checkout, arguments: { basket_id: bsk_a1b2c3 } }最容易被误读、也最值得强调的一点:这不是协议新特性。协议里没有handles/*方法,schema 里没有 handle 类型,wire 层根本不存在 handle 概念——它只是工具结果里的一个普通字符串,和后续调用里的一个普通参数。规范做的是删除(session),不是新增(handle);handle 是被官方盖章的工具设计模式。盖章的底气来自现实——主流远程 MCP Server 早就这么设计了:Server创建工具 → 返回 ID消费该 ID 的工具Linearcreate_issue→ issue idget_issue、update_issue、create_commentNotionnotion-create-pages→ page idnotion-update-page、notion-move-pagesGitHubcreate_pull_request→ PR numberpull_request_read、merge_pull_requestStripecreate_customer→ customer idcreate_invoice、list_subscriptions规范只是把生态已经收敛出的事实标准,升格为唯一标准。SEP 同时给了非规范性的设计建议:handle 保持不透明(bsk_a1b2c3不诱导解析,cart_user42_2026-03-11会);创建时带参(create_context(clusterstaging)优于创建后再 set);过期返回可读错误;提供destroy_*与list_*工具供模型清理与找回。3.4 连带收紧:list 端点不许再变脸session 删除后,规范补了一条一致性约束:tools/list、resources/list、prompts/list的结果不得依赖连接状态,不得因同连接上其他请求的副作用而变化。此前合法的动态模式——调过connect_database()之后,query和list_tables才出现在工具列表里——被禁止;新写法是query常驻列表、接受connection_id参数,没有有效连接就返回一个引导模型先调connect_database()的错误。(按请求凭证返回不同工具集依然合法——那是每请求输入,不是连接状态。)回报是缓存。此前 client 无法确认某个 server 的列表是否 session 相关,只能每个新 session 重拉;对高频起子代理的宿主,这是热路径上O(子代理数 × server 数)的重复调用。列表与 session 解耦后,fan-out 降到O(server 数);配合本次新增的ttlMs/cacheScope缓存元数据(SEP-2549)与工具列表 SHOULD 确定性排序的要求,还顺带提升 LLM 侧的 prompt cache 命中率。3.5 破坏面:一份 1000 仓库的抽样session 删除是 clean break——新版本里没有弃用期。听起来激进,但 SEP-2567 附了对 1000 个开源 MCP Server 仓库的自动化抽样:类别占比迁移动作应用层完全不引用 session ID90.0%无MapsessionId, Transport路由(TS SDK 模板代码)3.5%换 sessionless 传输后自然消失仅传输配置(设了sessionIdGenerator,从未读过)2.8%删一个构造参数session 键控的应用状态2.5%迁移到显式 handle 或 auth principal代理/网关 sticky 路由0.7%需要重新设计认证产物绑定 session(PKCE verifier 等)0.5%换服务端 nonce 或 token subject九成仓库零迁移;真正要动架构的,是最后三行合计约 3.7%。3.6 安全模型:持有 ≠ 授权handle 会出现在 session ID 从不出现的地方:聊天记录、子代理 prompt、剪贴板。SEP-2567 的安全指引分两档:有认证的 server:handle 只当名字用,每次调用校验(handle, auth_context)二元组——与 Google Doc ID、GitHub PR 号同款模型:ID 标识资源,请求上的凭证决定访问权。做到这一点,handle 泄露无害。无认证的 server:handle 事实上就是 bearer token,必须以 ≥128 位密码学随机熵生成(UUIDv4,或 22 字符 URL-safe base64)、不可从可预测输入推导、有界生命周期——与知道链接即可访问的分享 URL 同款纪律。客户端侧的新责任只有一条,但对 agent 工程很致命:上下文压缩时别把 handle 压没了。handle 活在对话转录里,摘要若把它裁掉,那份状态就成了孤儿。四、反向通道之死:MRTR 与 subscriptions/listen旧协议里,server 是可以反过来向 client 发请求的:sampling/createMessage(借模型)、elicitation/create(要用户输入)、roots/list(要工作目录)。实现上依赖挂起的 SSE 流加会话状态——这是无状态化绕不过去的一块。2026-07-28 的答案是MRTR(Multi Round-Trip Requests,SEP-2322):反向请求不再是独立请求,而是原请求的一种返回值。server 处理tools/call到一半需要用户输入时,返回resultType: input_required并附上它需要的输入请求(内嵌于 IncompleteResult);client 收集齐答案后,带着inputResponses重试原请求。配合 SEP-2260 的约束——服务端发起的交互只能内嵌在活跃的客户端调用作用域内——server 从此失去了凭空发起请求的能力。顺带一提:sampling 与 roots 本身又在弃用期(见下节),长期真正依赖 MRTR 的是 elicitation。通知类交互统一收进subscriptions/listen:一个 POST 请求换一条 SSE 流,client 逐项 opt-in 想要的通知类型(工具/资源/提示词列表变更、指定资源更新——资源订阅从独立 RPC 降格为该请求的一个参数),首条消息必须是服务端的 acknowledged 确认,多条订阅可并存、按请求 ID 解复用。注意,挂起的流依然存在——无状态化消灭的不是长连接,而是长连接背后隐式共享的会话状态:流的生命周期被限制在单个请求内,可选、可丢、断了重开即可。五、弃用期:Roots、Sampling、Logging(SEP-2577)特性弃用理由(SEP 口径)官方指路的替代Roots客户端采纳率低;语义弱——规范定位仅是 “informational guidance”,服务端可以不理会工具参数、资源 URI、服务端配置、环境变量Sampling实现负担重(人工审批、模型选择、安全,SEP-1577 之后还要支持 tool loop);自 2024-11 可用至今采纳寥寥服务端直连 LLM 提供商 API,自主控制模型、参数与流式Logging与成熟设施重复stdio 用 stderr;结构化观测用 OpenTelemetry弃用政策本身也是本次的新事物:Active → Deprecated → Removed三段生命周期。弃用起 12 个月内发布的所有规范版本保持全功能,每个这样的版本再各自维持一年支持,之后的版本才可移除;规范同时要求新实现 SHOULD NOT 再采纳这些特性,协商到弃用能力时应发出警告。对照 session 的 clean break,同一个版本里存在两种退场节奏:语义从未收敛、九成实现不依赖的(session)直接删;确有存量用户的(三特性)给一年以上缓冲。一处表面拧巴、实则自洽的设计:SEP-2575 一边把logging/setLevel收编成每请求_meta的logLevel字段,SEP-2577 一边宣判整个 Logging 特性弃用。正确读法是:迁移期内还能用,且用得更无状态;终点是 OTel。Sampling 的弃用另有一层安全含义,SEP 原文说得直白:sampling 允许 server 借道 client 请求 LLM 补全,是 prompt 注入与数据外带的攻击面,删除即降险。此外,2025-03-26 起就被 Streamable HTTP 取代、但一直保留兼容的旧版 HTTPSSE 传输,本次一并列入弃用清单。六、基础设施视角:这次改版真正的受益者RC 公告里有一句概括,值得原文引用:a remote MCP server that previously needed sticky sessions, a shared session store, and deep packet inspection at the gateway can now run behind a plain round-robin load balancer.一个此前需要粘性会话、共享会话存储、网关深包检测的远程 MCP Server,现在可以跑在普通轮询负载均衡器后面。拆成四个维度:负载均衡与弹性。任意副本可服务任意请求,sticky session 与共享 session store 整体出局;无状态工作池天然适配自动扩缩与 serverless。前述抽样里 0.7% 的 sticky 网关,SEP 给出的方向是把上游改造为无状态(状态入共享存储、以 handle 为键),而不是在网关层重造会话亲和。网关路由。SEP-2243 强制 Streamable HTTP POST 携带Mcp-Method与Mcp-Name头,网关、WAF 不解析 JSON body 即可完成路由、计量与限流;工具参数还可经x-mcp-header映射为自定义头。MCP 流量第一次对普通 L7 设施完全可见。缓存。SEP-2549 给tools/list、prompts/list、resources/list、resources/read四个读端点加上ttlMs与cacheScope,HTTP 语义缓存的整套设施可以直接复用;确定性工具排序进一步喂饱 LLM 端的 prompt cache。可观测。SEP-414 在_meta上规范了 W3C Trace Context 的传播约定(traceparent/tracestate/baggage),host → client → server 的调用链第一次能在标准 APM 里贯通。Logging 特性的弃用与这条是同一件事的两面:自建日志通道退场,OTel 补位。汇总成一句:MCP Server 的部署形态,从需要特殊对待的有状态服务回归普通 HTTP 微服务——过去十几年云原生生态在 LB、缓存、追踪上的全部积累即插即用。七、兼容性与迁移:不要假设透明兼容7.1 版本协商矩阵新版是 breaking change,靠协议版本号隔离。SEP-2575 给出的混布行为:旧 client × 双栈 server:server 可保留initialize应答,旧 client 无感——官方 v2 SDK 服务端就是这么实现的,同时应答initialize与server/discover;双栈 client × 旧 server(HTTP):client 直接按新版发请求,收到 400 / 版本不支持后回退旧版、补握手;双栈 client × 旧 server(stdio):没有 400 可依赖,规范建议先探测server/discover,收到-32601(Method not found)或版本错误再回退握手;纯新版 client × 旧 server:明确失败,没有兼容路径。所以不能假设透明兼容的准确展开是:兼容存在,但它是显式协商加回退的产物,不是默认赠品;任一侧收窄到只支持新版,另一侧必须跟进。依赖 session 状态的 server,官方建议留在旧协议版本,迁完再切。7.2 SDK 落位SDK版本关键变化Pythonmcp2.0.0b1FastMCP更名MCPServer;v1 保留关键修复与安全补丁TypeScriptv2 beta单包拆为modelcontextprotocol/server与/client;schema 层转 Standard Schema(Zod / Valibot / ArkType);ESM-only、Node 20;官方 codemod:npx modelcontextprotocol/codemodbeta v1-to-v2;v1 保六个月修复Gov1.7.0-pre.1无破坏性 API 变化,协议支持为增量C#2.0.0-preview.1弃用能力标注[Obsolete]另有 Rust SDK 以 beta 支持新规范。零散但会咬人的变化:资源缺失错误码从 MCP 自定义-32002改为标准 JSON-RPC-32602;授权侧 RFC 9207iss校验落地(SEP-2468)、动态客户端注册(DCR)正式弃用、转向 CIMD。给库作者的官方提醒:给mcp依赖加上界(如mcp1.27,2),别让 v2 stable 直接砸到下游。7.3 服务端迁移清单grep -r Mcp-Session-Id\|sessionIdGenerator\|stateless_http定位状态依赖——九成项目到此结束;session 键控的应用状态 →create_*工具铸 handle、各工具加 handle 参数、工具描述写清生命周期(“basket 闲置 24h 过期”)、补destroy_*/list_*;副作用式动态工具列表 → 工具常驻 handle 参数,未就绪时返回引导性错误;依赖 sampling → 直连模型 API;依赖 logging → stderr / OTel;依赖 roots → 工具参数或配置;网关 sticky 路由 → 摘除;session 键控的遥测与限流 → 换 auth principal 或请求级 correlation ID;鉴权自查:不再有握手期可以依赖,每个请求必须独立完成认证与授权。八、三个判断回到标题的问题:状态去哪了?判断一:状态没有消失,而是从协议层搬进了对话上下文,携带者从传输层换成了模型。handle 躺在工具结果里,聊天记录持久化了它,状态引用就随之持久化——换设备、重开会话、跨 agent 传递,天然免费。MCP 把会话恢复这个协议层老大难,外包给了聊天产品早已解决的历史同步。代价同样清晰:模型成了状态的单点,上下文压缩裁掉 handle,状态即孤儿。未来值得盯一个方向:SEP-2567 明确把用 schema 注解标记某字段是 handle留作后续提案——届时编排器就能识别哪些值是活状态,压缩、交接、清理都可以程序化。判断二:这是一次向 HTTP 哲学的回归,而且路径耐人寻味。HTTP 生而无状态,业界用 Cookie 在其上长出会话;MCP 生而有状态,不到两年把会话拆掉,让状态回到应用语义里。“pay as you go” 是 SEP-2575 的高频词:默认路径零状态、零仪式,长连接与任务持久性这些复杂度,谁需要谁显式购买。判断三:协议在收缩自己的野心。Sampling 弃用的潜台词是承认:server 需要模型时应该直连模型服务商,而不是借协议反向借道 client。连同 Roots、Logging 的退场,MCP 正在收敛回它最初的自我定位——把工具、资源、提示词接入模型的标准接线,而不是一个什么都管的 agent 运行时。瘦协议、厚生态,这是标准能活得久的经典形态。对写 server 的人,今天要做的第一件事不是重写,而是跑一遍 7.3 节那条 grep:九成概率你什么都不用改;剩下一成,第七节就是路线图。参考The 2026-07-28 Specification — MCP 官方博客(发布公告、弃用清单、SDK 与下载量数据)The 2026-07-28 MCP Specification Release Candidate(RC 时间线、五大板块、维护者引文)SEP-2575: Make MCP Stateless(删除握手、每请求_meta、server/discover、subscriptions/listen、被删 RPC 全表)SEP-2567: Sessionless MCP via Explicit State Handles(session 问题分析、handle 模式、1000 仓库抽样、安全指引)SEP-2577: Deprecate Roots, Sampling, and Logging(弃用理由、三段生命周期、12 个月窗口)MCP 规范 Key Changes(draft changelog)(SEP-2243 / SEP-2549 / SEP-414 等条目)Beta SDKs for the 2026-07-28 MCP Spec Release Candidate(四大 SDK 迁移示例与版本策略)SEP 原始 PR:#2575、#2567、#2322(MRTR)、#2577第三方分析:MCP Specification Version Timeline(版本史)、SEP-2567 deletes Mcp-Session-Id, and sticky routing with it — LatentEval(基础设施影响)、MCP Just Went Stateless — Microsoft Apps on Azure Blog(App Service 扩缩视角)