2026-07-28 MCP修订版移除初始化握手和协议级会话,远程服务更易扩展,但工具调用若涉及扣费、建单等副作用,重试可能引发重复操作。
无状态传输去掉的是一个状态持有者,而不是状态本身
新版 MCP 废除了 initialize/initialized 以及 Mcp-Session-Id 请求头。每个请求都携带服务器解释它所需的全部信息,任意请求都可以落在负载均衡器后任意一个兼容实例上。2026-07-28 官方规范发布版将这一结果描述为「无状态协议核心」。
这是重要的基础设施简化。它将粘性路由和协议自有会话存储从默认路径中移除。但它没有回答以下应用层问题:
该逻辑操作是否已经启动?
外部副作用是否已发生?
结果是否已持久化?
调用方是否已收到该结果?
谁有权限重试每一步?
生产级 AI 基础设施工程的纪律,从在为队列、数据库或 worker 框架做选型之前先命名这些责任方开始。Session 曾经通过给相关调用提供一个便捷容器来隐去部分决策。一旦容器消失,实现要么直接建模这些决策,要么将它们隐式留在进程内存中。
隐式状态是危险的选择。进程内存能告诉当前 worker 它看到了什么,但无法告诉下一个 worker 在崩溃、超时、重新调度或部署之后已经发生了什么。
以 issue_refund 这一工具为例。客户端发送一个请求,但该业务操作跨越了多个边界:
服务器接受意图。
Worker 开始执行操作。
支付提供商执行退款。
服务器记录提供商结果。
结果到达客户端。
「请求失败了」不是一种状态。它可能意味着进程在工作开始前就死了、在本地工作期间死了、在提供商提交退款后死了、或者在结果已存储但响应尚未到达前死了。这些状态需要不同的恢复行为。
如果服务器在每次超时后盲目重放整个 handler,第三个窗口可能导致退款重复执行两次。如果 worker 将操作标记为已开始后就不再重试,第一或第二个窗口可能使合法工作永远搁浅。如果将已存储的结果视为调用方已收到的证明,第五步可能悄然丢失投递。
协议无法解决这种歧义,因为这种歧义属于应用程序。无状态 MCP 让这个边界变得可见,但并没有消除它。
在写重试代码之前,定义一个逻辑 operation_id,并将每个崩溃窗口映射到持久化证据和一个责任方:
该表分离了三个经常被混为一谈的概念:
因此,一个可用的无状态 MCP 服务器架构需要一个任务记录,其生命周期独立于任何单一请求或服务器实例。它可以很紧凑,但必须能够在那类可以安全重试的故障中存活下来。
这也阐明了显式 MCP 状态句柄的用途。SEP-2567 用普通的服务器签发的句柄替代了隐式的 session 作用域应用状态,模型可以在调用之间携带这些句柄。句柄标识一个购物车、浏览器、工作流或操作。它不是效果是否已发生的证据。句柄背后的资源仍然需要自己的并发和恢复契约。
一个最小的操作记录可能是这样:
{
"operation_id": "refund_01J...",
"input_hash": "sha256:...",
"status": "effect_pending",
"attempt": 2,
"effect_key": "refund_01J...",
"effect_receipt": null,
"result": null,
"delivery_status": "not_ready"
}
字段本身不如围绕它们的不变量重要:
用不同输入重用同一个 operation_id 必须失败并关闭。
只有一个活跃的尝试可以拥有当前租约。
每个非幂等适配器接收一个稳定的 effect key。
提供商回执在执行被标记为完成前必须先持久化。
投递可以在不重新运行执行的情况下被重试。
这个设计将重试从「再次调用 handler」转变为「从最后一个持久化状态推进已知操作」。这就是韧性与重复执行之间的区别。
它也给了可观测性一个有用的单元。服务器实例 A 和 worker 实例 B 的日志不再是无关的请求追踪,而是附加到同一操作的多次尝试。告警可以区分卡住的租约、不确定的副作用、等待投递的已完成结果、以及 simply 停止轮询的客户端。
2026-07-28 版本还将长时间运行的工作移到了 Tasks 扩展中。服务器可以返回 task 句柄,客户端可以轮询或更新该 task。这更适合跨越一个请求生命周期的工作,但 task 句柄本身并不能使支付、邮件、部署或仓库变更变得幂等。
task 服务可以拥有生命周期转换,例如 accepted、working、input required、completed、failed 或 cancelled。与外部系统对话的适配器仍然必须拥有 effect key 和对账行为。投递层仍然必须拥有「结果存在」与「消费者已收到」之间的区别。
这种划分防止了一种常见的架构错误:让一个 status 字段描述三个独立的系统。队列可以如实地说一个任务完成了,而邮件提供商在接受消息后超时了。MCP task 可以如实暴露一个结果,而客户端在看到它之前就断开了连接。这两种说法可以同时为真。
并非每个工具都需要操作账本。只读搜索通常可以从头重试。没有外部副作用的确定性转换可能只需要请求级超时处理。Handler 的下游系统如果提供了强幂等性和可查询回执,则可以将大部分效果恢复委托给该系统。
持久化恢复也有成本:更多状态、保留规则、对账路径、访问控制和测试。显式句柄可能出现在聊天日志或子 agent 上下文中,因此经过身份验证的服务器应该将句柄绑定到请求的授权上下文。对于未身份验证的服务器,SEP-2567 建议将句柄视为具有高熵的短生命周期能力令牌。
目标不是让每个工具都成为工作流引擎。而是将恢复模型与不可逆边界相匹配。
用一个测试:如果重试操作可以使外部世界产生两次不同的变化,在第一次副作用之前持久化操作和一个稳定的幂等 key。如果执行和投递可能独立失败,则独立持久化它们。如果两个条件都不成立,则保持设计更小。
MCP 的无状态核心是一个真正的改进,因为它阻止了传输层 session 假装自己是应用状态。只有当每个持久化事实都有了一个明确的归属,并且每次重试都有一个责任方时,迁移才算完成。
你的实现在无状态变更之后,是否仍然将某类故障视为 session 所属?