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": {}
}
}
}
对服务器来说,这意味着用每个请求上的三项检查取代一次握手:
- 版本。 如果服务器不支持所请求的版本,就返回 400 和
UnsupportedProtocolVersionError,并列出它支持的版本。 - 请求头与请求体的核对。 每个请求都必须带
Mcp-Method,tools/call、resources/read和prompts/get还必须带Mcp-Name。它们镜像方法名和工具名,不一致即为HeaderMismatch错误。 - 能力。 服务器过去从
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 服务器是否在会话中保存了某些东西,现在必须改成句柄?欢迎在下方评论区告诉我们。