Streamable HTTP 在协议版本 2025-03-26 中引入,作为协议版本 2024-11-05 的 HTTP+SSE 传输的替代。
在 Streamable HTTP 传输中,服务器作为一个可以处理多个客户端连接的独立进程运作。概览:
- 服务器暴露一个接受 POST 的单一 HTTP 端点(MCP 端点)。
- 客户端将每个 JSON-RPC 请求或通知作为它自己的 HTTP POST 发送。
- 服务器以一个单一的 JSON 对象或一个限定于该请求的 Server-Sent Events(SSE)流回答每个请求,携带请求相关的通知,随后是最终响应。
- 服务器到客户端的交互(采样、征询、roots)根据多轮往返请求(MRTR)(SEP-2322)作为输入请求嵌入在结果中。
- 长期存在的变更通知(例如列表变更和资源更新)在一个
subscriptions/listen请求的响应流上投递。
https://example.com/mcp 这样的 URL。
安全与端点
在实现 Streamable HTTP 传输时:- 服务器**必须(MUST)**校验所有传入连接上的
Originheader 以防止 DNS 重绑定攻击。- 如果
Originheader 存在且无效,服务器**必须(MUST)以 HTTP 403 Forbidden 响应。HTTP 响应体可以(MAY)**由一个没有id的 JSON-RPC _错误响应_构成。
- 如果
- 在本地运行时,服务器**应当(SHOULD)**只绑定到 localhost(127.0.0.1),而不是所有网络接口(0.0.0.0)。
- 服务器**应当(SHOULD)**为所有连接实现适当的认证。
发送消息
客户端发送的每条 JSON-RPC 消息**必须(MUST)**是对 MCP 端点的一个新的 HTTP POST 请求。- 客户端**必须(MUST)**使用 HTTP POST 发送 JSON-RPC 消息。
- 客户端**必须(MUST)**包含一个
Acceptheader,将application/json和text/event-stream都列为受支持的内容类型。 - 客户端**必须(MUST)**在每个 POST 请求上包含请求元数据 header。
- HTTP POST 的消息体**必须(MUST)是单个 JSON-RPC 请求_或_通知。客户端不得(MUST NOT)**发送 JSON-RPC 响应。
- 如果消息体是一个 JSON-RPC 通知:
- 如果服务器接受它,服务器**必须(MUST)**返回 HTTP 状态码
202 Accepted且无消息体。 - 如果服务器无法接受它,它**必须(MUST)返回一个 HTTP 错误状态码(例如
400 Bad Request)。HTTP 响应体可以(MAY)**由一个没有id的 JSON-RPC _错误响应_构成。
- 如果服务器接受它,服务器**必须(MUST)**返回 HTTP 状态码
- 如果消息体是一个 JSON-RPC 请求,服务器**必须(MUST)返回
Content-Type: application/json(一个单一的 JSON 对象)或Content-Type: text/event-stream(一个 SSE 响应流)。客户端必须(MUST)**支持两者。
接收消息
当服务器返回一个 SSE 响应流(Content-Type: text/event-stream)时:
- 服务器**可以(MAY)在最终响应之前发送 JSON-RPC 通知——例如
notifications/progress或notifications/message。这些通知必须(MUST)**与发起的客户端请求相关。 - 服务器**不得(MUST NOT)**在此流上发送独立的 JSON-RPC 请求。服务器到客户端的交互(采样、征询、list-roots)根据 MRTR(SEP-2322)作为输入请求嵌入在一个
InputRequiredResult内,而不是作为单独的请求在此流或任何其他流上投递。这是相对于协议版本2025-03-26到2025-11-25中的 Streamable HTTP 的一个变更,在那些版本中服务器可以在 SSE 流上发送此类请求。 - 最终的 JSON-RPC 响应**应当(SHOULD)**终止该流。
subscriptions/listen 请求获得。服务器的响应本身是一个保持打开的 SSE 流,并投递客户端所选择加入的变更通知(例如 notifications/tools/list_changed 或 notifications/resources/updated)。诸如 notifications/progress 和 notifications/message 之类的请求范围通知不在监听流上投递——它们只在它们所关联的请求的响应流上流动。
在发起一个 SSE 流时,服务器**应当(SHOULD)**在 HTTP 响应中包含 X-Accel-Buffering: no header。这指示反向代理(例如 nginx)禁用响应缓冲,确保 SSE 事件立即被投递给客户端,而不是被保留在缓冲区中。没有此 header,代理可能在将消息发送给客户端之前累积它们,引入不希望的延迟并可能破坏 SSE 通信的实时性质。
对于长期存在的流——特别是
subscriptions/listen 响应流——鼓励服务器定期发出一个 SSE 注释行(以冒号开头的行,例如 :\r\n)作为保活(keep-alive)。这在没有通知流动的安静期间防止连接被中间方或客户端空闲超时关闭。根据 SSE 规范,任何以冒号开头的行都是不携带事件数据的注释;客户端必须忽略此类行,且不得将它们视为格式错误的输入。Last-Event-ID 实现的可恢复 SSE 流。
消息流
以下图表说明了单个 MCP 端点上的消息流。 请求与响应。 每个请求是它自己的 POST;服务器按请求选择是以一个单一的 JSON 对象还是一个 SSE 流响应: 服务器到客户端的交互(MRTR)。 当服务器需要来自客户端的输入时——采样、征询或 roots——它不发送它自己的 JSON-RPC 请求。它返回一个包含inputRequests 的 InputRequiredResult,客户端以匹配的 inputResponses 重试原始请求(参见多轮往返请求):
变更通知。 想要服务器发起的变更通知的客户端用 subscriptions/listen 打开一个长期存在的流;响应流保持打开并只携带客户端所选择加入的通知类型:
取消
服务器**必须(MUST)将关闭 SSE 响应流视为该请求的取消。因为每个请求都有它自己的响应流,传输级别的断开是明确无歧义的。服务器应当(SHOULD)尽快停止对被取消请求的工作,并不得(MUST NOT)**为它发送任何进一步的消息。完整规则参见取消。请求元数据
Streamable HTTP 传输将选定的 JSON-RPC 消息体字段镜像到 HTTP header 中,以便中间方(负载均衡器、网关、可观测性工具)可以在不解析消息体的情况下路由和检查请求。协议版本 header
对 MCP 端点的每个 POST 请求**必须(MUST)**包含一个MCP-Protocol-Version header。
例如:MCP-Protocol-Version: 2026-07-28
该 header 值**必须(MUST)与请求体 _meta 中携带的 io.modelcontextprotocol/protocolVersion 字段匹配。如果这些值不匹配,服务器必须(MUST)**以 400 Bad Request 和一个 HeaderMismatch JSON-RPC 错误拒绝该请求(参见服务器校验)。
如果服务器不实现所请求的协议版本(无论该版本对服务器是未知的,还是一个服务器选择不支持的已知版本),它**必须(MUST)**以 400 Bad Request 和一个列出其所支持版本的 UnsupportedProtocolVersionError 响应。协商流程参见版本管理:协议版本协商。
如果服务器不实现所请求的 RPC 方法,它**必须(MUST)**以 404 Not Found 和一个代码为 -32601(Method not found)的 JSON-RPC 错误响应。JSON-RPC 错误体将这种情况与一个不托管现代 MCP 端点的旧式 HTTP+SSE 服务器返回的 404 区分开来(参见向后兼容)。
一个支持实现早于 2025-06-18 的协议版本(它没有定义 MCP-Protocol-Version header)的客户端的服务器**可以(MAY)将一个省略该 header 的请求视为协议版本 2025-03-26。一个不支持此类客户端的服务器必须(MUST)**根据服务器校验拒绝没有该 header 的请求。
标准请求 header
这些 header 为合规性所必需(REQUIRED)。
如果
Mcp-Name 源值无法被安全地表示为一个纯 ASCII header 值,客户端**必须(MUST)**使用值编码中所述的 Base64 哨兵格式对它进行编码。
tools/call 请求:
resources/read 请求:
来自工具参数的自定义 header
MCP 服务器**可以(MAY)**使用工具inputSchema 内参数 schema 中的一个 x-mcp-header 扩展属性,指定特定的工具参数被镜像到 HTTP header 中。有关如何注解工具参数的详情,参见工具定义。
虽然对服务器而言 x-mcp-header 的使用是可选的,但客户端**必须(MUST)支持此特性。当服务器的工具定义包含 x-mcp-header 注解时,合规的客户端必须(MUST)**将指定的参数值镜像到 HTTP header 中。
Schema 扩展
x-mcp-header 属性指定用于构造 header 名 Mcp-Param-{name} 的名称部分。
对 x-mcp-header 值的约束:
- **不得(MUST NOT)**为空
- **必须(MUST)**匹配 HTTP 字段名 token 语法(
1*tchar,RFC 9110 第 5.1 节) - **不得(MUST NOT)**包含控制字符,包括回车(CR,
\r)或换行(LF,\n) - 在
inputSchema中的所有x-mcp-header值之间**必须(MUST)**大小写不敏感地唯一 - **必须(MUST)只应用于具有原始类型(integer、string、boolean)的参数。不允许类型为
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 键链)处的实例值。如果调用参数中该路径处不存在值,则省略该 header。
使用 Streamable HTTP 传输的客户端**必须(MUST)拒绝任何 x-mcp-header 值违反这些约束的工具定义。拒绝意味着客户端必须(MUST)将无效的工具从 tools/list 的结果中排除。客户端在拒绝一个工具定义时应当(SHOULD)记录一个警告,包括工具名和拒绝原因。这确保单个格式错误的工具定义不会阻止其他有效工具被使用。使用其他传输(例如 stdio)的客户端可以(MAY)**完全忽略 x-mcp-header 注解。
工具定义示例:
值编码
客户端**必须(MUST)**在将参数值包含到 HTTP header 中之前对它们进行编码,以确保安全传输并防止注入攻击。 类型转换:将参数值转换为其字符串表示:string:按原样使用该值integer:转换为十进制字符串表示(例如42、-7)boolean:转换为小写的"true"或"false"
Mcp-Name header 值。工具名和提示名仅被**应当(SHOULD)**约束为 header 安全的字符,因此一个在安全集之外的名称(或资源 URI)被携带为:
=?base64? 和后缀 ?= 表示该值是 Base64 编码的。这些标记是大小写敏感的,并**必须(MUST)完全按所示(小写)出现。需要检查这些值的服务器和中间方必须(MUST)**相应地解码它们。特别是,服务器在服务器校验期间将一个编码的 Mcp-Name 或 Mcp-Param-{Name} 值与对应的请求体值比较之前,**必须(MUST)**先解码它。
为避免歧义,客户端还**必须(MUST)**对任何匹配哨兵模式的纯 ASCII 值(即以 =?base64? 开头并以 ?= 结尾)进行 Base64 编码。
编码示例:
客户端行为
在通过 HTTP 传输构造一个tools/call 请求时,客户端必须(MUST):
- 从请求体中提取任何标准 header 的值(例如
method、params.name、params.uri)。 - 将
Mcp-Methodheader 以及(如果适用)Mcp-Nameheader 附加到请求。 - 检查工具的
inputSchema中标记有x-mcp-header的属性,并提取每个被注解属性的确切属性路径处的值,当不存在值时省略该 header(参见 Schema 扩展)。 - 根据值编码规则对这些值进行编码。
- 将一个
Mcp-Param-{Name}: {Value}header 附加到请求。
Mcp-Param-* header 缺失或与消息体不匹配而以一个 HeaderMismatch 错误拒绝一个请求,客户端**应当(SHOULD)**调用 tools/list 以检查工具的 inputSchema 是否有变更,然后以适当的 header 重试原始请求。
自定义 header 的服务器行为
不识别某个Mcp-Param-{Name} header 的中间服务器**必须(MUST)**转发它并在其他方面忽略它,如 HTTP Semantics RFC 所要求。
服务器**必须(MUST)**拒绝带有一个包含无效字符的已识别 Mcp-Param-{Name} header 的请求(参见值编码)。
任何处理消息体的服务器**必须(MUST)校验编码后的 header 值(若为 Base64 编码则在解码后)与请求体中对应的值匹配。如果任何校验失败,服务器必须(MUST)**以 400 Bad Request HTTP 状态和 JSON-RPC 错误代码 -32020(HeaderMismatch)拒绝请求。
大小写敏感性
Header 名称(在 RFC 9110 中称为”字段名”)是大小写不敏感的。客户端和服务器**必须(MUST)**对 header 名称使用大小写不敏感的比较。Header 值(例如方法名)是大小写敏感的。服务器校验
处理请求体的服务器**必须(MUST)**拒绝 header 中指定的值与请求体中对应值不匹配的请求。这防止了当网络中不同组件依赖不同真实来源时(例如负载均衡器基于 header 值路由,而 MCP 服务器基于消息体值执行)潜在的安全漏洞。在校验整数参数值时,服务器**应当(SHOULD)**以数值而非字符串的方式比较 header 值和消息体值(例如
42.0 和 42 被视为相等)。400 Bad Request 并必须(MUST)**包含一个使用以下错误代码的 JSON-RPC 错误响应:
此错误代码从 MCP 规范为协议定义的错误保留的子范围中分配。参见错误代码。
错误响应示例:
- 缺少一个必需的标准 header(
MCP-Protocol-Version、Mcp-Method、Mcp-Name)。 - 一个 header 值与对应的请求体值不匹配。对于允许 Base64 哨兵编码的 header(
Mcp-Name和Mcp-Param-{Name}),服务器在将它们与消息体值比较之前**必须(MUST)**解码编码后的值(参见值编码)。 - 一个 header 值包含无效字符。
中间方**必须(MUST)**为校验失败返回一个适当的 HTTP 错误状态(例如
400 Bad Request),但不要求返回一个 JSON-RPC 错误响应。基于镜像 header 强制执行策略的中间方(例如按租户路由或限流)**应当(SHOULD)验证
MCP-Protocol-Version header 指示一个需要 header–消息体校验的版本。如果版本较旧或该 header 缺失,中间方应当(SHOULD)**拒绝该请求,而不是信任未经校验的 header 值。向后兼容
一个同时支持现代(每请求元数据)MCP 版本和一个需要initialize 握手的旧式版本的客户端**可以(MAY)通过先尝试一个现代请求来检测服务器实现哪个时代。在 400 Bad Request 时,客户端在回退之前应当(SHOULD)**检查响应体:现代服务器也为 UnsupportedProtocolVersionError、MissingRequiredClientCapabilityError 和 header 校验失败使用 400。
- 如果消息体包含一个已识别的现代 JSON-RPC 错误,服务器讲的是一个现代版本的 MCP——使用公布的
supported版本重试或纠正请求,而不是回退。 - 如果消息体为空或不是一个已识别的现代 JSON-RPC 错误,回退到
initialize并在后续请求中继续使用旧式版本。
早期的 Streamable HTTP 修订版
协议版本2025-03-26 到 2025-11-25 也使用 Streamable HTTP 传输,但形态不同:服务器可以通过 Mcp-Session-Id header 分配一个会话(用 HTTP DELETE 终止),客户端可以用 HTTP GET 打开一个独立的 SSE 流以接收服务器发起的消息,服务器可以在 SSE 流上发送 JSON-RPC 请求,并且流可以通过 Last-Event-ID 恢复。这些机制都不是本修订版的一部分。
一个仅支持本修订版并从一个较旧客户端收到此类流量的服务器**应当(SHOULD)**按如下方式响应:
- 对 MCP 端点的 HTTP GET 或 DELETE:以
405 Method Not Allowed响应。 - 请求上的
Mcp-Session-Idheader:忽略它,且不铸造或回传会话 ID。 Last-Event-IDheader:忽略它;流不可恢复。
HTTP+SSE 传输(2024-11-05)
客户端和服务器可以按如下方式与已弃用的 HTTP+SSE 传输(来自协议版本 2024-11-05)保持向后兼容: 想要支持较旧客户端的服务器应该:- 继续托管旧传输的 SSE 和 POST 端点,与为 Streamable HTTP 传输定义的新”MCP 端点”并存。
- 也可以合并旧的 POST 端点和新的 MCP 端点,但这可能引入不必要的复杂度。
- 从用户处接受一个 MCP 服务器 URL,它可能指向一个使用旧传输或新传输的服务器。
- 尝试用如上所定义的
Acceptheader 向服务器 URL POST 一个请求:- 如果成功,客户端可以假定这是一个支持新的 Streamable HTTP 传输的服务器。
- 如果它以 HTTP 状态码
400 Bad Request、404 Not Found或405 Method Not Allowed失败并且响应体不是一个已识别的现代 JSON-RPC 错误(现代服务器会为不支持的版本、未知方法或 header 校验失败返回一个):- 向服务器 URL 发出一个 GET 请求,预期这将打开一个 SSE 流并返回一个
endpoint事件作为第一个事件。 - 当
endpoint事件到达时,客户端可以假定这是一个运行旧 HTTP+SSE 传输的服务器,并应为所有后续通信使用该传输。
- 向服务器 URL 发出一个 GET 请求,预期这将打开一个 SSE 流并返回一个