浅谈MCP协议中的SSE和Streamable HTTP
📖 前言
在开发 Chatspeed 的 MCP
模块时,当时我们想当然地认为,SSE
能自动处理好一切:比如长时间维护连接、断线后自动重连等等。
这种设计的美妙之处在于:库层封装了所有网络状态管理,开发者只需关注业务连接,无需处理底层网络细节。可事实上,在使用过程中经常遇到莫名其妙的不可用情况,用MCP
开发工具一测试,服务器明明是好的。后来才发现网络断开、电脑休眠等都可能造成410
(Gone
)错误。
刚开始并没有太在意,后来遇见多了才去想"为什么"。经过研究后才发现这是电脑系统机制+SSE
协议双重原因造成的。当电脑系统(无论是 macos
、windows
、linux
)休眠(有的叫挂起)后,为了节省电源,会断开绝大多数的 http
链接。SSE
协议的双方为了维持长期链接,SSE
的服务器端会与客户端之间维持着心跳,并在某段时间后没收到心跳就认为 SSE
客户端离开了,这就进入了资源回收清理 Session ID
的流程。当电脑恢复后,SSE
客户端重新链接时,由于服务器端的 Session ID
已经清理掉了,这时候就会出现 410
(Gone
)错误。
值得注意的是,Streamable HTTP
协议本身并不能"天然地"解决 SSE
的这个毛病。 尽管它提供了更灵活的传输机制,但在服务器重启或长时间断开连接后,如果会话状态没有得到妥善管理,Streamable HTTP
同样会因为客户端持有的 Session ID
失效而返回 401
(Unauthorized
) 或 410
(Gone
) 错误,具体取决于 MCP 服务器的实现。这表明,无论是 SSE
还是 Streamable HTTP
,其核心问题都在于会话状态的持久化与管理。 这也是 Chatspeed
后来决定重构会话管理器的根本原因。
不同的 MCP 服务器端(开发框架)可能返回不同的错误代码,比如
rmcp0.7
的SSE
协议Session ID
失效的错误代码是410
,而Streamable HTTP
的Session ID
失效的错误代码则是401
,无论何种错误本质上都是Session ID
失效造成的。
📊 SSE协议:优点与局限
Server-Sent Events (SSE)
是一种允许服务器向客户端单向推送更新的机制。它基于标准的HTTP
协议,通过一个持久的HTTP
连接发送事件流。
✅ SSE的优点:
- 简单易用:
SSE
基于HTTP
,无需复杂的握手过程,实现相对简单。浏览器原生支持,方便前端集成。 - 单向实时更新:非常适合服务器向客户端推送实时数据,例如聊天消息、通知或进度更新。
❌ SSE的局限:
尽管SSE
在某些场景下表现出色,但它也存在一些显著的局限性,尤其是在AI Agent
这种需要高稳定性和长连接的场景中:
- 连接不可恢复性:这是
SSE
最令人头疼的问题之一。在Chatspeed
的实际使用中,我们曾被SSE
在电脑休眠后无法使用的问题困扰了许久。 当客户端与服务器之间的SSE
连接意外中断(例如,电脑休眠、网络波动或客户端重启)时,SSE
连接无法自动恢复。客户端必须重新发起连接,如果客户端本身不具备自动重连功能,就得用户手动刷新或者重连,并给用户带来不连贯的体验,甚至觉得是Chatspeed MCP
代理服务器端的问题。对于AI Agent
而言,这意味着工具的可用性大打折扣,对于没有技术背景或者在MCP
协议方面没有研究的人来说,这一切的问题都是Chatspeed MCP
代理服务器的问题。 - 长连接资源消耗:服务器需要为每个客户端维护一个长时间开放的
HTTP
连接。在大规模部署时,这会消耗大量的服务器资源,增加服务器的负担。 - 单向通信:
SSE
是单向的,数据只能从服务器流向客户端。如果客户端需要向服务器发送数据,必须通过额外的HTTP
POST
请求,增加了通信的复杂性。 - 与现代基础设施兼容性问题:一些中间件、代理和负载均衡器对长时间保持的
HTTP
连接支持不佳,可能导致SSE
连接不稳定或被意外关闭。
🚀 Streamable HTTP协议:新一代流式传输
为了解决SSE
的局限性,MCP
协议引入了Streamable HTTP
。它并非传统意义上的"流式HTTP",而是一种兼具多种特性的传输机制,旨在提供更健壮、更灵活的流式通信。
💡 Streamable HTTP的核心理念:
- 基于普通HTTP请求:客户端通过标准的
HTTP
POST
/GET
请求发起通信。 - 可选的SSE升级:服务器可以根据需要,将响应升级为
SSE
流,以实现流式传输能力。 - 去中心化与无状态:不强制要求持续连接,支持构建无状态的服务器架构,
降低了服务器的维护成本。
✨ Streamable HTTP
的优点:
- 支持无状态服务器:无需维持高可用的长连接,服务器资源利用率更高,更易于扩展。
- 纯
HTTP
实现:与现有的HTTP
基础设施(如中间件、代理、负载均衡器)兼容性极佳,部署和集成更加顺畅。 - 连接可恢复性(需配合会话管理):
Streamable HTTP
提供了更灵活的传输机制,为实现连接可恢复性提供了更好的基础。但要真正实现连接中断后的无缝恢复,避免401
或410
错误,必须配合服务器端的会话管理机制。通过将会话状态持久化,即使服务器重启或网络短暂中断,客户端也能使用旧的Session ID
重新连接并恢复会话,而无需重新初始化。这极大地提升了用户体验和工具的可用性。 - 灵活性:服务器可以根据实际需求选择是否使用
SSE
进行流式响应,提供了更大的灵活性。 - 向后兼容:作为
HTTP
+SSE
传输方式的渐进式改进,它能够与旧版SSE
客户端保持良好的向后兼容性。
📝 总结与建议
SSE
服务器端不可用有时候并不是服务器端本身的问题,而是由于网络中断超过keep-alive
时间后,服务器端进行正常资源回收,导致客户端使用原先的Session ID
连接时,因Session ID
失效而出现410
错误。
无论是 SSE
还是 Streamable HTTP
,在没有完善的会话管理机制下,都可能在服务器重启或长时间断开连接后,导致客户端收到 401
(Unauthorized
) 或 410
(Gone
) 错误。 这类错误的核心原因在于服务器端丢失了客户端的会话状态。
💡 解决方案:引入会话管理
值得一提的是,在 rmcp0.7
框架中,SSE
协议的会话管理是直接硬编码实现的,这使得对其进行扩展以支持持久化会话变得非常困难。而 Streamable HTTP
协议则预留了可扩展的会话管理接口,这为我们实现自定义的持久化会话管理提供了可能。因此,Chatspeed
目前仅为 Streamable HTTP
协议实现了会话持久化功能。
为了彻底解决这个问题,核心在于实现会话的持久化管理。其通用思路如下:
- 会话状态持久化:服务器不再将会话信息仅仅存储在内存中,而是将其写入到持久化存储(如数据库、文件系统)中。会话信息通常包括会话ID、创建时间、过期时间等。
- 会话有效期与清理:为每个会话设置一个合理的有效期。服务器端需要一个后台任务,定期扫描并清理已过期的会话,防止存储无限增长。
- 会话恢复机制:当客户端带着一个旧的但未过期的会话ID重新连接时,服务器能够从持久化存储中读取该会话信息,并“复活”对应的会话状态,使其能够继续处理请求,而无需客户端重新发起初始化。
- 客户端透明:对于客户端而言,会话的恢复过程应该是透明的,它无需感知服务器的重启或会话的重建。
通过引入这样的会话管理机制,可以极大地提升 AI Agent
应用的健壮性和用户体验,确保在各种复杂网络环境下都能提供稳定、流畅的服务。
Rust rmcp
框架下的会话管理实现参考:src-tauri/src/mcp/server/persistent_session.rs
Streamable HTTP
是MCP
协议传输层的一次重要优化,它在保留原有HTTP
+SSE
模式优势的基础上,通过配合完善的会话管理机制,成功解决了连接不可恢复、长连接负担重、传输不灵活等问题,带来了更高的可用性、灵活性和稳定性。
对于 AI Agent
的开发者而言,强烈建议优先选择 Streamable HTTP
协议,并务必实现健壮的会话管理。它能够有效避免在电脑休眠后连接断开的痛点,确保 AI Agent
在各种使用场景下都能提供稳定、流畅的服务体验。通过Streamable HTTP
和会话管理,我们可以构建出更加健壮、可靠的AI Agent
应用,让AI真正成为我们工作和生活中的得力助手。
目前 Chatspeed 的 MCP
代理已新增了 Streamable HTTP
协议并实现了会话高效管理能确保在网络断线或电脑休眠(7天内)会话自动恢复。为了兼容老用户当前仍然保留了 SSE
协议,不过仍然强烈建议Chatspeed
用户尽快迁移至Streamable HTTP
协议,未来某个版本我们可能会彻底删除SSE
协议。切换过程很简单,只要将 MCP
代理地址的 /mcp/sse
改为 /mcp/http
即可
如果你对 Chatspeed MCP
代理感兴趣,你可以访问 MCP代理 章节来了解详细信息。