上周五下午我在例行更新依赖的时候顺手把 MCP SDK 升了个小版本。然后整个 agent 就挂了。不是报错——是静默失败。工具列表返回空agent 以为自己没有工具可用所有 task 全部跳过。我盯着日志看了十分钟才反应过来SDK 里那个initialize握手被删了。这不是 bug。这是 MCP 协议 2026-07-28 版本的 breaking change。如果你还没关注 MCP 这次的更新节奏现在知道还不晚。2026 年 5 月 21 日Anthropic 发布了 MCP2026-07-28的 Release Candidate正式版 7 月 28 日上线。这可能是 MCP 自 2024 年 11 月诞生以来最大的一次协议重构——核心从 stateful 改成 stateless三个核心特性直接被标记 deprecated授权体系也重新做了加固。说人话就是你的 MCP 客户端和服务端代码大概率要改。但这篇文章不是给你翻译 spec 文档的。网上的公告已经够多了。我想做的是另一件事把这些 SEPSpecification Enhancement Proposal拆开告诉你哪些 change 会导致你的代码悄悄炸掉哪些只是纸面上看着吓人、实际上不用管。MCP 为什么要去状态化先理解一下背景——不然你可能会问好好的 session 为什么删掉。MCP 当前的协议2025-11-25版本是这样的工作流程sequenceDiagramparticipantClientasMCPClientparticipantLBasLoadBalancerparticipantS1asServerInstance1participantS2asServerInstance2participantStoreasSessionStoreClient-LB:POST/mcp(initialize)LB-S1:RoutetoInstance1S1--Client:Mcp-Session-Id:abc123NoteoverS1,Store:SessionstatestoredinInstance1Client-LB:POST/mcp(tools/call)NoteoverLB:Muststicky-routetoInstance1LB-S1:Route(sessionaffinity)S1-Store:Loadsessionabc123S1--Client:ToolresultClient-LB:POST/mcp(tools/call)LB-S2:❌Wronginstance!S2--Client:Error:unknownsession问题在哪Session 把客户端粘在了一个特定的服务端实例上。你想做水平扩展要么搞 sticky session要么搭一个共享的 session store要么就直接放弃多实例部署。这在 2024 年底 MCP 刚出来的时候不是大问题——大家都是单机跑着玩。但到 2026 年MCP 已经有超过 10,000 个活跃公共服务器、每月 9,700 万次 SDK 下载。企业级部署的需求逼着协议必须改。2026-07-28的解决方案很彻底把协议层的 session 直接删掉。sequenceDiagramparticipantClientasMCPClientparticipantLBasLoadBalancerparticipantS1asServerInstance1participantS2asServerInstance2Client-LB:POST/mcpbr/Mcp-Method:tools/callbr/Mcp-Name:searchLB-S1:AnyinstanceS1--Client:ToolresultClient-LB:POST/mcpbr/Mcp-Method:tools/callbr/Mcp-Name:searchLB-S2:AnyinstanceS2--Client:ToolresultNoteoverClient,S2:Everyrequestisself-contained每个请求自包含。不再需要initialize握手不再需要Mcp-Session-Idheader 粘滞路由。任何一个服务端实例都能处理任何一个请求。听起来很美。但对已经写了代码的人来说这意味着——坑 1initialize 握手没了你的客户端初始化代码会报错这是影响面最大的 breaking change。旧代码2025-11-25# 旧版 MCP 客户端 —— 这段代码在 2026-07-28 后会炸frommcpimportClientclientClient(http://localhost:8000/mcp)# ❌ 2026-07-28 中 initialize 方法已被移除responseclient.initialize(protocol_version2025-11-25,client_info{name:my-agent,version:1.0})session_idresponse.session_id# ❌ Mcp-Session-Id 不再返回# 后续调用需要携带 session_idresultclient.call_tool(search,{q:hello},session_idsession_id)新代码2026-07-28# 新版 MCP 客户端 —— 无状态模式frommcpimportClientclientClient(http://localhost:8000/mcp)# ✅ 不再需要 initialize直接调用# 协议版本和客户端信息通过 _meta 字段在每个请求中传递resultclient.call_tool(search,{q:hello},meta{io.modelcontextprotocol/protocolVersion:2026-07-28,io.modelcontextprotocol/clientInfo:{name:my-agent,version:1.0}})影响判断如果你用的是官方 SDKPython/TypeScript并且依赖了initialize()方法或session_id属性——必炸。如果你只是调用了tools/list和tools/call这种高层 API 且 SDK 版本已经更新到支持2026-07-28SDK 内部可能帮你做了兼容。但别赌建议直接检查。一个让你更头疼的细节协议版本号从2025-11-25变成了2026-07-28。如果你的代码里有硬编码的版本号字符串也记得改。这玩意儿藏在很多人的配置文件里。坑 2你的 SSE 长连接会断——改成轮询了这是第二个容易被忽略的 breaking change。如果你用 Streamable HTTP也就是 SSE来接收服务端推送你的代码逻辑需要改。旧逻辑2025-11-25服务端通过 SSE 推送通知比如资源变更客户端保持一个长期打开的 SSE 连接来接收。典型场景是资源列表变了服务端主动推一条notifications/resources/updated。新逻辑2026-07-28SSE 长连接不再用于推送通知。服务端只能在处理客户端请求期间发起 server-to-client 请求且通过Multi Round-Trip Requests机制服务端返回InputRequiredResult客户端收集答案后重新发起请求。// 服务端不再保持 SSE 连接推送通知// 而是返回一个需要客户端继续的响应{resultType:inputRequired,inputRequests:{confirm:{type:elicitation,message:确认删除 3 个文件,schema:{type:boolean}}},requestState:eyJzdGVwIjoxLCJmaWxlcyI6WyJhIiwiYiIsImMiXX0}影响判断如果你依赖了 SSE 推送来做实时通知比如工具列表变更后自动刷新这个功能没了。替代方案是使用ttlMs 客户端轮询tools/list或者用cacheScope来控制缓存策略。坑 3Roots、Sampling、Logging 被标记废弃——但不是立刻不能用这是三件看起来吓人但其实不紧急的事。Feature替代方案紧急程度Roots用 Tool 参数、Resource URI 或服务端配置替代 低——至少 12 个月后才移除Sampling直接对接 LLM Provider APIOpenAI/Anthropic 低——同上Loggingstdio 传输用stderr结构化可观测性用 OpenTelemetry 低——同上这三个只是annotation-only deprecation——也就是说在2026-07-28版本中它们仍然可以正常使用。按照新的 Feature Lifecycle Policy从 Deprecated 到 Removed 至少需要 12 个月。影响判断如果你现在用着这三个特性不用急着改。但新项目就别用了。如果你在用 Sampling让 MCP 服务端调用 LLM建议趁早迁移到直接调 API。坑 4Tasks API 彻底重写了这是最坑的一个。Tasks 在2025-11-25里是实验性的核心功能但在2026-07-28中变成了一个ExtensionsAPI 也完全重写了。如果你在生产环境用了 Tasks——对不起必须迁移。旧 Tasks API实验版# ❌ 旧版 Tasks —— 2026-07-28 中不再可用taskclient.create_task(long_running_job,params{...})task_idtask.id# 轮询完成whileTrue:statusclient.get_task(task_id)ifstatus.statecompleted:break新 Tasks Extension# ✅ 新版 Tasks —— 通过 Extension 机制# 客户端声明支持 tasks extension# 服务端决定是否将调用转为异步 taskresultclient.call_tool(long_running_job,params{...},extensions[io.modelcontextprotocol/tasks])ifresult.task_handle:# 服务端决定这是异步任务handleresult.task_handle# 通过 tasks/get、tasks/update、tasks/cancel 管理statusclient.tasks_get(handle)关键变化-tasks/list被删除了——因为 stateless 架构下没法安全地做 scope-tasks/create不再由客户端主动创建——由服务端决定一个调用是否应该变成异步- Task 生命周期完全重写影响判断如果你用 Tasks 做过任何生产部署这次升级是必须重写的。好消息是 Tasks 现在作为 Extension 独立演进以后版本不会随便 break 它了。坑 5JSON Schema 升级$ref可能引入新问题2026-07-28把工具定义的inputSchema和outputSchema从受限的 JSON Schema 提升到了完整的 JSON Schema 2020-12。这意味着你现在可以用这些以前不支持的特性{type:object,properties:{action:{oneOf:[{const:create,description:创建新记录},{const:delete,description:删除记录},{const:update,description:更新记录}]},payload:{allOf:[{$ref:#/$defs/basePayload},{$ref:#/$defs/timestamped}]}},$defs:{basePayload:{type:object,properties:{id:{type:string}}},timestamped:{type:object,properties:{created_at:{type:string,format:date-time}}}}}看起来很强大对吧但有一个限制你必须知道实现方不能自动解析外部$refURI且应该限制 schema 深度和验证时间。也就是说你可以在$defs里定义内部引用但不能写$ref: https://example.com/schemas/user.json然后指望客户端自动去下载。这个设计是为了防止服务端通过恶意 schema 搞 DoS 攻击。影响判断如果你的旧 schema 只用了简单的typeproperties不受影响。如果你之前用了一些擦边球的 schema 写法比如自己 hack 了$ref支持升级后反而需要确认客户端 SDK 是否正确支持了 JSON Schema 2020-12。快速对照什么该立刻改什么可以拖变更项影响行动initialize握手移除 高立即改——检查所有调用 initialize() 的代码Mcp-Session-Id移除 高立即改——删除所有 session 相关代码SSE 长连接推送移除 中计划改——迁移到ttlMs 轮询Tasks API 重写 高立即改如果用到了Roots/Sampling/Logging 标记废弃 低不用急着改新项目别用JSON Schema 2020-12 低旧 schema 兼容新功能可选授权加固iss 验证等 中检查 OAuth 配置确保iss参数正确Mcp-Method/Mcp-Nameheader 中如果你在网关层做了 body 检测路由一个真实的自检脚本光说不练假把式。我写了个小脚本可以快速扫描你的代码里可能被2026-07-28breaking change 影响的调用#!/bin/bash# MCP 2026-07-28 兼容性快速扫描# 在你的项目根目录下运行echo MCP 2026-07-28 兼容性扫描 echo# 检查 initialize 调用echo1. 检查 initialize() 调用...|grep-rninitialize\s*(--include*.py--include*.ts--include*.js.2/dev/null|grep-vnode_modules|grep-v.git||echo ✅ 未发现|echo# 检查 session_id 使用echo2. 检查 session_id / Mcp-Session-Id 使用...|grep-rnsession_id\|Mcp-Session-Id\|sessionId--include*.py--include*.ts--include*.js.2/dev/null|grep-vnode_modules|grep-v.git||echo ✅ 未发现|echo# 检查协议版本硬编码echo3. 检查协议版本字符串...|grep-rn2025-11-25\|protocolVersion--include*.py--include*.ts--include*.js--include*.json--include*.yaml.2/dev/null|grep-vnode_modules|grep-v.git||echo ✅ 未发现|echo# 检查 Tasks API 旧用法echo4. 检查旧版 Tasks API...|grep-rncreate_task\|get_task\|tasks/list--include*.py--include*.ts--include*.js.2/dev/null|grep-vnode_modules|grep-v.git||echo ✅ 未发现|echo# 检查 SSE 长连接echo5. 检查 SSE 推送依赖...|grep-rnnotifications/resources\|notifications/tools\|ServerSentEvent\|EventSource--include*.py--include*.ts--include*.js.2/dev/null|grep-vnode_modules|grep-v.git||echo ✅ 未发现|echoecho 扫描完成 最后MCP 这次升级确实是断了后路式的重构——session 直接删掉Tasks 直接重写。但说实话这个方向是对的。stateless 协议让水平扩展和网关路由变得极其简单以前需要 sticky session 共享存储的架构可以扔进垃圾桶了。不过话说回来——每次 spec 里写just a simple migration的时候实际工作量都远超预期。我猜 7 月 28 日之后GitHub issues 里会冒出一大批升级 SDK 后工具不可用的帖子。如果你已经在用 MCP 做生产部署我的建议是这周就去读一下 draft spec别等到正式版发布才动手跑一遍上面的扫描脚本看看你代码里踩了几个坑在 staging 环境先升级别在生产环境直接莽你用的 MCP 服务器多吗升级 SDK 的时候炸了几个评论区说说——我准备在 7 月 28 日正式版发布后再整理一波实际迁移案例。| 参考MCP 2026-07-28 Release Candidate Blog | MCP 2026 Roadmap (The New Stack) | Draft Specification |