2026-10-16 · 林一舟
现在去搜 MCP 教程,有个现象值得警惕:排在前面的文章里,相当一部分还在教你写 SSEServerTransport——这套写法在 2025 年 6 月的规范里已经被正式废弃了。代码能跑,方向是错的,等你照着它上线,改造成本会翻倍。
这篇把三种传输模式一次讲透:它们各自解决什么问题、SSE 为什么死掉、以及 2026 年今天你应该怎么选。不重复入门内容,默认你已经跑通过第一个 server。
stdio 模式下,客户端(Claude Desktop、Cursor)把你的 server 当作本地子进程拉起,通过标准输入输出交换 JSON-RPC 消息。
它的优点经常被"本地"两个字低估:
npx -y 你的包名,就完成了全部"安装"什么时候 stdio 是永久正确答案而不是权宜之计?当你的工具依赖本地环境——本机文件、内网系统、用户自己的凭据。这类工具做成远端反而是错的:数据不出本机,本身就是卖点。
对应的纪律是:stdio server 拥有用户本机的全部权限,你每注册一个 tool,就是授权模型动一次用户的机器。参数校验(zod)和最小权限不是最佳实践,是底线。
最初的远程方案叫 HTTP+SSE:两个端点,一个挂 SSE 长连接收服务端推送,一个 POST 收客户端请求。它在单机 demo 里工作得很好,然后在大规模部署时暴露了三个致命伤:
所以 2025-06 规范用 Streamable HTTP 取代了它:单个端点(/mcp),客户端全部走 POST,服务端可选把响应升级为 SSE 流。需要推送才有流,不需要就是普通请求——长连接从"必选项"变成了"可选项",上面三个问题随之瓦解。旧的 /sse 端点模式退出历史。
判断标准很简单:2025 年中以后的教程/模板还在以 SSEServerTransport 为核心的,直接关掉。
Streamable HTTP 有两种部署形态,这是选型的真正分水岭:
有状态(stateful):服务端为每个会话生成 mcp-session-id,后续请求带这个头,会话粘在单实例上。适合需要跨请求记忆、服务端推送的长交互场景。代价:多实例部署需要 sticky session。
无状态(stateless):每个请求独立处理,不保留会话。2026-07 规范修订把它推到台前——无状态的 server 天然可缓存、可路由、可水平扩展,放在任何 CDN/网关后面都不别扭。对"工具调用"这类请求-响应型负载,无状态几乎总是更好的默认。
无状态模式的核心代码出乎意料地短(express):
app.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // 无会话
enableJsonResponse: true, // 纯 JSON 响应,不升级 SSE
});
res.on("close", () => void transport.close());
const server = createServer(); // 你的工具注册,与 stdio 完全同一份
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
注意第三行注释的含义:双传输共用一套能力注册是工程上最重要的设计决策。你的工具写在 createServer() 里,stdio 和 HTTP 只是两种"插座"——改工具永远只改一处,这是模板设计里我最坚持的一条。
一个常见误判:开发者为了"显得正式"把个人工具也做成远端 HTTP。代价是多出来的部署、鉴权、证书、监控,换来的功能一项都用不上。stdio 到 HTTP 的迁移成本很低(能力注册不动),从 stdio 起步、需要时再上远端,几乎总是最优路径。
/sse 路径和 GET 长连接不再需要,全合并到单一 /mcp 端点的 POSTEventSource 重连逻辑全部删除,Streamable HTTP 的会话恢复由协议层处理mcp-session-id 是协议头不是查询参数;无状态下干脆没有它把这套双传输+鉴权+无状态默认的工程结构直接拿走:我做了一个生产就绪的 MCP Server 模板(TypeScript,含真协议测试、CI/CD、npm 发布流和容器部署配置),配合《MCP 开发速查手册》食用,改三处当天发布。入口在我主页。
关于作者:林一舟,夜航船(NightShip)主理人。白天泊岸,夜里出航。