2026 年 7 月 28 日,Model Context Protocol 发布了规范的新修订版。变更日志开头的两项改变了每一个远程服务器的工作方式:协议层面的会话没有了,initialize 握手也没有了。现在,每个请求都自带服务器回答它所需的一切。

对许多服务器来说,这是一次迁移。对读取身份证件的服务器来说,这更像是一次确认,因为这类服务器本来就有充分的理由保持无状态。本文从工具服务器的角度梳理这次修订:哪些变化可以忽略,哪些必须实现,哪些值得尽早采用。示例来自我们运行的一个服务器,所以下文提到的缺口也是我们自己的缺口。

这是 doc.cheap 的博客。doc.cheap 是一款护照和身份证 OCR API,前面有一个 MCP 服务器。阅读涉及产品的部分时,请记住这一点。

简要版

2026-07-28 中的变化 无状态工具服务器要做什么
没有 Mcp-Session-Id,没有会话 如果从未保存过会话,什么都不用做
没有 initialize 握手;版本和能力放在每个请求的 _meta 中 按请求读取,并与 MCP-Protocol-Version 头核对
server/discover 实现它:这是 MUST
必须带 Mcp-Method 和 Mcp-Name 头 检查它们与请求体一致
每个结果都带 resultType 发送 "complete"
list 和 read 结果带 ttlMs 和 cacheScope 选定缓存时长和范围
工具按确定的顺序返回 保持注册顺序稳定
移除 HTTP GET 流和可恢复性 对 GET 和 DELETE 返回 405
Roots、Sampling 和 Logging 被弃用 通过工具参数接收文件,日志写到 stderr 或 OpenTelemetry

本文其余部分逐行解释,并说明为什么这些变化对处理证件的服务器更重要。

会话没有了,这正适合证件服务器

变更日志的第一项从 Streamable HTTP 传输中移除了"协议层面的会话和 Mcp-Session-Id 头"。像 tools/list 这样的列表端点"不再因连接而异",需要跨调用保存状态的服务器则"使用由服务器生成的显式句柄,作为普通的工具参数传递"。

证件服务器本来就很少需要会话。想想会话会保存什么:一个 API 密钥,也许是一张等待读取的图像,也许是上一次的结果。这些都是你不愿意在两次调用之间留在进程内存里的数据,在那里,一个 bug 就可能把它交给下一个调用方。

我们托管在 https://mcp.doc.cheap/mcp 的服务器,在这次修订之前就是这样构建的。代码里写着它"有意设计为无状态":每个 POST 都得到一个新的服务器和一个新的传输,回答之后即被丢弃,因此"内存中没有任何会话,重启不会丢失任何东西,一个调用方的密钥永远不会被另一个调用方的请求看到"。传输在创建时不带会话 ID 生成器,在 TypeScript SDK 中正是这一点让它成为无状态的;它以普通 JSON 而不是流来回答。

修订版所描述的句柄模式也已经存在。当智能体想取回一个已保存的结果时,它不依赖会话来记住它。scan_document 返回一个扫描 ID,get_scan 和 delete_scan 工具把这个 ID 作为普通的 scan_id 参数接收。ID 就是句柄。任何服务器实例都能处理它,明天重新连接的客户端也能使用它。

如果你的服务器按会话保存了某些东西,要问的问题是同一个:它能否变成一个由服务器返回、由客户端传回的 ID?对于证件处理工作,答案通常是可以,而且通常还能带来隐私上的好处。

没有握手:版本随每个请求一起传递

第二项让 MCP 在协议层面变为无状态。initialize 和 notifications/initialized 的交换被移除,"每个请求现在都在 _meta 中携带其协议版本和客户端能力"。在 HTTP 上,版本还会放在一个请求头里。Streamable HTTP 页面规定每个 POST 必须(MUST)包含 MCP-Protocol-Version 头,且其值必须与 _meta 中的值一致;否则服务器以 400 和 HeaderMismatch 错误回应。

下面是新形式下一次证件工具调用的样子。请求头和 _meta 的键沿用规范自己的示例;工具和参数是我们的,值为占位符:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: scan_document

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "scan_document",
    "arguments": { "image_base64": "<base64 image>", "retain_hours": 0 },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "<client>", "version": "<version>" },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

对服务器来说,这意味着用每个请求上的三项检查取代一次握手:

  1. 版本。 如果服务器不支持所请求的版本,就返回 400 和 UnsupportedProtocolVersionError,并列出它支持的版本。
  2. 请求头与请求体的核对。 每个请求都必须带 Mcp-Method,tools/call、resources/read 和 prompts/get 还必须带 Mcp-Name。它们镜像方法名和工具名,不一致即为 HeaderMismatch 错误。
  3. 能力。 服务器过去从 initialize 记住的内容,现在要从手头的请求中读取。

对证件服务器来说,这些请求头比看上去更重要。一次 scan_document 调用以 base64 携带整张图像;我们的托管服务器接受最大 36 MiB 的请求体。有了请求头里的 Mcp-Method 和 Mcp-Name,网关无需读取请求体,就能把开销大的工具与轻量工具分开路由、计数或限流。规范为这类网关加了一条提醒:它们应当(SHOULD)核实版本头所指的版本确实要求校验请求头与请求体,否则拒绝请求,而不是信任一个没人检查过的请求头。

server/discover 是必需的

第三项新增了 server/discover,"服务器必须(MUST)实现此 RPC,以公布其支持的协议版本、能力和身份"。客户端可以先调用它来选择版本,也可以在 stdio 上把它当作兼容性探测。

这是无状态服务器唯一不能跳过的变化。我们的服务器仍在 GET / 上提供一个小的发现文档(名称、版本、传输和端点),但那只是一个普通网页,不是协议方法。想要使用 2026-07-28 通信的服务器,需要自己回答 server/discover。

结果:resultType、缓存和顺序

另外三项较小的变化改变了结果的形态。

  • resultType。 现在每个结果都带有它:普通回答为 "complete",多轮往返请求的中间回答为 "input_required"。客户端必须(MUST)把来自旧协议服务器、省略了该字段的结果视为 "complete",所以旧服务器仍能工作,但新服务器要发送它。
  • ttlMs 和 cacheScope。 tools/list、prompts/list、resources/list、resources/read 和 resources/templates/list 的结果必须带上以毫秒计的新鲜度提示和一个范围,"public" 或 "private"。如果服务器的工具列表只在部署时变化、资源是随附的文档,那么较长的 ttlMs 加 "public" 是自然的选择。依赖调用方密钥的资源则应为 "private"。
  • 确定的工具顺序。 服务器应当(SHOULD)以确定的顺序返回 tools/list 中的工具,以帮助客户端缓存和提示缓存命中。如果代码中按固定顺序注册工具,就保持这样;我们的服务器每次都以相同顺序注册六个工具。

向用户追问:多轮往返请求

旧版修订允许服务器在一次调用中途向客户端发送自己的请求:用 elicitation/create 询问用户,用 sampling/createMessage 借用客户端的模型,用 roots/list 获知允许访问的文件夹。新修订用 Multi Round-Trip Requests 模式取而代之。服务器以 resultType: "input_required" 和一个 inputRequests 列表回答,客户端再带上 inputResponses 重试原请求。

对证件工具来说,这正是提出"照片左侧被裁掉了,要再发一张吗?"这类问题的干净位置。因为重试就是同一个请求再发一次,两次调用之间服务器无需保存任何东西。像我们这样从未使用过服务器发起请求的服务器,在这里没有需要迁移的内容。

哪些被弃用,以及用什么替代

Roots、Sampling 和 Logging 已被弃用。在至少十二个月的弃用期内它们仍可工作,但"新的实现不应再添加对它们的支持"。建议的替代方案很适合证件服务器:

  • 用工具参数或配置传递文件,取代 Roots。 我们的本地服务器只有在配置中设置了图像目录时才读取文件路径,而且只限该目录之内;目录之外的路径会被拒绝。参数由模型选择,围栏由运维者划定。
  • 把日志写到 stderr 或 OpenTelemetry,取代 Logging。 本次修订还移除了 logging/setLevel;日志级别现在按请求通过 _meta 传递,服务器不得为没有要求日志的请求发出日志消息。追踪上下文(traceparent)也可以放在 _meta 中。

旧客户端还会继续调用

客户端不会在同一天全部迁移,因此传输页面说明了只支持 2026-07-28 的服务器应如何处理旧的流量:

  • 对 MCP 端点的 GET 或 DELETE:405 Method Not Allowed。
  • Mcp-Session-Id 头:忽略它,绝不生成或回传。
  • Last-Event-ID 头:忽略它,因为流已不再可恢复。

我们的托管服务器已经对其端点上的 GET 和 DELETE 返回 405 和 Allow: POST, OPTIONS 头,并且不生成会话 ID。仍在使用旧修订的客户端会从错误体中得知这一点:规范要求它只有在 400 的响应体不是可识别的新式错误时,才回退到 initialize。

我们的服务器现状

准确地说:托管服务器构建在 TypeScript SDK 1.30.0 版本之上,我们自己的代码目前还没有任何地方提到 2026-07-28。上文中已经成立的部分,都来自它的设计:没有会话,用 ID 作句柄,对 GET 和 DELETE 返回 405,文件受配置的围栏限制。server/discover、请求头检查、resultType 和缓存字段是剩下的工作。护照 OCR MCP 服务器页面和 MCP 服务器指南介绍了客户端目前连接的内容。

如果你正在为智能体挑选证件 API,护照 OCR API 对比列出了各家服务;MCP 这一侧值得分别向每一家单独询问。

你自己的工具服务器检查清单

  • 请求之间不保留任何状态;后续调用需要的东西,都是服务器返回的 ID。
  • 每个请求的版本都与 MCP-Protocol-Version 头核对,对不支持的版本返回 UnsupportedProtocolVersionError。
  • Mcp-Method 和 Mcp-Name 与请求体核对。
  • server/discover 返回版本、能力和身份。
  • 结果带 resultType;list 和 read 结果带 ttlMs 和 cacheScope。
  • tools/list 每次返回相同的顺序。
  • GET 和 DELETE 得到 405;会话和事件 ID 的请求头被忽略。
  • 文件通过参数传入,并限制在配置的围栏内,而不是通过 Roots。
  • 日志写到 stderr 或 OpenTelemetry,而不是发给客户端。

完整规范见 modelcontextprotocol.io,发布文章介绍了背景。关于我们服务器的更多信息,请见护照 OCR MCP 服务器页面。

问你一个问题: 你的 MCP 服务器是否在会话中保存了某些东西,现在必须改成句柄?欢迎在下方评论区告诉我们。