还在纠结 MCP 的 stdio、SSE、Streamable HTTP 三种连接怎么选的人,先停一下。我核对官方 spec 的时候发现一个很多人没注意的点:2025-11-25 版只定义两种标准传输,HTTP+SSE 已经被 Streamable HTTP 取代,只留给老客户端做兼容。所以问题不是三选一,是二选一。这篇讲清这两种传输的机制、切换的代价,和一个本地远程的选型分界。
SSE 为什么被废
先说最容易被忽略的一点。HTTP+SSE 是 2024-11-05 那版协议里的远程传输,它要两个端点,一个 POST 发消息,一个独立的 event-stream 端点收流。这个设计在浏览器里合理,在服务端很别扭。客户端要维护一条长连接,断线了要重连,多租户下每个客户端占一条连接,横向扩展很难做。
我核对了 2025-11-25 的 spec 原文。它把 Streamable HTTP 定为标准远程传输,HTTP+SSE 标成 deprecated,只用于向后兼容。MCP 的 TypeScript SDK 文档里写得更直白:Streamable HTTP 推荐给远程服务器,HTTP+SSE 废弃、仅兼容,stdio 给本地进程内集成。所以别再问 SSE 怎么选了。新项目没有理由选它,除非你手上有一堆老客户端只认 SSE 端点。
我核对的过程是这样的。我先搜了 SSE 和 Streamable HTTP 的关系,拿到 5 个二手解读,说法不完全一致。有的还把它当成并列的 3 种。我干脆打开 modelcontextprotocol.io 的 spec 原文,看到 transports 一节只列了 2 种,页面标题上就写着 2025-11-25 这个版本号。这一步把二手解读里的分歧定死了。
stdio 管本地
stdio 的机制很朴素。客户端把 MCP server 作为子进程拉起来,server 从 stdin 读 JSON-RPC 消息,往 stdout 写响应。一条消息一行,换行分隔,消息内部不许有内嵌换行。stderr 留给日志。客户端可以抓、可以转发、也可以忽略,但不能拿 stderr 的输出当错误信号。stdout 上只能写合法的 MCP 消息,写别的就乱了。
我把 spec 的 transports 一节拆开读,记下 3 个关键约束:stdio 用 newline 分隔、stderr 只放日志、客户端要优先支持 stdio。这 3 条是我从 2025-11-25 那页原文里抄出来的,一个字没改。
一个最小的 stdio 消息长这样,客户端发一条 initialize 请求,协议版本写 2025-11-25:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"demo","version":"0.1.0"}}}
stdio 的优点是零网络开销,没有端口、没有鉴权、没有中间人,进程死了连接就断。缺点是单租户,一个 server 只服务一个客户端,跨机器没法用。所以它天然属于本地。编辑器里的 MCP 插件、CLI 里的工具,都是 stdio 的地盘。官方 spec 也写了一句,客户端应该尽量优先支持 stdio。这句话我在 spec 的 transports 一节里确认过。
Streamable HTTP 管远程
远程的活交给 Streamable HTTP。server 是一个独立进程,对外只暴露一个 HTTP 端点,比如挂在某个域名下的 /mcp 路径,同时支持 POST 和 GET。客户端发消息,每一条 JSON-RPC 都是一个新的 HTTP POST。请求头里 Accept 必须同时列 application/json 和 text/event-stream,告诉服务器两种响应它都收。
服务器收到一条 JSON-RPC 请求,要么回 text/event-stream 开启一条 SSE 流,要么回 application/json 只给一个 JSON 对象,客户端两种都得支持。如果是通知或响应这类没有 id 的消息,服务器回 202 Accepted,body 留空。如果服务器不想在这端点提供 SSE 流,GET 请求可以回 405 Method Not Allowed。这几个状态码都是 spec 白纸黑字写的,不是我总结的。
流式不是白送的。SSE 流断开随时可能发生,断线不等于客户端取消了请求。要取消得显式发一条 CancelledNotification。服务器也可以把流做成可恢复的,客户端带 Last-Event-ID 重连。这些细节在 spec 的 Sending Messages to the Server 一节里,读一遍比看任何二手总结都清楚。
安全这一层别省
官方 spec 里 Streamable HTTP 的安全警告写得比传输本身还长。第一条是必须校验 Origin 头,防止 DNS rebinding 攻击,Origin 非法就回 403。第二条是本地跑的时候绑 127.0.0.1,别绑 0.0.0.0。第三条是要做认证。这三条不做的后果是,恶意网页能通过 DNS rebinding 去连你本地的 MCP server。这种攻击路径对纯 stdio 根本不存在,因为你没开端口。
我的判断是:stdio 的零攻击面是它最大的隐性优势。很多团队图方便,把一个本可以走 stdio 的工具硬做成 HTTP,等于自己给自己开了一个口子,还得补 Origin 校验和认证。没这个必要。
选型分界
给个结论。server 和 client 在同一台机器上,用 stdio,没有第二选项。server 要跨机器、要服务多个客户端、要部署到云端,用 Streamable HTTP。SSE 只在你要兼容 2024 年那批老客户端时才保留,新代码一律不写它。
切换的代价也要说清。从 stdio 换成 Streamable HTTP,你要多管四件事:端口和域名、Origin 校验、认证、断线重连。这些在 stdio 里全是免费的。所以能用 stdio 就别上 HTTP,这不是偷懒,是把该省的安全成本省掉。配置上,Claude Code 里本地是 claude mcp add -- npx -y ... 这种子进程形式,远程是 claude mcp add --transport http,一个参数之差,背后是两套完全不同的安全责任。