
摘要MCP传输层支持stdio、SSE和Streamable HTTP三种模式。本文从性能、安全性、部署场景三个维度对比三种传输给出选型建议和同一Server多传输模式切换方案。传输层详解 stdio vs SSE vs Streamable HTTP我把一个 MCP 服务端部署给异地团队用第一版用 stdio结果跨网络根本连不上。换成 SSE 能跑了但又踩了双端点的坑。最后迁到 Streamable HTTP 才算稳。这篇把三种传输模式掰开揉碎讲清楚配上可直接切换传输模式的代码让你知道每种模式该用在哪。三种传输模式总览MCP 底层用 JSON-RPC 2.0 编码消息消息必须是 UTF-8。规范在 2025-06-18 版本里只定义了两种标准传输stdio 和 Streamable HTTP。SSE准确说是 HTTPSSE是 2024-11-05 旧版的远程传输现在已废弃但很多老服务端还在用所以得一起讲。三种模式定位很清晰。stdio 给本地用客户端把服务端当子进程拉起通过标准输入输出通信。HTTPSSE 给旧版远程用靠两个 HTTP 端点配合 Server-Sent Events 推消息。Streamable HTTP 给新版远程用单端点支持 POST 和 GET可选 SSE 流式推送还能做会话管理和断线续传。stdio 本地传输stdio 是最简单的模式。客户端把服务端作为子进程启动服务端从 stdin 读 JSON-RPC 消息往 stdout 写消息。消息之间用换行符分隔单条消息内部不能有换行。日志只能往 stderr 写客户端可以选择转发或忽略。这套模式有几个硬约束。服务端不能往 stdout 写任何非 MCP 消息的内容客户端也不能往服务端 stdin 写非协议内容。我第一次写服务端时习惯性用print()调试结果打印的内容被客户端当成 JSON-RPC 消息解析直接报错断连。stdio 只能本地用因为它依赖父子进程的管道跨网络没法用。stdio 的好处是零配置、低延迟、无网络开销适合本地工具和桌面客户端比如 Claude Desktop。客户端能完全控制服务端的启动参数和环境变量安全性也好把控。HTTPSSE 旧版远程传输2024-11-05 版本的远程传输用两个 HTTP 端点。客户端先 GET 一个 SSE 端点打开长连接服务端通过这条连接推送消息并在首个endpoint事件里告诉客户端往哪个 POST 端点发请求。之后客户端的请求都 POST 到那个端点服务端的响应和通知通过 SSE 连接回传。这套设计的问题在于连接模型割裂。请求走 POST响应走 SSE两条通道要协调好状态。服务端还要维护两个端点的路由部署和调试都麻烦。规范在 2025-06-18 版本用 Streamable HTTP 替换了它。旧版服务端如果还想兼容老客户端可以同时保留 SSE 端点和新的 MCP 端点。新客户端会先尝试 POST InitializeRequest失败再回退到 GET 探测 SSE。这套回退逻辑让新老版本能并存一段时间。Streamable HTTP 新版远程传输Streamable HTTP 是当前推荐的远程传输。服务端只暴露一个 MCP 端点比如https://example.com/mcp同时支持 POST 和 GET。客户端发消息用 POST请求头要带Accept: application/json, text/event-stream表示两种响应都接受。服务端对请求可以返回普通 JSON也可以升级成 SSE 流。升级成 SSE 流的好处是服务端能在返回最终响应前先推送进度通知和子请求长任务体验好很多。客户端也能用 GET 打开一条 SSE 流纯接收服务端的主动通知跟任何请求解耦。这套单端点设计比旧版干净太多。会话管理是 Streamable HTTP 的重要能力。服务端在初始化响应里带一个Mcp-Session-Id头客户端后续所有请求都要带上这个 id。会话过期服务端返回 404客户端要重新初始化。客户端不再需要会话时发 DELETE 显式终止。断线续传靠 SSE 事件 id。服务端给 SSE 事件附全局唯一 id客户端断线后用Last-Event-ID头重连服务端从断点续传未送达的消息。这套机制对网络不稳定的远程场景很实用。安全方面规范给了三条硬要求。服务端必须校验Origin头防 DNS 重绑定攻击本地运行要绑定 127.0.0.1 而不是 0.0.0.0所有连接要做认证。这三条少一条都可能被远程网页利用来攻击本地服务端。三种模式对比与选型下面这张表把三种模式的关键维度放在一起对比。维度stdioHTTPSSE已废弃Streamable HTTP连接模型父子进程管道双端点GET 流加 POST 请求单端点POST 加可选 GET 流消息方向stdin/stdout 双向请求 POST响应 SSE 推POST 请求响应 JSON 或 SSE会话管理进程生命周期即会话无显式会话 idMcp-Session-Id 头管理断线续传不适用不支持支持Last-Event-ID多客户端一对一支持支持可多流并存服务端推送支持通知支持SSE支持SSE 流部署复杂度低高双端点中安全控制进程级本地信任需自定义Origin 校验加会话加认证适用场景本地工具、桌面客户端兼容老客户端新版远程服务协议状态当前标准废弃保留兼容当前标准选型建议很直接。本地工具和桌面集成一律用 stdio简单可靠。新做的远程服务端直接上 Streamable HTTP别再碰 SSE。只有要兼容还没升级的老客户端时才保留 SSE 端点做过渡。完整代码先装依赖。pipinstallfastmcp服务端transport_server.py通过命令行参数切换三种传输模式。# transport_server.py# 演示同一服务端如何切换 stdio / SSE / Streamable HTTP 三种传输importsysfromfastmcpimportFastMCP# 创建服务端实例名字会出现在初始化握手信息里mcpFastMCP(TransportDemo)mcp.tooldefping()-str:一个最简单的工具返回 pong用来验证连通性。returnpongif__name____main__:# 从命令行读传输模式默认 stdiomodesys.argv[1]iflen(sys.argv)1elsestdioifmodestdio:# stdio 模式默认传输客户端以子进程方式拉起# 注意服务端别用 printstdout 只能写 MCP 消息mcp.run()elifmodesse:# SSE 旧版远程传输已废弃仅用于兼容老客户端# 默认端点路径是 /ssemcp.run(transportsse,host127.0.0.1,port8765)elifmodehttp:# Streamable HTTP 新版远程传输推荐# 默认端点路径是 /mcpmcp.run(transportstreamable-http,host127.0.0.1,port8765)else:print(f未知传输模式:{mode},filesys.stderr)sys.exit(1)客户端transport_client.py按模式连接对应传输并调用工具。# transport_client.py# 演示客户端如何连接三种传输模式的服务端importasyncioimportsysfromfastmcpimportClientasyncdefmain():# 从命令行读模式默认 stdiomodesys.argv[1]iflen(sys.argv)1elsestdio# 根据模式选择连接源# Client 会根据传入内容自动推断传输方式ifmodestdio:# 传脚本路径自动用 stdio 拉起子进程sourcetransport_server.pyelifmodesse:# 旧版 SSE连接 /sse 端点sourcehttp://127.0.0.1:8765/sseelifmodehttp:# 新版 Streamable HTTP连接 /mcp 端点sourcehttp://127.0.0.1:8765/mcpelse:print(f未知模式:{mode})return# 构造客户端async with 管理连接生命周期asyncwithClient(source)asclient:# 列出工具确认握手成功toolsawaitclient.list_tools()print(可用工具:,[t.namefortintools])# 调用 ping 工具验证端到端通路resultawaitclient.call_tool(ping,{})print(ping 结果:,result.data)if__name____main__:asyncio.run(main())效果验证stdio 模式直接跑客户端它会自动拉起服务端子进程。python transport_client.py stdio输出“可用工具: [‘ping’]”和“ping 结果: pong”。SSE 模式先起服务端再跑客户端开两个终端。# 终端 1启动 SSE 服务端python transport_server.py sse# 终端 2连接并调用python transport_client.py sseStreamable HTTP 同理。# 终端 1启动 Streamable HTTP 服务端python transport_server.py http# 终端 2连接并调用python transport_client.py http两种远程模式输出和 stdio 一致。想看 SSE 流式推送的效果把上一篇文章的进度通知服务端换成transportstreamable-http部署客户端用 HTTP 连接进度回调照常触发。常见问题与避坑1. stdio 模式 print 污染协议流。这是最高频的坑。服务端里任何print()或第三方库往 stdout 的输出都会被客户端当 JSON-RPC 消息解析直接报错断连。调试日志一律走 stderrprint(..., filesys.stderr)或用 logging 配置 stderr handler。被依赖库坑过一次排查了两小时才定位是某个 SDK 在 stdout 打了版本号。2. Streamable HTTP 忘了校验 Origin。规范明确要求校验 Origin 头防 DNS rebinding。本地服务端只绑 127.0.0.1 还不够远程网页仍可能通过 DNS 重绑定访问。用 FastMCP 这类框架会内置校验自己用低级 API 实现时务必手动加 Origin 白名单。3. SSE 双端点连接顺序错。旧版 SSE 必须先 GET 打开 SSE 流收到endpoint事件拿到 POST 地址后才能发请求。我一开始直接 POST服务端不认。新项目别用 SSE 了老项目迁移时注意这个顺序。4. Mcp-Session-Id 没带上导致 400。Streamable HTTP 下服务端初始化时返回会话 id后续请求都要带上。用低级客户端自己拼请求时容易漏框架客户端一般自动管理。收到 400 就检查是不是漏了会话头。5. 跨网络硬上 stdio。stdio 只能父子进程本地用有人想用 SSH 隧道或网络管道强行转发 stdin/stdout延迟和稳定性都很差。跨网络就用 Streamable HTTP别在 stdio 上折腾。小结传输层选型记住三句话。本地用 stdio新版远程用 Streamable HTTPSSE 只在兼容老客户端时保留。stdio 注意别污染 stdoutStreamable HTTP 注意 Origin 校验和会话头管理SSE 别在新项目里用。下一篇把 MCP 和 Function Calling、OpenAPI 放一起对比看不同场景该怎么选。相关推荐MCP协议全景Host、Client、Server架构详解多传输模式切换同一个Server支持stdio和HTTP部署上线Docker容器化与云端部署