Streamable HTTP 在协议版本 2025-03-26 中引入,作为协议版本 2024-11-05 中 HTTP+SSE 传输 的替代方案。
在 Streamable HTTP 传输中,服务器作为一个独立进程运行,能够处理多个客户端连接。简而言之:
- 服务器暴露一个单一的 HTTP 端点(MCP endpoint),接受 POST。
- 客户端将每个 JSON-RPC 请求或通知作为各自独立的 HTTP POST 发送。
- 服务器对每个请求返回单个 JSON 对象,或者返回一个作用域限定到该请求的 Server-Sent Events(SSE)流,该流携带与请求相关的通知,随后是最终响应。
- 服务器到客户端的交互(sampling、elicitation、roots)作为输入请求嵌入到结果中,遵循 Multi Round-Trip Requests (MRTR)(SEP-2322)。
- 长生命周期的变更通知(例如列表变更和资源更新)通过
subscriptions/listen请求的响应流传递。
https://example.com/mcp 的 URL。
安全性与端点
在实现 Streamable HTTP 传输时:- 服务器 MUST 验证所有传入连接上的
Origin标头,以防止 DNS rebinding 攻击。- 如果存在
Origin标头且无效,服务器 MUST 返回 HTTP 403 Forbidden。HTTP 响应体 MAY 包含一个没有id的 JSON-RPC 错误响应。
- 如果存在
- 在本地运行时,服务器 SHOULD 只绑定到 localhost(127.0.0.1),而不是所有网络接口(0.0.0.0)。
- 服务器 SHOULD 对所有连接实施适当的身份验证。
发送消息
客户端发送的每条 JSON-RPC 消息 MUST 都是到 MCP endpoint 的一个新的 HTTP POST 请求。- 客户端 MUST 使用 HTTP POST 发送 JSON-RPC 消息。
- 客户端 MUST 包含一个
Accept标头,列出application/json和text/event-stream作为受支持的内容类型。 - 客户端 MUST 在每个 POST 请求中包含 请求元数据标头。
- HTTP POST 的主体 MUST 是单个 JSON-RPC request 或 notification。客户端 MUST NOT 发送 JSON-RPC responses。
- 如果主体是 JSON-RPC notification:
- 如果服务器接受它,服务器 MUST 返回 HTTP 状态码
202 Accepted,且没有响应体。 - 如果服务器无法接受它,它 MUST 返回一个 HTTP 错误状态码(例如
400 Bad Request)。HTTP 响应体 MAY 包含一个没有id的 JSON-RPC 错误响应。
- 如果服务器接受它,服务器 MUST 返回 HTTP 状态码
- 如果主体是 JSON-RPC request,服务器 MUST 返回
Content-Type: application/json(单个 JSON 对象)或Content-Type: text/event-stream(SSE 响应流)之一。客户端 MUST 同时支持两者。
接收消息
当服务器返回 SSE 响应流 (Content-Type: text/event-stream)时:
- 服务器 MAY 发送 JSON-RPC notifications——例如
notifications/progress或notifications/message— 在最终响应之前发送。这些通知 MUST 与发起该通知的客户端请求相关联。 - 服务器 MUST NOT 在此流上发送独立的 JSON-RPC requests。
服务器到客户端的交互(sampling、elicitation、list-roots)作为输入请求嵌入到
InputRequiredResult中,遵循 MRTR(SEP-2322),而不是作为单独的请求在此流或任何其他流上发送。这是对协议版本2025-03-26至2025-11-25中 Streamable HTTP 的一项更改;在这些版本中,服务器可以在 SSE 流上发送此类请求。 - 最终的 JSON-RPC response SHOULD 终止该流。
subscriptions/listen
request。服务器的响应本身是一个保持打开的 SSE 流,并
投递客户端选择订阅的变更通知(例如
notifications/tools/list_changed 或 notifications/resources/updated)。
诸如 notifications/progress 和
notifications/message 之类按请求作用域的通知 不会 在 listen 流上投递——它们
只会在其所关联请求的响应流中传输。
在启动 SSE 流时,服务器 SHOULD 在 HTTP 响应中包含
X-Accel-Buffering: no 头。这会指示反向代理(例如 nginx)禁用响应缓冲,确保 SSE 事件能够立即传递给客户端,而不是被保留在缓冲区中。没有该头时,代理可能会在发送给客户端之前累积消息,从而引入不必要的延迟,并可能破坏 SSE 通信的实时特性。
对于长生命周期流——尤其是
subscriptions/listen 响应流——建议服务器
定期发送一行 SSE 注释(以冒号开头的行,例如 :\r\n)作为保活。这样可以避免在空闲期没有通知流动时,连接被中间代理或客户端空闲超时关闭。根据 SSE 规范,任何以冒号开头的行都是不携带事件数据的注释;客户端必须忽略此类行,且不得将其视为格式错误的输入。Last-Event-ID 恢复 SSE 流。
消息流
以下图示展示了单个 MCP endpoint 上的消息流。 请求与响应。 每个请求都是一个独立的 POST;服务器会针对每个请求自行决定返回单个 JSON 对象还是 SSE 流: 服务器到客户端交互(MRTR)。 当服务器需要来自客户端的输入——sampling、elicitation 或 roots——它不会发送自己的 JSON-RPC 请求。它会返回一个包含inputRequests 的 InputRequiredResult,而客户端会携带匹配的 inputResponses 重试原始请求(参见 Multi Round-Trip Requests):
变更通知。 希望接收服务器发起的变更通知的客户端会使用
subscriptions/listen 打开一个长生命周期流;响应流保持
打开状态,并且只携带客户端选择接收的通知类型:
取消
关闭 SSE 响应流 必须 被服务器视为该请求的取消。由于 每个请求都有自己的响应流,因此传输级断开是明确无歧义的。服务器 应该 尽快停止已取消请求的工作,并且 不得 为其发送任何后续消息。完整规则请参见 取消。请求元数据
Streamable HTTP 传输会将选定的 JSON-RPC 正文字段镜像到 HTTP 头中,以便中介层(负载均衡器、网关、可观测性 工具)无需解析正文即可路由和检查请求。协议版本头
发送到 MCP endpoint 的每个 POST 请求 必须 包含一个MCP-Protocol-Version 头。
例如:MCP-Protocol-Version: 2026-07-28
该头值 必须 与请求正文的
_meta 中携带的 io.modelcontextprotocol/protocolVersion 字段匹配。
如果这些值不匹配,服务器 必须 拒绝该请求,
返回 400 Bad Request 和一个 HeaderMismatch JSON-RPC 错误
(见 Server Validation)。
如果服务器未实现所请求的协议版本(无论该版本对服务器而言是未知版本,还是服务器已知但选择不支持的版本),它 必须 返回 400 Bad Request 和一个
UnsupportedProtocolVersionError,列出其支持的版本。请参见
Versioning: Protocol Version Negotiation 了解协商流程。
如果服务器未实现所请求的 RPC 方法,它 必须 返回
404 Not Found 和一个错误码为 -32601
(Method not found)的 JSON-RPC 错误。该 JSON-RPC 错误体将此情形与由不托管现代 MCP endpoint 的旧版 HTTP+SSE 服务器返回的 404 区分开来(见 Backward Compatibility)。
支持早于 2025-06-18 的协议版本客户端(该版本未定义 MCP-Protocol-Version 头)的服务器 可以 将缺少该头的请求视为协议版本 2025-03-26。不支持此类客户端的服务器 必须 按照 Server Validation 拒绝缺少该头的请求。
标准请求头
| 头名称 | 来源字段 | 适用范围 |
|---|---|---|
Mcp-Method | method | 所有请求 |
Mcp-Name | params.name or params.uri | tools/call、resources/read、prompts/get 请求 |
Mcp-Name 的来源值无法安全地表示为普通 ASCII
头值,客户端 必须 按照 值编码 中描述的 Base64 哨兵格式对其进行编码。
tools/call 请求:
resources/read 请求:
来自工具参数的自定义头
MCP 服务器 可以 使用参数的 schema 中的inputSchema 内的
x-mcp-header 扩展属性,将特定工具参数映射到
HTTP 头中。详见
工具定义
,了解如何为工具参数添加注解。
虽然服务器使用 x-mcp-header 是可选的,但客户端 必须
支持此功能。当服务器的工具定义包含
x-mcp-header 注解时,符合规范的客户端 必须 将指定的参数值映射到 HTTP 头中。
Schema 扩展
x-mcp-header 属性指定用于构造
头名称 Mcp-Param-{name} 的名称部分。
x-mcp-header 值的约束:
- 不得 为空
- 必须 符合 HTTP field-name token 语法(
1*tchar,RFC 9110 第 5.1 节) - 不得 包含控制字符,包括回车符(CR,
\r) 或换行符(LF,\n) - 必须 在
inputSchema中所有x-mcp-header值之间大小写不敏感地唯一 - 必须 仅应用于原始类型(integer、string、boolean)的参数。不允许类型为
number的参数。 整数值 必须 位于 JavaScript 的安全范围内 (−253+1 到 253−1) - 必须 仅应用于从 schema 根 静态可达 的属性:即仅通过由
properties键组成的链可达。该链 不得 穿过items(或任何 其他数组关键字)、组合关键字(oneOf、anyOf、allOf、not)、 条件关键字(if/then/else),或$ref。只要链中的每一步都是properties键,就允许嵌套对象属性。任何其他位置的x-mcp-header注解都会使该注解——从而使工具定义——无效。
properties 键链)。如果调用参数中该路径没有值,则省略该头。
使用 Streamable HTTP 传输的客户端 必须 拒绝任何 x-mcp-header 值违反这些约束的工具定义。拒绝意味着客户端 必须 将该无效工具从 tools/list 的结果中排除。客户端 应该 在拒绝工具定义时记录警告,包括工具名称和被拒绝的原因。这样可以确保单个格式错误的工具定义不会阻止其他有效工具的使用。使用其他传输方式(例如 stdio)的客户端 可以 完全忽略 x-mcp-header 注解。
工具定义示例:
值编码
客户端 必须 在将参数值包含到 HTTP 头之前对其进行编码,以确保安全传输并防止注入攻击。 类型转换:将参数值转换为其字符串表示形式:string: 原样使用该值integer: 转换为十进制字符串表示形式(例如42、-7)boolean: 转换为小写"true"或"false"
Mcp-Name 头值。工具和
提示名称仅 应该 受限于对头部安全的字符,因此超出安全集合的名称(或资源 URI)将按如下方式传递:
=?base64? 和后缀 ?= 表示该值已
使用 Base64 编码。这些标记区分大小写,并且 必须 以
所示的精确形式出现(小写)。需要检查这些值的服务器和中介组件 必须 进行相应解码。特别是,服务器 必须
在将编码后的 Mcp-Name 或 Mcp-Param-{Name} 值与
请求正文中的相应值进行比较之前先对其解码,
参见 服务器校验。
为避免歧义,客户端 必须 还要对任何匹配哨兵模式的纯 ASCII 值进行 Base64 编码(即,以 =?base64? 开头并以 ?= 结尾)。
编码示例:
| 原始值 | 原因 | 编码后的头值 |
|---|---|---|
"us-west1" | 纯 ASCII | Mcp-Param-Region: us-west1 |
"Hello, 世界" | 包含非 ASCII | Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?= |
" padded " | 前导/尾随空格 | Mcp-Param-Text: =?base64?IHBhZGRlZCA=?= |
"line1\nline2" | 包含换行符 | Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?= |
"=?base64?literal?=" | 匹配哨兵模式 | Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?= |
客户端行为
当通过 HTTP 传输构造tools/call 请求时,客户端
必须:
- 从请求正文中提取任何标准头的值(例如,
method、params.name、params.uri)。 - 将
Mcp-Method头以及(如适用)Mcp-Name头附加到 请求中。 - 检查工具的
inputSchema中标记了x-mcp-header的属性,并提取每个已注解属性精确 属性路径上的值;如果没有值则省略该头(见 Schema Extension)。 - 按照 值编码 规则对这些值进行编码。
- 将
Mcp-Param-{Name}: {Value}头附加到请求中。
客户端 必须 使用最近一次获取到的
该工具
inputSchema 来构造 Mcp-Param-* 头。若客户端从未获取过
该工具的 inputSchema,应该 在不带 Mcp-Param-*
头的情况下发送请求。如果服务器因为必需的 Mcp-Param-*
头缺失或与正文不匹配而拒绝请求,客户端 应该 调用
tools/list 以获取当前的 inputSchema,然后使用合适的头重试原始请求。客户端 可以 通过其他方式预先加载工具
定义(例如来自之前的会话或
配置),从而在未先调用 tools/list
的情况下也能发送头。服务器对自定义头的行为
不识别Mcp-Param-{Name} 头的中介服务器
必须 转发它,并在其他方面忽略它,正如
HTTP 语义 RFC 所要求的那样。
服务器 必须 拒绝包含无效字符的、被识别的 Mcp-Param-{Name} 头
(见 值编码)。
任何处理消息正文的服务器 必须 验证编码后的
头值(如果是 Base64 编码,则在解码后)与正文中的相应
值匹配。若任何验证失败,服务器 必须 返回
HTTP 状态 400 Bad Request,并使用 JSON-RPC 错误码
-32020(HeaderMismatch)拒绝该请求。
| 场景 | 客户端行为 | 服务器行为 |
|---|---|---|
| 提供了参数值 | 客户端必须包含该头 | 服务器必须验证头与正文匹配 |
参数值为 null | 客户端必须省略该头 | 服务器不得期望该头 |
| 参数未出现在参数中 | 客户端必须省略该头 | 服务器不得期望该头 |
| 客户端省略头但正文中有值 | 不符合规范的客户端 | 服务器必须拒绝该请求 |
大小写敏感性
头名称(在 RFC 9110 中称为“字段名”) 是不区分大小写的。客户端和服务器 MUST 对头名称使用不区分大小写的 比较。头_值_(例如方法名)是 区分大小写的。服务器验证
处理请求正文的服务器 MUST 拒绝那些其 头中指定的值与请求正文中的相应值不匹配的请求。这可防止在 网络中不同组件依赖不同事实来源时出现潜在安全漏洞 (例如,负载均衡器根据头值进行路由,而 MCP 服务器 根据正文值执行)。在验证整数参数值时,服务器 SHOULD 以数值方式而不是字符串方式比较头值和正文值(例如,
42.0 和 42 被视为相等)。400 Bad Request,并且 MUST 使用以下错误码
包含一个 JSON-RPC 错误响应:
| Code | Name | Description |
|---|---|---|
-32020 | HeaderMismatch | HTTP 头与请求正文中的相应值不匹配,或者缺少/格式错误的必需头。 |
- 缺少必需的标准头(
MCP-Protocol-Version、Mcp-Method、Mcp-Name)。 - 头值与对应的请求正文值不匹配。
对于允许使用 Base64 哨兵编码的头(
Mcp-Name和Mcp-Param-{Name}),服务器 MUST 在将编码值与正文值比较前先将其解码(见 值编码)。 - 头值包含无效字符。
中介层 MUST 在验证失败时返回适当的 HTTP 错误状态(例如,
400 Bad Request),但不要求返回
JSON-RPC 错误响应。实施基于镜像头的策略的中介层(例如按租户路由
或限流)SHOULD 验证
MCP-Protocol-Version
头所指示的版本是否需要进行头-正文验证。如果该
版本较旧或缺少该头,中介层 SHOULD 拒绝
该请求,而不是信任未经验证的头值。向后兼容性
客户端可以通过首先尝试现代请求来检测对现代 MCP 版本(如请求元数据所示)以及需要initialize 握手的旧版本,以判断服务器实现的是哪个时代。若收到 400 Bad Request,客户端应当在回退之前检查响应体:现代服务器也会对 UnsupportedProtocolVersionError、MissingRequiredClientCapabilityError 和请求头验证失败使用 400。
- 如果响应体包含可识别的现代 JSON-RPC 错误,则说明服务器支持现代 MCP 版本——应根据公布的
supported版本重试,或修正请求,而不是回退。 - 如果响应体为空,或不包含可识别的现代 JSON-RPC 错误,则回退到
initialize,并在后续请求中继续使用旧版本。
更早的 Streamable HTTP 修订版
协议版本2025-03-26 到 2025-11-25 也使用了 Streamable HTTP 传输,但形式不同:服务器可以通过 Mcp-Session-Id 头分配会话(通过 HTTP DELETE 终止),客户端可以通过 HTTP GET 打开单独的 SSE 流以接收服务器发起的消息,服务器可以在 SSE 流上发送 JSON-RPC requests,并且流可以通过 Last-Event-ID 恢复。这些机制都不属于本修订版。
只支持此修订版并接收到来自旧客户端的此类流量的服务器应当如下响应:
- 对 MCP 端点的 HTTP GET 或 DELETE:返回
405 Method Not Allowed。 - 请求中的
Mcp-Session-Id头:忽略它;不要生成或回显会话 ID。 Last-Event-ID头:忽略它;该流不可恢复。
HTTP+SSE 传输(2024-11-05)
客户端和服务器可以通过以下方式保持与已弃用的 HTTP+SSE 传输(来自协议版本 2024-11-05)的向后兼容性: 希望支持旧客户端的服务器应当:- 继续同时提供旧传输的 SSE 和 POST 端点,
以及为 Streamable HTTP 传输定义的新 “MCP 端点”。
- 也可以将旧的 POST 端点与新的 MCP 端点合并,但这可能会引入不必要的复杂性。
- 从用户处接受一个 MCP 服务器 URL,该 URL 可能指向使用旧传输或新传输的服务器。
- 对该服务器 URL 发起 POST 请求,并使用上文定义的
Accept头:- 如果成功,客户端可以假定这是支持 新的 Streamable HTTP 传输的服务器。
- 如果失败并返回 HTTP 状态码
400 Bad Request、404 Not Found或405 Method Not Allowed,且响应体不是一个 可识别的现代 JSON-RPC 错误(现代服务器会针对不支持的版本、未知方法或头验证失败返回此类错误):- 向服务器 URL 发起 GET 请求,期望这会打开
一个 SSE 流,并将
endpoint事件作为第一个事件返回。 - 收到
endpoint事件后,客户端可以假定这是 运行旧 HTTP+SSE 传输的服务器,并应在后续所有通信中使用该 传输。
- 向服务器 URL 发起 GET 请求,期望这会打开
一个 SSE 流,并将