SSD Nodes Learn 🎉 VPS $4.99/月起
指南 Matt Connor作者: Matt Connor

MCP 无状态服务器架构变更详解:移除会话与握手

MCP 2026-07-28 版本移除了 initialize 握手与会话机制。本文分析该变更对负载均衡、连接超时、身份验证及反向代理的具体影响,助您快速适配无状态协议架构。

什么是无状态 MCP 服务器

无状态 MCP 服务器在请求之间不保留任何客户端状态。每个请求都携带协议版本、客户端能力以及服务器响应所需的凭据,因此任何机器上的任何进程都可以响应任何请求。MCP(Model Context Protocol,即代理用于访问工具的线路格式)在 2026-07-28 版本中将其定为规则,移除了 initialize 握手以及底层的 HTTP 会话。

这就是其核心运维意义所在。不保留客户端状态的服务器可以部署在普通的负载均衡器后,无需会话亲和性;在部署期间重启不会中断客户端连接;且可以运行四个相同的进程而非仅一个。面向会话的服务器若没有额外的机制,则无法实现上述任何一点。

Model Context Protocol 是一种无状态协议:处理请求所需的所有信息均包含在请求本身中。服务器独立处理每个请求;不应从先前的请求中推断任何状态,即使是来自同一连接或流的请求也不例外。

无状态并不意味着服务器不存储任何数据。数据库、队列和缓存依然存在。它指的是协议在连接上不携带状态,因此服务器不得将连接、进程或打开的套接字视为“当前处于对话中的客户端”的替代标识。

2026-07-28 版本移除了哪些内容

2026-07-28 是截至 2026 年 8 月的当前规范版本。与 2025-11-25 相比,它移除了五个用于支持会话的功能。

  • initialize 请求和 notifications/initialized 通知。现在完全没有握手过程 (SEP-2575)。
  • Mcp-Session-Id 头部,以及使用 HTTP DELETE 进行会话终止的功能 (SEP-2567)。
  • 独立的 HTTP GET 流,服务器曾通过该流推送通知。它已被 subscriptions/listen 取代,后者是一个普通的 POST 请求,其响应为长连接流。
  • SSE (服务器发送事件) 流的可恢复性。Last-Event-ID 头部和每个事件的 ID 已被移除,因此中断的流会导致传输中的请求丢失,客户端必须将其作为带有新请求 ID 的新请求重新发起。
  • pinglogging/setLevelnotifications/roots/list_changed。日志级别现在是每个请求的字段,即 _meta 中的 io.modelcontextprotocol/logLevel

新增了一个方法,且所有服务器必须实现它。server/discover 在一次调用中返回服务器支持的协议版本、功能和标识。这是目前仅存的最接近握手的功能,客户端调用它是可选的。

为什么会话传输在生产环境中难以运行

2025-11-25 及更早版本中,服务器可以在初始化时生成会话 ID,并通过 InitializeResultMcp-Session-Id 头部返回。客户端随后必须在后续的每个请求中发送该头部。协商的协议版本和客户端能力存储在服务器内存中,并以该 ID 作为键。上述每一项选择都会带来运维成本。

  • 重启会清除会话表。规范要求服务器对携带失效会话 ID 的请求返回 404 Not Found,并要求客户端使用新的 InitializeRequest 重新开始。每次部署都会导致所有已连接的客户端发生重连。
  • 第二个副本无法识别第一个副本的会话。横向扩展意味着负载均衡器必须使用粘性路由,或者所有副本在处理每个请求时都要读取共享的会话存储。
  • 会话表是随空闲客户端数量增长的内存占用。DELETE 是可选的,未发送该指令而直接关闭的客户端会留下残留条目。
  • 列表结果可能因连接而异,因此在服务器前端进行缓存是不安全的。

移除会话机制可同时解决上述四个问题。在修改任何配置之前,理解这一变更至关重要。

当前每个请求所携带的内容

每个发送至 MCP 端点的 POST 请求都是独立的。协议版本和客户端能力在请求体中通过 _meta 传输,部分选定字段会被镜像到 HTTP 头部,以便中间件无需解析 JSON 即可进行路由。

POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
Authorization: Bearer <access token>

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {"location": "Seattle, WA"},
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {"name": "ExampleClient", "version": "1.0.0"},
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

每个请求都必须包含 io.modelcontextprotocol/protocolVersionio.modelcontextprotocol/clientCapabilitiesclientInfo 虽然不是强制要求,但客户端仍应发送。缺少必填字段的请求属于格式错误,服务器必须拒绝该请求,并返回 JSON-RPC 错误 -32602 和 HTTP 状态码 400 Bad Request

每个请求都必须包含 Mcp-Method 头部。在 tools/callresources/readprompts/get 请求中,Mcp-Name 是必填项。头部的值必须与请求体中的内容一致;若服务器处理了请求体,发现两者不匹配,必须拒绝请求并返回 400 Bad Request 以及错误代码 -32020HeaderMismatch。制定此规则的原因是:基于头部进行路由的负载均衡器与基于请求体执行逻辑的服务器是两个不同的事实来源。若您基于这些头部进行路由或限流,请先检查 MCP-Protocol-Version:早期版本未对头部与请求体进行校验,因此在这些版本中,头部的值不可信。

版本不匹配现在被视为普通的单次请求错误,而非握手失败。若服务器未实现所请求的版本,应返回 400 Bad Request 以及错误 -32022UnsupportedProtocolVersion,并在 data.supported 中列出其支持的版本。客户端从该列表中选择一个版本并重试。

状态的去向:令牌、游标与订阅

状态并未消失,而是转移到了可见且可记录的位置。

凭据随每个请求传递。 由于没有用于关联身份的会话,访问令牌会随每次 HTTP 调用发送并进行实时验证。详细信息请参阅下方的身份验证章节。

游标必须携带自身的位置信息。 tools/listresources/listprompts/listresources/templates/list 上的分页使用不透明的游标字符串,客户端不得解析或修改该字符串。在单进程服务器中,通常将偏移量保存在内存中并以会话为键。由于没有会话,游标必须包含足够的信息供任何副本恢复列表,因此需将位置信息编码进游标并进行签名,或将其保存在所有副本共享的存储中。无效的游标应返回 -32602。必须对游标进行签名,因为不透明游标本质上仍是客户端提供的输入,代码在解码并信任它之前必须进行校验。

订阅属于请求,而非连接。 需要变更通知的客户端会发送带有过滤条件的 subscriptions/listen,指定其所需的类型:toolsListChangedpromptsListChangedresourcesListChangedresourceSubscriptions。服务器回复 notifications/subscriptions/acknowledged 并保持该响应流开启。如果流中断,服务器不会保留任何状态,客户端需重新发送 subscriptions/listen 以恢复订阅。

跨调用应用状态变为显式句柄。 当服务器确实需要在调用之间记忆某些内容时,规范的解决方案是使用服务器生成的标识符,并将其作为普通工具参数传回。该标识符会出现在工具模式(tool schema)中,可以被记录日志,且绝不会隐含在连接中。拥有真实用户数据的服务器(例如 自托管的 MCP 邮件服务器)会使用此模式代替会话:邮箱或草稿标识符作为工具参数,因此任何副本都可以处理后续调用。

部署:反向代理、超时与健康检查

MCP 端点是一个接收 POST 请求的路径。大多数流量是短请求和 JSON 响应,任何代理都能处理。例外情况是流式响应,此时代理的默认设置会产生负面影响。当你从笔记本电脑演示环境迁移到 运行在 VPS 上的 MCP 服务器 时,这一点尤为重要。

location /mcp {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_buffering off;
    proxy_read_timeout 1h;
    proxy_send_timeout 1h;
}

proxy_buffering off 很重要,因为 Nginx 默认会缓冲代理响应,这会导致 SSE 事件被暂存,直到缓冲区填满或响应结束。规范要求服务器在 SSE 响应中发送 X-Accel-Buffering: no,Nginx 会遵循该响应头,因此正确的服务器会自行告知代理正确的处理方式。请务必设置该指令,因为这是你可控的部分。

proxy_read_timeout 的默认值为 60 秒。如果 subscriptions/listen 流静默时间超过该值,Nginx 会将其关闭,而非你的服务器。因此,日志显示进程正常,但客户端显示连接断开。请仅在 MCP 的 location 块中提高该值,不要在整个服务器范围内设置。建议服务器在静默期间发送 SSE 注释行(以冒号开头的行)作为保活信号,这能防止中间件中断流连接。

Caddy 的配置需求较少。为了传输效率,它默认进行部分缓冲,并在响应包含 Content-Type: text/event-stream 时立即刷新,因此无需额外指令即可实现流式传输。

mcp.example.com {
	reverse_proxy 127.0.0.1:8080 {
		health_uri /healthz
		health_interval 10s
	}
}

注意健康检查的目标。不要将主动检查指向带有 GET 的 MCP 端点,因为仅实现此版本的服务器会对 GETDELETE 返回 405 Method Not Allowed,而 Caddy 的默认健康检查方法是 GET。这会导致代理将完全正常的后端标记为宕机。请为代理提供一个简单的路径(如 /healthz),并单独通过 POST 请求检查协议。

curl -sS https://mcp.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: server/discover' \
  -d '{"jsonrpc":"2.0","id":"health-1","method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'

带有 supportedVersions 列表的 200 表示进程已启动并能正常通信。带有 JSON-RPC 错误 -32601404 表示进程已启动,但未提供 server/discover 服务,而这是每个 2026-07-28 服务器必须实现的。带有 -32022400 表示你的检查器请求的版本当前构建不支持,这正是你在依赖升级后需要捕获的情况。开源版 Nginx 没有主动健康检查功能,因此请在 upstream 中使用被动 max_failsfail_timeout,并通过监控系统执行协议检查。

滚动重启现在只会导致正在进行的请求中断,不会产生其他影响。执行排空(drain)、等待 POST 请求完成、启动新进程,客户端会自动重试失败的请求。唯一会中断的是打开的 subscriptions/listen 流,因为该流是连接到特定进程的实时连接。无状态化消除了会话亲和性,但并未消除当前已打开流的连接亲和性,且没有任何路由规则能解决此问题。客户端可以区分两者:以空 subscriptions/listen 结果结束的流表示正常关闭,而未以该结果结束的流则表示连接断开,客户端可将其视为重新连接的触发条件。

缓存首次变得可行。列表方法的响应现在包含 ttlMscacheScope,且 cacheScope: "public" 告知共享中间件可以缓存该响应。这之所以安全,是因为列表结果不再随连接而变化,这是移除会话后的直接结果。

为什么在没有会话时身份验证方式会发生变化

在使用会话时,人们倾向于在 initialize 进行一次身份验证,随后将会话 ID 作为后续所有操作的凭证。以这种方式使用的会话 ID 是一种不限受众、没有过期时间且无法撤销的持有者凭证,由您自己的服务器签发。移除会话机制后,这种快捷方式也随之消失,取而代之的是更严格的验证流程。

受保护的 MCP 服务器充当 OAuth 2.1 资源服务器。客户端发出的每个 HTTP 请求都必须携带 Authorization: Bearer <access token>,服务器会在每次请求时验证该令牌。验证过程包含受众检查:根据 RFC 8707(OAuth 2.0 资源指示器),服务器必须确认令牌是专门为其签发的,不得接受或转发用于其他用途的令牌。客户端通过发送带有服务器规范 URI 的 resource 参数来请求正确的受众。

发现过程基于质询机制。当请求到达且没有可用令牌时,服务器会响应 401 Unauthorized

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

客户端读取 resource_metadata,获取该文档(RFC 9728,OAuth 2.0 受保护资源元数据,MCP 服务器必须实现此标准),找到授权服务器并执行流程。如果有效令牌的权限不足,服务器将返回 403 Forbidden,并附带 error="insufficient_scope" 以及该操作所需的范围(scopes)。

这对您的运行方式产生两个影响。令牌验证现在发生在每次请求时,而非每个会话仅执行一次,因此每次调用都进行一次到内省端点的网络往返会增加延迟:建议优先使用可通过签名、受众和过期时间进行本地验证的令牌,或者根据令牌对验证结果进行短时间的缓存。此外,由于没有会话来维持身份,授权必须在每次调用时根据令牌计算得出。这比会话模型更严谨,且符合将凭证排除在代理进程之外的通用实践,相关内容详见 将密钥排除在 AI 代理之外

此版本说明了什么,以及未说明什么

以上内容仅描述 2026-07-28 版本。它并不描述 MCP 的长期状态,也不适用于您去年部署的服务器。

2025-11-25 及更早版本的客户端与服务器仍使用握手模型。规范将这些版本称为旧版(legacy),并将基于每请求元数据的版本称为现代版(modern)。如果仅支持此版本的服务器遇到旧版客户端,应在 MCP 端点上向 GETDELETE 回复 405 Method Not Allowed,忽略任何 Mcp-Session-Id 标头(不生成也不回显),并忽略 Last-Event-ID,因为流不支持恢复。双模服务器可在同一端点同时提供两种服务:携带现代 _meta 的请求以无状态方式处理,而 initialize 请求则选择旧版会话语义。

因此,在信任本文档前,请先检查版本字符串。如果您的 SDK 仍发送 initialize,则会话对您的部署而言依然存在,上述与会话相关的问题仍需您自行管理。客户端侧亦是如此:您本地的代理进程(例如 在 VPS 上运行编码代理 中的设置)仅在其使用的库支持现代版本时,才具备此意义上的无状态性。请读取您的运行时协商出的版本,查阅对应的规范版本,并将本页面视为对特定版本的描述,而非对协议整体的描述。

FAQ

无状态 MCP 服务器是否意味着我无法存储任何数据?

不是。无状态描述的是协议,而非您的应用程序。数据库、队列和缓存的使用方式与以往完全相同。不同之处在于,跨多个调用的状态必须通过客户端在每次请求中传递的显式标识符来引用,例如工具参数中由服务器生成的句柄。您不能做的是从连接中推断上下文:规范规定,服务器不得依赖同一连接上的先前请求来建立功能、协议版本或客户端身份,因为每个请求都会在 _meta 中提供这些信息。

我是否仍然需要在负载均衡器上配置会话保持(Sticky Sessions)?

普通请求不需要。根据 2026-07-28 版本,每个 POST 请求都携带其自身的协议版本、功能和凭据,因此任何副本都可以响应任何请求,轮询(round-robin)调度即可满足需求。唯一长连接的内容是 subscriptions/listen 响应流,它是一个指向单个进程的开放连接。当该进程结束时,连接随之终止,客户端会重新发送 subscriptions/listen 以重新建立连接。这是连接生命周期问题,而非会话亲和性问题,任何路由规则都无法阻止它。

Mcp-Session-Id 和 HTTP GET 流发生了什么变化?

两者均已在 2026-07-28 版本中根据 SEP-2567 和 SEP-2575 移除。仅实现此版本的服务器应在 MCP 端点上对 405 Method Not AllowedGET 以及 DELETE 的请求返回 405 Method Not Allowed,并应忽略 Mcp-Session-Id 标头,而不是将其回显。服务器发起的变更通知现在通过 subscriptions/listen 请求的响应流传输,而不是通过独立的 GET 流。必须继续为旧版客户端提供服务的服务器,应在实现此版本的同时保留旧版本的行为。

如何在没有握手的情况下对 MCP 服务器进行健康检查?

使用两个层级。将代理的活动检查指向您的应用程序提供的普通 HTTP 路径,因为对 MCP 端点执行 GET 会正确返回 405,这会导致健康的后端被标记为宕机。然后,通过发送 server/discover 的 POST 请求来检查协议本身(每个 2026-07-28 服务器都必须实现此方法),并确认响应为 HTTP 200 且列出了您的客户端所使用的协议版本。如果收到 404 且包含 JSON-RPC 错误 -32601,则表示进程正在运行但未提供该方法;如果收到 400 且包含 -32022,则表示该构建版本不支持您请求的协议版本。