Model Context Protocol(MCP)的两种传输通道:stdio 与 Streamable HTTP
MCP 协议依赖 JSON-RPC 来编排客户端与服务器之间的消息对话。然而,仅有消息格式并不足以支撑数据流通——真正让通信流畅运转的是底层的传输通道。在 MCP 体系中,这个通道被称为传输层(Transport)。官方提供了两种标准实现:stdio 和 Streamable HTTP。它们的设计理念截然不同,分别面向差异化的应用场景。理解这些差异,有助于在实际开发中做出恰当的技术选型。
stdio:本地子进程通信
stdio 传输机制专为本地环境设计,核心理念是简洁高效。在该模式下,客户端会直接启动一个 MCP 服务器作为其子进程。
工作原理
客户端启动服务器子进程后,一条专用的通信管道随即建立。客户端通过向服务器进程的标准输入(stdin)写入数据来发送消息,同时从标准输出(stdout)读取数据以接收回复。本质上,这类似于命令行中通过管道连接两个程序,但规范性和标准化程度更高。
所有经由 stdin 和 stdout 传输的消息必须是完整的 JSON-RPC 格式,并以换行符分隔。需要注意一个关键限制:stdout 上不得出现任何非 MCP 消息的内容。若服务器需要输出日志或调试信息,必须走标准错误流(stderr),客户端可选择捕获、显示或忽略这些日志。
通信的生命周期与客户端进程紧密绑定。当对话结束时,客户端会关闭服务器的 stdin 流,随后终止子进程。一次完整的会话就此终结。
生命周期管理
在 stdio 模式下,服务器生命周期完全由客户端掌控。通信起始于客户端创建子进程,而关闭也由客户端主动发起。
标准的关闭流程如下:客户端首先关闭通往服务器子进程的 stdin。服务器检测到 stdin 关闭后,应当自行清理资源并退出。若服务器未能及时退出,客户端会依次发送 SIGTERM 和 SIGKILL 信号来强制终止。这种父子进程管理模型能确保资源及时释放,避免产生“僵尸进程”。
这种模式特别适合集成到本地开发工具中——例如 IDE 插件或命令行工具。它需要与语言服务器或其他本地服务进行高效、低延迟的交互,并且能够完全掌控这些服务的生命周期。
Streamable HTTP:灵活的网络通信
与 stdio 的紧密耦合形成鲜明对比,Streamable HTTP 是一种面向网络环境设计的、更加灵活且解耦的传输机制。在该模式下,服务器作为独立进程运行,可以与客户端位于不同机器上,甚至同时处理来自多个客户端的连接。
通信流程
Streamable HTTP 的核心围绕一个统一的 MCP 端点(Endpoint)展开,该端点同时支持 HTTP POST 和 HTTP GET 请求。
当客户端需要向服务器发送消息时,它会向该端点发起一个 HTTP POST 请求,请求体包含一条单独的 JSON-RPC 消息。如果发送的是通知(Notification)或响应(Response),服务器成功接收后会返回 HTTP 202 Accepted 状态码,表示“已接收”。
如果发送的是一个请求(Request),服务器的响应有两种可能。对于简单请求,服务器可以直接在 HTTP 响应体中返回一个 application/json 格式的 JSON-RPC 响应。但对于可能产生多个事件或需要流式返回数据的复杂请求,服务器会返回 Content-Type: text/event-stream——这意味着建立了一个服务器发送事件(SSE,Server-Sent Events)流。通过这个 SSE 流,服务器可以持续向客户端推送消息,直到最终的 JSON-RPC 响应发送完毕,随后关闭流。
除了通过 POST 请求被动建立 SSE 流,客户端也可以主动发起一个 HTTP GET 请求来监听来自服务器的消息。这意味着,即使没有客户端的主动请求,服务器也能主动向客户端推送通知或发起请求。
会话与状态管理
HTTP 本身是无状态的,Streamable HTTP 为此引入了会话管理机制来维持交互上下文。在初始化阶段,服务器可以在响应头中通过 Mcp-Session-Id 返回一个唯一的会话 ID。
客户端收到该会话 ID 后,必须在后续所有 HTTP 请求中都带上 Mcp-Session-Id 请求头。服务器据此识别并关联同一客户端的连续请求,从而维护一个有状态的会话。
这种机制还为连接恢复提供了可能。SSE 规范允许为每个事件附加一个 ID。如果连接意外中断,客户端在重新连接时可以带上 Last-Event-ID 头,请求服务器从上一次中断的位置继续推送消息。在网络不稳定的环境中,这能显著提升通信的鲁棒性。
安全注意事项
将通信暴露到网络上,必然引入额外的安全风险。实现者需要关注以下几个关键点:
- 服务器必须验证
Origin请求头,以防止 DNS 重新绑定(DNS Rebinding)攻击。 - 在本地运行时,服务器应仅绑定到
localhost(127.0.0.1),而不是所有网络接口(0.0.0.0),避免局域网内其他设备的非授权访问。 - 服务器应对所有连接实施恰当的认证和授权机制。
如果忽略了这些措施,本地运行的 MCP 服务器可能会被远程攻击者盯上,造成严重的安全隐患。
stdio 与 Streamable HTTP 的核心区别
深入了解两种传输机制的运作方式后,我们可以把它们的核心差异归纳为一张表格。这些差异直接决定了各自的适用场景。
| 特性 | stdio |
Streamable HTTP |
|---|---|---|
| 耦合模型 | 紧密耦合。客户端是父进程,服务器是子进程。 | 松散耦合。客户端和服务器是独立进程,通过网络通信。 |
| 生命周期 | 服务器生命周期由客户端完全控制。 | 服务器独立运行,生命周期与客户端无关。 |
| 通信方式 | 标准输入(stdin)和标准输出(stdout)。 |
HTTP POST、HTTP GET 以及服务器发送事件(SSE)。 |
| 并发能力 | 通常是一对一的通信。 | 设计上支持处理多个客户端的并发连接。 |
| 会话管理 | 隐式会话,会话生命周期等同于进程生命周期。 | 显式会话管理,通过 Mcp-Session-Id HTTP 头维持。 |
| 网络弹性 | 不适用,因为是本地进程间通信。 | 支持连接恢复,通过 SSE 的 Last-Event-ID 机制实现。 |
| 安全模型 | 相对安全,通信限制在本地,不暴露网络端口。 | 必须实施严格的网络安全策略,如认证、CORS、主机绑定等。 |
| 适用场景 | 本地开发工具、IDE 插件、桌面应用内集成。 | Web 应用、远程服务、需要多客户端连接的后端系统。 |
选择哪种传输方式,本质上是在简单性与灵活性之间做权衡。
stdio 模型胜在简洁高效。由于不涉及网络协议栈,通信开销极低,也无需考虑复杂的网络安全问题。对于那些客户端和服务器注定运行在同一台机器上的场景——例如编辑器插件与语言服务器的集成——stdio 是非常理想的选择。
Streamable HTTP 则提供了更高的灵活性和扩展性。它允许客户端和服务器分离部署,支持多客户端连接,并内置了会话管理与连接恢复等高级功能。这使得 MCP 能够走出本地环境,应用于构建复杂的分布式系统和 Web 应用。当需要搭建一个可供网页、移动应用或其他远程客户端访问的 MCP 服务时,Streamable HTTP 是唯一合适的标准选项。
两种方案各有千秋,没有绝对的优劣。关键在于明确自己的应用场景:是追求极致的本地效率,还是拥抱网络化的分布式架构。搞清了这一点,选择自然就清晰了。
