01.开篇
模型上下文协议(MCP)为智能体/模型与工具之间通信定义了协议标准。25年3月26日,Claude MCP官方引入了一种新的通信机制 Streamable HTTP,相比原有的HTTP+SSE的方式有了显著改进。本文将从Why、What、How几个方面进行介绍,一是希望本文能够给MCP应用的实际开发和问题定位提供参考,二是能够对相关类似场景的技术方案决策提供可借鉴的信息。
02.HTTP+SSE及其挑战
在最初的MCP协议中,沿用了OpenAI采用的HTTP+SSE方案作为通信传输层机制,其原理如下:
图1、HTTP+SSE的传输方式(图片由ChatGPT生成)
在HTTP+SSE的实现中,客户端和服务器通过两个通道进行通信:
-
HTTP请求/应答:客户端通过标准HTTP请求向服务器发送消息
-
SSE:服务器通过/sse端点向客户端推送事件消息
根据MCP官方的说法,虽然这种设计很简单,但存在几个关键问题:
问题1:不支持重连和断点续传
由于无状态特性,当SSE连接断开,所有会话状态就会丢失,客户端不得不重新建立连接并重新初始化新的会话。特别是当智能体/大模型正在执行耗时较长的任务过程中,就很有可能因为WiFi不稳定导致中断,整个任务只能重头再来。(PS:原本等了一个小时进度到了99%~直接哭晕在厕所)
问题2:服务器必须维持长连接
由于必须保持长连接打开状态,服务器在高并发下就会产生大量的资源消耗和浪费。而且在服务器重启和维护时,所有连接都会被断开,大量用户任务都只能被迫重新开始,对服务的体验和可靠性产生大范围的负面影响。
问题3:所有信息只能通过SSE方式传输
事无大小,都需要按照SSE的方式一刀切,哪怕是实现最简单的echo,也要先建立SSE通道,由此造成不必要的复杂性和开销浪费,并且降低整体性能。比如在云函数计算的场景下,就不适合维护SSE长连接。
问题4:基础设施限制
现有的基础设施,比如CDN、负载均衡、API网关等,都是面向Web应用的,可能很难正确处理长连接。比如nginx早前的版本使用stream时无法很好的支持DNS解析更新、防火墙可能会强制断开超时连接,从而导致服务不可靠。
基于上述原因MCP官方引入了全新的Streamable HTTP机制,接下来将对其进行详细介绍。
03.Streamable HTTP传输机制的原理
书接上文,为了解决上述问题和挑战,MCP协议进行了一次重大升级。
1、设计原则
全新的Streamable HTTP协议基于如下核心原则进行设计:
· 兼容性优先:确保传输方式与现有的HTTP生态无缝集成
· 灵活性:支持有状态和无状态两种模式
· 资源效率:消除不必要的长连接,按需分配资源
· 可靠:支持重连机制和会话断点恢复
2、关键改进
相比原有的传输方案,新方案进行了下列改进:
√ 会话标识:引入了会话ID机制,支持有会话状态管理和会话断点恢复。
√ 单一端点:不再需要专用的/sse端点,通过单个端点聚合所有通信。
√ 按需启用流式传输:服务器可以灵活选择返回标准HTTP响应,也可以者跃迁到SSE事件流。
3、大致流程
1) 初始化会话
- 客户端发送初始请求到端点(比如/message)
- 可选:服务器生成并返回会话ID给客户端,该会话ID将用于识别后续请求所属的会话
2) 客户端到服务器的通信方式
- 所有请求都通过POST请求发给MCP 端点
- 如果有会话ID,在请求中包含该请求以便服务器进行会话识别
3) 服务器响应方法
- 标准应答:直接返回一个JSON对象(Content-Type: application/json),从而实现简单交互。
- 流式应答:返回JSON-RPC SSE流(Content-Type: text/event-stream),发送一系列事件之后关闭连接。
- 长连接:可以维持一个SSE连接进行持续发送事件。
4) 客户端主动建立SSE流
- 客户端可以发送GET请求到MCP端点,主动建立SSE流
- 服务器可以通过这个流推送通知和请求
5) 重连恢复
- 如果连接掉线,客户端可以使用此前的会话ID进行重新连接
- MCP服务器可以恢复会话状态并继续此前的交互
4、协议规范
更细节的协议规范如下:
1) 服务器整体规范
√ MCP服务器必须提供一个同时支持 POST 和 GET 方法的 HTTP 端点路径(即MCP 端点)。
√ MCP服务器比如验证所有传入连接的Origin头,防止DNS重定性攻击。
√ 本地运行时,MCP服务器应该仅绑定本地主机地址(127.0.0.1),不应绑定所有网卡(0.0.0.0)。
√ MCP服务器应对所有连接进行适当的身份验证。
2) 向MCP服务端发送消息的规范
MCP客户端发送给MCP端点的每个JSON-RPC消息都必须是一个新的HTTP POST请求。
√ 客户端必须包含Accept头,列出支持application/json和text/event-stream两种内容的响应。
√ POST请求体必须是下列三种情况之一:
-
单个JSON-RPC请求、通知或响应
-
一个数组,包含一个或多个JSON-RPC请求 和/或 通知
-
一个数组,包含一个或多个JSON-RPC响应
√ 如果POST请求仅输入了JSON-RPC响应 或 通知中的一种(不限数量),那么
-
如果MCP服务器同意接收输入,就必须返回HTTP 202(Accepted)并且没有响应体。
-
如果不同意接收输入,就必须以HTTP错误状态码返回,比如HTTP 400(Bad Reqest)。可选滴,响应体可以是一个没有id的JSON-RPC错误响应。
√ 如果POST请求输入了任意数量的JSON-RPC请求,服务器要么要返回Content-Type: text/event-stream来初始化SSE流,要么返回Content-Type: application/json 来返回一个JSON对象。客户端必须同时支持这两种方式。
√ 如果MCP服务器初始化了SSE流
-
SSE流应当为POST请求体中的每个JSON-RPC请求发送一个JSON-RPC响应。这些响应可以采用批量方式发送(即一个事件中可以发送一批JSON-RPC响应)。
-
MCP服务器可以在发送JSON-RPC响应之前发送JSON-RPC请求和通知,并且这些消息应当与MCP客户端发送的请求有关。这些请求和通知也可以采用批量方式发送。
-
只要会话还没过期,MCP服务器就不应当在完成对所有已收到的JSON-RPC请求发送JSON-RPC响应之前关闭SSE流。
-
完成所有JSON-RPC响应发送后,MCP服务器应当关闭SSE流。
-
MCP服务器应当假设连接随时可能断开,因此:
-
断开连接不应当被理解为MCP客户端取消了请求。
-
MCP客户端如果需要取消请求,应当明确发送MCP的取消通知(CanceledNotification)。
-
MCP服务器如果需要在断开连接后不丢失消息,可以实现SSE流的断点续传。
3) 监听来自MCP服务器的消息
√ MCP客户端可以向MCP端点发送HTTP GET请求,以此打开SSE流,从而允许服务器先对MCP客户端发送消息,而无需先由MCP客户端MCP服务器发送HTTP POST数据。
√ MCP客户端的HTTP GET请求必须包含Accept头,列出支持text/event-stream响应内容。
√ MCP服务器必须对该HTTP GET请求响应Content-Type: text/event-stream头,或者返回HTTP 405(Method Not Allowed)以明确该MCP端点不支持SSE流。
√ 如果MCP端点支持SSE流,那么
-
MCP服务器可以通过SSE流发送JSON-RPC请求 和 通知。这些请求和通知可以采用批量方式发送。
-
这些JSON-RPC消息的内容应当无关于此时MCP客户端的任何JSON-RPC请求。
-
如果MCP客户端还没有恢复此前请求的SSE流,MCP服务器就不得向该流发送JSON-RPC响应。
-
MCP服务器可以随时关闭SSE流。
-
MCP客户端可以随时关闭SSE流。
4) MCP客户端建立多连接
√ MCP客户端可以同时保持多个SSE流的连接。
√ MCP服务器的每个JSON-RPC消息发送给一个流,不能向多个流广播消息。
√ MCP服务器支持断点续传可以减少消息丢失的风险。
5) 断点恢复和消息重传
√ MCP服务器可以在SSE事件中加入SSE标准规范中所描述的id字段。
-
如果存在ID字段,并且使用了会话管理,那么每个事件的ID必须在会话内全局唯一。
-
如果存在ID字段,并且没有使用会话管理,那么每个事件的ID必须在同一个MCP客户端的所有流中全局唯一。
√ 如果MCP客户端希望断点重连,应当向MCP端点发送HTTP GET请求,并在请求头中包含Last-Event-ID头来指定收到的最后一个事件的ID。
-
MCP服务器可以根据此请求头重放该事件ID之后发送的消息,从该点恢复流。
-
MCP服务器不得重播已经在其他流上发送的消息。
√ 事件ID应当由MCP服务器按每个流进行分配,充当特定流的游标。
6) 会话管理
MCP会话由MCP客户端和MCP服务端之间交互逻辑形成,始于初始化阶段。为了建立有会话状态的MCP服务器,需要执行如下操作:
√ 使用Streamable HTTP传输的MCP服务器可以在初始化时分配会话ID,具体方法是将包含初始化结果(InitializeResult)的HTTP响应加上Mcp-Session-Id响应头。
-
该会话ID应当是全局唯一的,并且加密安全(例如,安全生成的UUID、JWT或加密哈希)。
-
该会话ID只能包含可见的ASC-II字符(0x21-0x7e)。
√ 如果在初始化阶段返回了Mcp-Session-Id,那么使用Streamable HTTP的MCP客户端必须将Mcp-Session-Id请求头包含在所有后续HTTP请求中。
-
如果MCP服务器需要使用会话ID,应当使用HTTP 400(Bad Request)响应没有Mcp-Session-Id的请求。
√ MCP服务器可以在任何时候终结会话,而在此之后,MCP服务器必须以HTTP 404(Not Found)响应具有该会话ID的请求。
√ 如果MCP客户端发送带有Mcp-Session-Id头的请求收到了HTTP 404响应,MCP客户端就必须发送一个不带会话ID的新初始化请求(InitializeRequest)来开启新会话。
√ 当MCP客户端不再需要某个特定会话(比如用户退出了MCP客户端应用),MCP客户端就应当向MCP端点发送一个带有Mcp-Session-Id头的HTTP DELETE请求,以显式化地终结该会话。
-
MCP服务器如果要表明不允许MCP客户端终结会话,就应当以HTTP 405(Method Not Allowed)来响应该请求。
7) 向后兼容性
MCP客户端和MCP服务器可以向后兼容已弃用的HTTP+SSE传输方式。
MCP服务器应当:
√ 保留旧的SSE和POST端点,同时为Streamable HTTP传输添加新的MCP端点
-
将旧的端点和新的端点结合可能会引入不必要的复杂度
MCP客户端应当:
√ 接受用户提供的MCP服务器URL可以指向旧的或的新的传输方式。
√ 尝试向服务器的URL地址POST一个带有Accept头的初始化请求(InitializeRequest)。
-
如果成功了,客户端就假定这是支持Streamable HTTP的服务器。
-
如果失败返回了HTTP 4xx状态码(例如 405 Method Not Allowed或者404 Not Found):
-
向该URL地址发起一个GET请求,期待会打开SSE流且返回的第一个事件是个endpoint事件。
-
当收到了endpoint事件,客户端就可以认定这是运行旧HTTP+SSE传输方式的服务器,应当后续的所有通信都应当使用这种传输方式。
8) 自定义传输方式
√ MCP客户端和MCP服务器可以根据自己特定需要实现其他自定义的传输方式。本协议并不要求特定传输方式,可以在任何支持双向消息交换的通信信道上实现。
√ 如果选择支持自定义传输方式,必须确保传输方式满足MCP定义的JSON-RPC消息格式与生命周期要求。自定义传输方式应当编写文档记录其专有的连接建立和信息交换方式,从而提升互操作性。
04.开发实现
1、实现MCP服务器
1) 工程准备
首先,初始化项目并安装依赖:
2) 应用设置
依次设置如下内容:
3) 实现MCP端点
① 定义服务器
② 定义复用传输中间件
③ 定义初始化传输和会话中间件
④ 定义会话断点续传中间件
⑤ 定义核心请求处理中间件
⑥ 定义HTTP 400中间件
⑦ 定义HTTP 500中间件
⑧ 组装中间件
2、实现MCP客户端
1) 定义客户端
2) 定义连接逻辑
3) 定义获取工具逻辑
4) 定义调用工具逻辑
5) 定义断开连接逻辑
3、测试MCP服务器和MCP客户端
4、更多实例代码和官方检查器
如果要发掘MCP服务器或者MCP客户端完整的能力,可以参考官方的例子:
https://github.com/modelcontextprotocol/typescript-sdk/tree/main/src/examples
官方也提供了图形化界面的MCP服务检查器,你只需要运行如下命令即可:
-END-
文章作者| 李淳
原文始发于微信公众号(EBCloud):MCP的Streamable HTTP传输层全解读
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论