Streamable HTTP 在 2025-03-26 协议版本中引入,作为 2024-11-05 协议版本中 HTTP+SSE 传输 的替代方案。
在 Streamable HTTP 传输中,服务器作为一个独立进程运行,可以处理多个客户端连接。概览如下:
- 服务器暴露单个 HTTP 端点(MCP 端点),并接受 POST。
- 客户端将每个 JSON-RPC 请求或通知作为各自独立的 HTTP POST 发送。
- 服务器对每个请求的响应要么是单个 JSON 对象,要么是一个仅针对该请求范围的 Server-Sent Events(SSE)流,其中包含与请求相关的通知,随后是最终响应。
- 服务器到客户端的交互(采样、询问、roots)会作为输入请求嵌入结果中,遵循 多轮往返请求(MRTR)(SEP-2322)。
- 长生命周期的变更通知(例如列表变更和资源更新)会通过
subscriptions/listen请求的响应流传递。
https://example.com/mcp 的 URL。
安全与端点
在实现 Streamable HTTP 传输时:- 服务器 必须 在所有传入连接上验证
Origin标头 以防止 DNS 重绑定攻击。- 如果
Origin标头存在且无效,服务器 必须 响应 HTTP 403 Forbidden。HTTP 响应正文 可以 包含一个 不含id的 JSON-RPC 错误响应。
- 如果
- 在本地运行时,服务器 应当 仅绑定到 localhost (127.0.0.1),而不是所有网络接口(0.0.0.0)。
- 服务器 应当 为所有连接实现适当的身份验证。
发送消息
客户端发送的每个 JSON-RPC 消息必须是一个新的 HTTP POST 请求,发送到 MCP 端点。- 客户端必须使用 HTTP POST 发送 JSON-RPC 消息。
- 客户端必须包含一个
Accept请求头,其中同时列出application/json和text/event-stream作为受支持的内容类型。 - 客户端必须在每个 POST 请求中包含请求元数据头 。
- HTTP POST 的请求体必须是单个 JSON-RPC 请求 或 通知。客户端不得发送 JSON-RPC 响应。
- 如果请求体是 JSON-RPC 通知:
- 如果服务器接受它,服务器必须返回 HTTP 状态码
202 Accepted,且没有响应体。 - 如果服务器无法接受它,必须返回一个 HTTP 错误状态码
(例如,
400 Bad Request)。HTTP 响应体可以包含一个没有id的 JSON-RPC 错误响应。
- 如果服务器接受它,服务器必须返回 HTTP 状态码
- 如果请求体是 JSON-RPC 请求,服务器必须返回以下任一内容:
Content-Type: application/json(单个 JSON 对象)或Content-Type: text/event-stream(SSE 响应流)。客户端 必须同时支持这两种形式。
此核心协议修订版未定义通过 Streamable HTTP 从客户端到服务器的
notifications。核心协议中唯一由客户端发送的通知
notifications/cancelled
仅用于 stdio 传输;在
Streamable HTTP 上,关闭 SSE 响应流本身就是取消信号,并且不期望
notifications/cancelled 消息(见
Cancellation)。上面的通知规则描述的是通知 POST 的传输机制;本修订版未定义通知 POST 的请求头要求。接收消息
当服务器返回 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
请求可获得长生命周期的通知流。服务器的响应本身就是一个 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 端点上的消息流。 请求与响应。 每个请求都是独立的 POST;服务器会针对每个请求选择返回单个 JSON 对象还是 SSE 流: 服务器到客户端交互(MRTR)。 当服务器需要来自客户端的输入——采样、征询或根信息时,它不会发送自己的 JSON-RPC 请求。它会返回一个包含inputRequests 的 InputRequiredResult,然后客户端使用匹配的 inputResponses 重试原始请求(参见 多轮往返请求):
变更通知。 想要接收服务器发起的变更通知的客户端,会使用 subscriptions/listen 打开一个长连接流;响应流会保持打开状态,并且只承载客户端选择订阅的通知类型:
取消
关闭 SSE 响应流必须被服务器视为该请求的取消。由于每个请求都有其自己的响应流,因此传输层级的断开连接是明确无歧义的。服务器应该尽快停止对已取消请求的工作,并且不得再为其发送任何进一步的消息。完整规则请参见 取消。请求元数据
Streamable HTTP 传输会将选定的 JSON-RPC 正文字段镜像到 HTTP 头中,以便中间件(负载均衡器、网关、可观测性 工具)无需解析正文即可路由和检查请求。协议版本头
每个发送到 MCP 端点的 POST 请求必须包含一个MCP-Protocol-Version 头。
例如:MCP-Protocol-Version: 2026-07-28
该头的值必须与请求体 _meta 中携带的
io.modelcontextprotocol/protocolVersion 字段匹配。如果这些值不匹配,服务器必须拒绝该请求,
返回 400 Bad Request 和一个 HeaderMismatch JSON-RPC 错误
(参见 服务器验证)。
如果服务器未实现请求的协议版本(无论该版本对服务器来说是未知版本,还是服务器已知但选择不支持的版本),它必须返回 400 Bad Request,并返回一个
UnsupportedProtocolVersionError,其中列出其支持的版本。有关协商流程,请参见
版本控制:协议版本协商。
如果服务器未实现请求的 RPC 方法,它必须返回
404 Not Found 和一个代码为 -32601
(Method not found)的 JSON-RPC 错误。该 JSON-RPC 错误主体将此情况与由不承载现代 MCP 端点的旧版 HTTP+SSE 服务器返回的 404 区分开来(参见 向后兼容性)。
支持实现早于 2025-06-18 协议版本客户端的服务器(这些版本未定义 MCP-Protocol-Version 头)可以
将省略该头的请求视为协议版本 2025-03-26。不支持此类客户端的服务器必须根据 服务器验证 拒绝不带该头的请求。
标准请求头
这些标头对于符合规范是必需的。
如果
Mcp-Name 的源值无法安全地表示为普通 ASCII
标头值,客户端必须使用 值编码 中描述的 Base64 哨兵格式对其进行编码。
tools/call 请求:
resources/read 请求:
来自工具参数的自定义头
MCP 服务器 MAY 指定特定的工具参数,通过在工具inputSchema 中该参数的 schema 里使用 x-mcp-header 扩展属性,将其映射到
HTTP 头中。有关如何为工具参数添加注解的详细信息,请参见
工具定义。
虽然服务器使用 x-mcp-header 是可选的,但客户端 MUST
支持此功能。当服务器的工具定义包含 x-mcp-header 注解时,符合规范的客户端 MUST 将
指定的参数值映射到 HTTP 头中。
Schema 扩展
x-mcp-header 属性指定用于构造头名称 Mcp-Param-{name} 的名称部分。
x-mcp-header 值的约束:
- MUST NOT 为空
- MUST 匹配 HTTP field-name token 语法(
1*tchar,RFC 9110 第 5.1 节) - MUST NOT 包含控制字符,包括回车符(CR,
\r) 或换行符(LF,\n) - 在
inputSchema中所有x-mcp-header值之间,MUST 以不区分大小写的方式保持唯一 - MUST 仅应用于原始类型(integer、string、boolean)的参数。不允许 type 为
number。整数值 MUST 位于 JavaScript 的安全范围内 (−253+1 到 253−1) - MUST 仅应用于从 schema 根节点 静态可达 的属性:只能通过仅由
properties键组成的链路到达。该链路 MUST NOT 穿过items(或任何 其他数组关键字)、组合关键字(oneOf、anyOf、allOf、not)、条件关键字(if/then/else)或$ref。只要链路中的每一步都是properties键,就允许嵌套对象属性。任何其他位置出现的x-mcp-header注解都会使该 注解——从而使工具定义——无效。
properties 键链)上的实例值。如果调用参数中该路径上没有值,
则省略该头。
使用 Streamable HTTP 传输的客户端 MUST 拒绝任何存在
违反这些约束的 x-mcp-header 值的工具定义。拒绝意味着客户端 MUST 将无效工具从 tools/list 的结果中排除。客户端 SHOULD 在拒绝工具定义时记录警告,包括
工具名称和拒绝原因。这可确保单个格式错误的工具定义不会阻止其他有效工具被使用。
使用其他传输方式(例如 stdio)的客户端 MAY 完全忽略 x-mcp-header
注解。
工具定义示例:
值编码
客户端 MUST 在将参数值包含到 HTTP 头之前对其进行编码,以确保安全传输并防止注入攻击。 类型转换:将参数值转换为其字符串表示形式:string:按原样使用该值integer:转换为十进制字符串表示形式(例如,42、-7)boolean:转换为小写"true"或"false"
Mcp-Name 头值。工具和
prompt 名称仅在头安全字符上受到 SHOULD 级约束,因此超出安全集的名称(或资源 URI)应按如下方式携带:
=?base64? 和后缀 ?= 表示该值已进行
Base64 编码。这些标记区分大小写,且 MUST 按所示
形式精确出现(小写)。需要检查这些值的服务器和中间件 MUST
相应地对其解码。特别是,在
服务器验证期间,服务器 MUST
在将编码后的 Mcp-Name 或 Mcp-Param-{Name} 值与相应请求体值进行比较之前对其解码。
为避免歧义,客户端 MUST 还要对任何匹配哨兵模式(即以 =?base64?
开头并以 ?= 结尾)的纯 ASCII 值进行 Base64 编码。
编码示例:
客户端行为
当通过 HTTP 传输构建tools/call 请求时,客户端
MUST:
- 从请求体中提取任何标准头的值(例如,
method、params.name、params.uri)。 - 将
Mcp-Method头以及(如适用)Mcp-Name头附加到 请求中。 - 检查工具的
inputSchema中带有x-mcp-header标记的属性,并提取每个已注解属性精确 属性路径上的值;如果没有值,则省略该头(见 Schema 扩展)。 - 按照 值编码 规则对这些值进行编码。
- 将
Mcp-Param-{Name}: {Value}头附加到请求中。
Mcp-Param-* 头缺失或与请求体不匹配而返回
HeaderMismatch 错误,客户端
SHOULD 调用 tools/list 以检查工具
inputSchema 是否有变更,然后使用相应的头重试原始请求。
自定义头的服务器行为
不识别Mcp-Param-{Name} 头的中间服务器
MUST 按照 HTTP 语义 RFC 的要求转发该头并忽略其内容。
服务器 MUST 拒绝包含无效字符的、被识别的 Mcp-Param-{Name} 头(见
值编码)。
任何处理消息体的服务器 MUST 验证已编码
的头值(若为 Base64 编码,则在解码后)与请求体中的相应值一致。若任何验证失败,服务器 MUST 以
400 Bad Request HTTP 状态和 JSON-RPC 错误码
-32020(HeaderMismatch)拒绝请求。
大小写敏感性
标头名称(在 RFC 9110 中称为“字段名称”) 是不区分大小写的。客户端和服务器 MUST 对标头名称使用不区分大小写的 比较。标头 值(例如方法名称) 是区分大小写的。服务器验证
处理请求体的服务器必须拒绝那些头部中指定的值与请求体中对应值不匹配的请求。这可以防止网络中不同组件依赖不同“事实来源”时产生潜在的安全漏洞(例如,负载均衡器根据头部值进行路由,而 MCP 服务器根据请求体值执行)。在验证整数参数值时,服务器应当以数值而不是字符串来比较头部值和请求体值(例如,
42.0 和 42 被视为相等)。400 Bad Request,并且必须使用以下错误代码包含一个 JSON-RPC 错误响应:
此错误代码分配自 MCP 规范为协议定义错误保留的子范围。参见
错误代码。
错误响应示例:
- 缺少必需的标准头部(
MCP-Protocol-Version、Mcp-Method、Mcp-Name)。 - 头部值与对应的请求体值不匹配。对于允许 Base64 哨兵编码的头部(
Mcp-Name和Mcp-Param-{Name}),服务器必须在将编码值与请求体值进行比较之前先对其进行解码(参见 值编码)。 - 头部值包含无效字符。
中介层必须在验证失败时返回适当的 HTTP 错误状态(例如,
400 Bad Request),但不要求返回 JSON-RPC 错误响应。基于镜像头部执行策略的中介层(例如,按租户进行路由
或速率限制)应当验证
MCP-Protocol-Version
头部所指示的版本是否要求进行头部—请求体验证。如果版本较旧或头部缺失,中介层应当拒绝该请求,而不是信任未经验证的头部值。向后兼容性
同时支持现代(按请求元数据)MCP 版本和需要initialize 握手的旧版的客户端,MAY 通过先尝试一次现代请求来检测服务器实现的是哪个时代。遇到 400 Bad Request 时,客户端在回退之前 SHOULD 检查响应体:现代服务器对于
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 请求,并且流可以通过 Last-Event-ID 恢复。这些机制都不属于本修订版。
仅支持本修订版并收到来自旧客户端此类流量的服务器 SHOULD 如下响应:
- 对 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 流,并将