MCP 无状态革命:2026-07-28 规范详解与实战迁移

MCP 2026-07-28 规范移除会话与握手,转向逐请求自描述的无状态架构。
2026-07-28 版 MCP 规范移除了 initialize 握手与 Mcp-Session-Id 会话头,把协议版本、客户端能力与身份信息塞进每个请求的 _meta 信封,使任意服务实例都能处理任意请求,普通轮询负载均衡即可横向扩展。应用状态改由数据库承载,通过工具返回的显式句柄(如 cart_id)寻址,且每个句柄都必须做用户归属校验。服务端主动发起的 elicit 与 sampling 改为 Multi Round-Trip Requests:返回 input_required,客户端携带 inputResponses 与经 HMAC-SHA256 签名的 requestState 重试。文章还给出了 TypeScript SDK v2 的完整迁移路径、双实例验证、错误码语义、兼容性边界与十二步迁移清单。
MCP 无状态革命:2026-07-28 规范详解与实战迁移

Model Context Protocol(MCP)迎来了一次架构层面的重大转向。2026-07-28 版规范正式移除了 initialize 握手与 Mcp-Session-Id 会话头,将整个协议从有状态的会话模型改造为逐请求自描述的无状态模型。官方发布博客将这一定位为「开发者呼声最高的特性之一」,并直言「MCP 正在从双向有状态协议转变为请求/响应式的无状态协议」。
这意味着任何一个服务实例都可以处理任何一个请求,普通轮询负载均衡器即可横向扩展,无需粘性会话,也无需共享会话存储。对构建 Agent 工具链的团队而言,这既是一次显著的部署简化,也是一次必须认真对待的迁移工程。
这次规范到底改了什么
改动集中在「去会话化」与「请求自描述」两条主线上。旧版本中,客户端先发 initialize 完成握手,随后凭借 Mcp-Session-Id 维持上下文;新版规范删除了握手、HTTP GET 通知流、SSE 可恢复性以及服务端主动发起的请求,并把协议版本、客户端能力与客户端信息全部塞进每个请求的 _meta 信封。
| 变更点 | 旧版(2025-11-25) | 新版(2026-07-28) |
|---|---|---|
| 会话 | Mcp-Session-Id 头 + DELETE 结束 | 已移除,改用显式句柄 |
| 握手 | initialize + notifications/initialized | 已移除,版本与能力随请求携带 |
| 服务端发请求 | 推送 elicitation/create、sampling/createMessage | 返回 resultType: "input_required",客户端重试 |
| 通知流 | HTTP GET 流 + resources/subscribe | subscriptions/listen 长连接 POST |
| 可恢复性 | Last-Event-ID 重投 | 已移除,断流即重发请求 |
| 结果类型 | 无类型字段 | 强制 resultType:complete 或 input_required |
| 缓存 | 无 | 列表与读取结果强制携带 ttlMs 与 cacheScope |
此外,server/discover 成为每个服务端必须实现的新 RPC,用于声明支持的版本、能力与身份。ping 与 logging/setLevel 被删除,日志级别改为通过请求的 _meta 传递。资源未找到的错误码从 -32002 调整为 -32602,并新增 -32020 至 -32022 三个错误码。
规范同时将 Roots、Sampling、Logging、旧的 HTTP+SSE 传输方式与动态客户端注册标记为废弃。按照官方特性生命周期政策,废弃特性拥有至少十二个月的移除窗口期,这给存量系统留出了缓冲空间。
_meta 信封是 MCP 各版本中用于承载附带元数据的保留字段,规范允许在请求/响应的 params 或 result 中放置 _meta 对象,工具与客户端可自由读写。在 2026-07-28 之前的版本里,它主要用于进度令牌、追踪 ID 等辅助信息;新版规范把它提升为协议协商的主要载体:协议版本、客户端能力声明、客户端名称与版本、日志级别等原先靠 initialize 握手一次性交换的信息,现在都逐请求写入 _meta。这一设计的代价是每个请求体积略增、头与体需要保持一致性校验,收益则是服务端不再依赖任何前置状态即可完整理解请求语义,从而支撑无状态部署。实现时需注意 _meta 的键名应遵循规范保留前缀,避免与工具自定义元数据冲突。
为什么无状态是更好的选择
除了部署层面的收益,官方给出的理由还有一个常被忽视的维度:模型可见性。发布博客指出,如果服务端需要跨调用保持状态,「从一个工具铸造一个显式句柄,让模型把它作为参数传回来。我们发现这比隐藏在传输层的会话状态效果更好——模型能看到句柄,并在工具之间传递它。」
这个观点值得展开。会话状态对模型是不可见的黑盒,模型无法推理、无法调试、也无法在多个工具之间显式编排。而 cart_id、job_id 这类句柄一旦作为工具返回值暴露出来,模型就获得了对状态的一等公民访问权,能够做出更合理的决策。
AWS 架构团队在其平台实践中得出了相同结论:基于会话的服务端所需要的粘性会话与共享会话存储,「已经不再必要」。他们从基础设施视角验证了这次规范调整的实际价值,这与 MCP 官方博客的表述形成了交叉印证。
| 维度 | 有状态 MCP(会话时代) | 无状态 MCP(2026-07-28) |
|---|---|---|
| 首个请求 | initialize 后再 notifications/initialized | 直接是真实调用 tools/call |
| 负载均衡 | 粘性会话或共享会话存储 | 普通轮询即可 |
| Serverless 适配 | 会话需跨冷启动存活,较别扭 | 天然契合 |
| 状态存放位置 | 隐藏在传输层会话中 | 自建数据库,以模型可见的句柄寻址 |
| 向用户提问 | 服务端在调用中途推送 elicitation/create | 返回 input_required,客户端重试 |
Multi Round-Trip Requests(MRTR)是本次规范用来替代服务端主动推送的核心机制。旧模型中,服务端在工具执行中途通过 SSE 连接向客户端推送 elicitation/create 或 sampling/createMessage,客户端应答后服务端继续执行;这要求连接在整段交互期间保持存活,且服务端能定位到那个特定的会话。MRTR 把一次交互拆成多个独立的 HTTP 请求:服务端发现缺少必要输入时,返回 resultType: "input_required"、一份 inputRequests 清单以及可选的 requestState,随即结束本次请求;客户端收集用户答案后,携带 inputResponses 与原样回传的 requestState 重新发起同一个调用。服务端凭借签名验证 requestState 的来源与完整性,恢复上下文继续处理。这样每个请求都自成闭环,任意实例都能接手,代价是客户端需要实现重试循环——SDK 客户端已内置该逻辑。
用 TypeScript 构建无状态 MCP 服务端
原作者的实战基于官方 TypeScript SDK v2(@modelcontextprotocol/server 2.3.1),在 Node.js 24.22.0 上完成验证。核心思路是:为每个请求构造一个全新的 McpServer 实例,交给 createMcpHandler 处理;在 Node 环境用 @modelcontextprotocol/node 的 toNodeHandler 包装,在 Web 运行时则直接调用 handler.fetch(request)。
状态放进存储,而不是会话
示例是一个极简购物车服务,购物车数据存放在共享存储中,以一个服务端铸造的句柄作为键。原文用 JSON 文件模拟以支持两个进程共享,生产环境应替换为 Postgres 或 Redis。
这里有一个关键的安全细节:句柄会流经模型,因此必须被视为标识符而非访问凭证。原文的 carts.get() 方法中显式校验 cart.owner === owner,注释写得很直白——「句柄不是凭证」。每一次工具调用都要拿句柄与已认证用户做比对。
用 requestState 承载交互中间状态
只在一次交互的多个轮次之间需要的数据,交给 requestState,并用 createRequestStateCodec 做 HMAC-SHA256 签名。这个编解码器在实例之间共享密钥,因此 A 实例铸造的状态 B 实例也能验证。
SDK 的迁移文档明确指出,requestState「经由客户端往返,因此是不可信输入」,必须做完整性保护;同时它「是签名而非加密」,所以绝不能把秘密放进去。这两句话构成了使用 requestState 的完整边界。
双实例验证:状态确实不在连接里
原文启动了两个实例(端口 3101 与 3102),在 A 上创建购物车,然后依次通过 B 和 A 添加商品:
create_cart on A → Created cart_d892bc05-… on A
add_item on B → 1 line(s) in cart_d892bc05-…, served by B
add_item on A → 2 line(s) in cart_d892bc05-…, served by A
每次调用落在不同进程上,却没有任何中断,因为状态存在存储里,而不是连接里。这是对无状态设计最直观的验证。
无状态请求长什么样
一个无状态 MCP 请求就是单次 HTTP POST,携带三个头:MCP-Protocol-Version、Mcp-Method,工具调用场景再加 Mcp-Name。请求体的 params._meta 重复协议版本并声明客户端能力。由于没有前置握手,客户端发送的第一个请求就可以是工具调用本身。
server/discover 由 SDK 自动实现,返回支持的版本列表、能力与 serverInfo。列表类结果因为设置了 cacheHints,会告诉客户端可以缓存 60 秒。若不设置,SDK 会采用最保守的策略:ttlMs: 0 配合 cacheScope: "private"。规范强调 "public" 允许共享中间缓存,因此当工具列表因用户而异时,必须保持在 private。
cacheHints 中的 ttlMs 与 cacheScope 是本次规范新增的缓存语义。ttlMs 表示该结果可被缓存多少毫秒,设为 0 表示不可缓存;cacheScope 取 "public" 时允许共享中间缓存(如 CDN 或反向代理)存储并复用该响应,取 "private" 时只允许针对单一用户或客户端的私有缓存。对于 tools/list、resources/list 与资源读取结果,规范强制要求携带这两个字段,SDK 在未显式设置时采用最保守的 ttlMs: 0 加 private。判断标准很简单:内容对所有用户一致才可用 public,一旦列表或资源内容因认证主体而异,就必须保持 private,否则共享缓存可能把一个用户的数据返回给另一个用户。
确认交互改由多轮请求承担
失去会话后,服务端无法在调用中途推送 elicitation/create。取而代之的是 Multi Round-Trip Requests(MRTR):服务端返回 resultType: "input_required",附带问题清单 inputRequests 与可选的 requestState;客户端向用户询问后,带着 inputResponses 和原样回传的 requestState 重试同一个调用。
原文用 clear_cart 演示了这个流程:第一轮发往 B 实例,返回 input_required;第二轮发往 A 实例,因为两个实例共享 MCP_STATE_KEY,A 成功验证了 B 铸造的状态并返回 complete。当把 requestState 改动一个字符后,服务端返回 -32602 与 "Invalid or expired requestState"。
使用 SDK 客户端时无需手写重试逻辑,它会通过你注册的 elicitation/create 处理器自动应答并重试。不过原文提醒了一个容易被忽略的点:v2 的 Client 默认仍然使用 2025 握手,必须通过 versionNegotiation: { mode: 'auto' } 或 { pin: '2026-07-28' } 显式启用新版协商。
错误响应与兼容性边界
无状态服务端的错误语义相当明确:头与体不一致或缺头返回 HTTP 400 与 -32020;不支持的协议版本返回 -32022 并附带 data.supported;工具需要请求未声明的客户端能力时返回 -32021;ping 这类已移除方法返回 404 与 -32601;对 GET 请求返回 405。
头校验的存在是因为安全。Streamable HTTP 规范要求服务端必须拒绝不一致,否则「负载均衡器按头值路由、而 MCP 服务端按体值执行」的差异可能被利用。
兼容性方面,createMcpHandler 默认 legacy: 'stateless',会逐请求应答 2025 时代的 initialize 与后续请求,且不签发会话 ID。普通工具可用,但需要用户输入的工具对老客户端会失败,因为逐请求的旧版连接无法接收服务端到客户端的请求。原文实测中,clear_cart 在老客户端下返回了明确的错误提示,指出该连接无法声明所需能力。
若老客户端确实需要确认交互,可行方案是保留原有的有状态处理器,用 isLegacyRequest(request) 做前置判断,将旧流量路由到旧处理器。规范版本页也明确,旧版客户端连接仅支持新版的服务端会失败,因为「旧版客户端没有向前回退机制」。
isLegacyRequest(request) 是 SDK 提供的请求判别辅助函数,用于在同一个处理器入口区分 2025 时代与 2026-07-28 时代的流量。典型用法是在路由层先调用它:返回 true 的请求交给保留的有状态处理器,返回 false 的交给逐请求的无状态处理器。这样老客户端仍能完成握手、维持会话并使用普通工具,而新客户端享受无状态部署。需要注意这条双轨路径并非完全等价:走旧处理器的连接是逐请求应答的,无法接收服务端到客户端的请求,因此依赖 elicitation 或 sampling 的工具在老客户端下会失败并返回明确错误。规范版本页也指出,旧版客户端连接仅支持新版的服务端会直接失败,因为旧客户端没有向前回退机制,所以双轨支持是迁移期内的过渡手段,而非长期方案。
迁移清单
迁移的核心动作可以归纳为:升级 SDK、切换为逐请求处理器、将会话数据分流到句柄 / requestState / 认证信息、把推送式 elicit 改写为 input_required,最后移除会话基础设施并设置缓存提示。
- 升级 SDK:TypeScript 从
@modelcontextprotocol/sdk1.x 迁移到@modelcontextprotocol/server2.x;Python、Go、C# 的 Tier 1 SDK 也已同步更新。 - 排查所有会话 ID 使用点(
extra.sessionId、ctx.sessionId、以Mcp-Session-Id为键的映射),并将数据迁移至句柄、requestState或认证主体。 - 从工具返回句柄(
cart_id、job_id、doc_id),并在工具描述中点名,引导模型回传。 - 对每个句柄都做用户归属校验。
- 把
elicitInput()、requestSampling()改写为return inputRequired({...}),并在重入时读取ctx.mcpReq.inputResponses。 - 用
createRequestStateCodec封装requestState,并在实例间共享密钥。 - 确保工具可安全重试——流不再可恢复,连接中断意味着客户端会重新发起请求。
- 为
tools/list与资源设置cacheHints,结果因用户而异时用private。 - 删除
ping处理器与会话级日志级别代码,改用 stderr 或 OpenTelemetry。 - 将资源未找到错误从
-32002改为-32602。 - 同时测试新旧两个时代:
mode: 'auto'的现代客户端与旧版客户端,包括需要用户输入的工具。 - 完成以上步骤后,才移除粘性会话、会话存储与 GET 路由。
常见误区
原文列出的高频错误大多会在测试中以可见的 400 暴露,但其中两类会静默泄露数据,值得单独强调:把 cart_id 当作访问凭证,以及把按用户区分的列表标记为 cacheScope: 'public'。前者造成越权访问,后者可能导致共享缓存把一个用户的列表返回给另一个用户。
其他值得警惕的点包括:把秘密写入 requestState(客户端可 base64 解码)、各实例使用不同的 MCP_STATE_KEY 导致跨实例重试失败、自定义客户端漏发 Mcp-Method / Mcp-Name 头、v2 客户端停留在默认握手设置,以及把需要 elicit 的工具挂在 legacy: 'stateless' 路径下。
关键要点
- 2026-07-28 规范让 MCP 协议层彻底无状态:无
initialize、无Mcp-Session-Id,每个请求自描述。 - 服务端必须实现
server/discover,并对头体不一致返回-32020。 - 应用状态存入数据库,由工具返回的句柄寻址,每个句柄都要做归属授权。
- 确认交互改为多轮请求:返回
input_required,客户端带inputResponses与requestState重试。 - 用
createRequestStateCodec签名requestState并共享密钥,它是签名而非加密,不能放秘密。 createMcpHandler可逐请求服务旧版客户端,但该路径无法处理 elicit 交互。- v2 TypeScript 客户端默认仍是 2025 握手,需用
versionNegotiation: { mode: 'auto' }主动启用新版。
相关推荐

Cursor免费版8分钟建生产级应用,却败在同一个安全细节
Cursor免费版用8分12秒构建生产级Node+Postgres应用,22项API测试与界面检查全绿,比Copilot快6分钟,但默认密钥、无法撤销的JWT会话和登录无限流三个问题暴露了AI编程工具的安全盲区。

GoHighLevel对话AI接入MCP:智能体可调用40+外部应用
GoHighLevel 对话 AI 新增 MCP 支持,智能体可在对话中调用 40+ 外部应用完成预约、付款、表格写入等操作。了解功能限制、配置方法和上线时间。

物业管理软件为何必须拥抱AI与MCP协议
物业管理软件正转向开放生态。某系统已上线MCP服务器并筹备云端连接器,支持AI智能体接入与自定义工作流。本文分析MCP、开放API为何成为物业软件选型新标准。