跳到主要内容

把服务器放上网络 — SSE、Streamable HTTP 与断线续传

这一章讲三件事: 为什么 STDIO 出不了本机、上网要换成什么; MCP 的两代网络传输各长什么样——以及为什么先学的那代已经被判了死刑; 联网之后才出现的三个问题:会话怎么维持、断线怎么续、进度怎么推。 原书用第 4、5 两章讲这件事,我们并成一章,因为它们是一条推理链: 每一代的毛病,正是下一代的样子。

1. 顶层全景:一台上网的 MCP 服务器,到底是台 Web 服务器

先立住本章第一个、也是最根本的画面:

STDIO 服务器:客户端 spawn 它,走进程管子,不出本机
上网的服务器:它自己就是一台 Web 服务器,挂着网址,客户端隔着网络来连

图说:换传输不换协议——管子里流的还是第 02 章那套 JSON-RPC,
只是「管子」从进程管道换成了 HTTP 请求。

「上网」的意思是:你的 Express 程序(Express 是 Node.js 里最常用的 Web 框架, 负责「监听端口、按网址分发请求」)把 MCP 服务器挂在一个或两个网址下面1。 挂几个网址、各管什么,两代传输的答案不一样——这就是本章的全部争执。

本章主走查是同一台服务器、同一个 add 工具(两数相加), 先后用两代传输各调一遍 a=5, b=10,最后故意断一次线, 看第三代本事(断线续传)怎么把丢掉的消息找回来。

2. 第一代:SSE,一根只出不进的长管子

SSE(Server-Sent Events,服务器推送事件)是 HTTP 上的一种老技术: 客户端发起一个普通请求,服务器偏偏不结束它,把这条连接一直挂着, 之后服务器随时可以往里「推」一条消息——单向的,只有服务器能讲,客户端只能听2。 推过来的内容有固定格式:内容类型就是 HTTP 的 content type 字段—— 标明「这个响应是什么格式」;SSE 流里它固定是 text/event-stream

这个字段待在 HTTP 的头部(header——响应正文前面那几行说明性质的信息, 比如格式、长度,和正文分开放);每条消息是纯文本, 带 event:data: 这样的字段,两个换行结束一条**2。 浏览器里接收它的接口叫 EventSource,它最典型的用途是股票行情、比分直播这类 「服务器随时有新料」的页面2

MCP 拿它这样用:拆成两个网址3:

网址方法管什么
/sseGET客户端来连,服务器把这条连接挂成长管子,往后推消息都走它
/messagesPOST客户端要发消息(握手、调工具),往这里 POST

代码上,服务器要为每条 SSE 连接建一个 SSEServerTransport,按 sessionId(会话编号) 存进一张表;POST 到 /messages 的请求带着这个编号来, 服务器照编号查出该用哪条管子4:

app.get('/sse', async (req, res) => {
const transport = new SSEServerTransport('/messages', res);
transports.sse[transport.sessionId] = transport; // 登记:这个会话走这条管子
res.on("close", () => { delete transports.sse[transport.sessionId]; });
await server.connect(transport);
});
app.post('/messages', async (req, res) => {
const transport = transports.sse[req.query.sessionId]; // 照编号找管子
…transport.handlePostMessage(req, res, req.body)
});

主走查第一程(cURL 三步打 SSE)——书里给的全程,三个终端动作5:

① curl http://127.0.0.1:3000/sse
← event: endpoint
data: /messages/?session_id=53ddee76d5ec4b4aaa9420f24462210a
(服务器说:你的管子挂好了,以后发消息往这个带编号的网址发)

② 换个终端,POST /messages/?session_id=53ddee76… 发 notifications/initialized

③ 同终端再 POST tools/list
← 回应不出现在这个终端!它从第 ① 步那根长管子里,推回第一个终端:
event: message
data: {"result":{"tools":[{"name":"add",…}]},"jsonrpc":"2.0","id":1}

第 ③ 步就是 SSE 形态的精髓:你从一个门递信,回信从另一个门出来。 用 Inspector 测它得到的答案和 STDIO 时代一模一样:add(5, 10)Result: 156—— 又一次验证「换传输不换协议」。

3. 剧情反转:这一节教你的是一门「死」技术

现在说本章最重要的时间戳。书里写得很直白:SSE 传输已于 2025 年 5 月被废弃 (deprecated——官方宣布「以后别用新的了,但旧的还得继续维护一阵子」), 由 Streamable HTTP 取代7

那为什么还要学?作者给了两个数字理由:宣布废弃时, 20 个参考服务器、50 多个官方集成、186 个社区实现都还在用 SSE, 全生态累计超过 2000 台——你写客户端去连别人的服务器,大概率撞见它8新一代要学,老一代要认,这是维护者的宿命。

那 SSE 到底哪里不好?下一代正是它的「错题本」9:

SSE 的毛病Streamable HTTP 的解法
两个网址,实现和运维都绕一个网址 /mcp 全包
客户端到一个门递信、另一个门收信,断线就断片支持断线续传,重连后接着上次的位置收
长连接穿过负载均衡、网关时常常要特殊照顾普通 POST 请求,现代 HTTP 设施全都认
单向:推只能服务器→客户端同一个端点双向都行

4. 第二代:Streamable HTTP,一个网址,两种回答

Streamable HTTP(可流式 HTTP)只有一个网址,约定俗成叫 /mcp, 客户端把消息 POST 上去;妙处在于:服务器可以按情况自选怎么回10——

  • 事情简单,回一个普通 JSON(一问一答,干干脆脆);
  • 事情要推好几条(比如先推三条进度再给结果),回一条 SSE 流,推完就关。

客户端在请求的 Accept 头里声明两种都能收: Accept: application/json, text/event-stream—— 「你给哪种我接哪种」,选择权留给服务器10

主走查第二程(cURL 打 /mcp),对照着第 2 节看,差别全在细节里11:

① POST /mcp initialize(protocolVersion: "2025-03-26")
← 响应头里带回 mcp-session-id: 39a0b504364140ce97d8eded79b1c244

② POST /mcp notifications/initialized
头部带 mcp-session-id: 39a0b504… ← 注意:编号从网址参数挪到了请求头

③ POST /mcp tools/call name=echo, message="chris"
← event: message data: {"method":"notifications/message",…"Processing file 1/3:"}
event: message data: {…"Processing file 2/3:"}
event: message data: {…"Processing file 3/3:"}
event: message data: {"jsonrpc":"2.0","id":1,"result":{…"Here's the file content: chris"…}}

三件事一次看全:单一网址、编号改走 mcp-session-id 请求头、 一次调用的进度通知和最终结果,从同一条响应流里依次推回来—— 不再有两个门。会话还是那个会话(第 02 章讲过:一次连接期间双方记住彼此的状态), 只是携带它的方式从网址参数变成了头部12

5. 联网才有的人事①:断线续传

手机进隧道,连接断了。SSE 时代,管子断了就是断了,断的时候推到一半的消息,丢了。 Streamable HTTP 给了第二条命,这套机制叫断线续传(resumability)13:

服务器这一侧,给传输挂一个事件存储(event store)—— 每推一条消息,先存一份再发,每条都编了号;书里用的是 SDK 自带的 InMemoryEventStore(内存版,重启就没,只能演示用)14:

transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
eventStore, // 挂上它,续传能力就开了
onsessioninitialized: (sessionId) => { transports[sessionId] = transport; }
});

客户端这一侧,断线前记住两样:会话编号(mcp-session-id) 和最后收到的消息编号(last-event-id);重连时改用 GET 请求 /mcp, 把这两样放进头部——服务器就从存储里把那之后的消息重放给你13

主走查第三程(故意断线):书里调 process-files 处理三个 CSV (一种逗号分隔的纯文本表格格式,Excel 能直接打开)文件, 流里依次推来 sales1.csv processedsales2.csv processed(记住这条的编号 …_meh2n52f)、sales3.csv processed 和最终结果。 假设收完第二条就断了——重连15:

GET /mcp
头部: mcp-session-id: 957f11af-…
last-event-id: …_meh2n52f ← 「我最后只收到这条」

← event: message data: {…"sales3.csv processed"} ← 第三条,补上了
event: message data: {"result":{…"Files processed: 3"…}} ← 最终结果,也没丢

一条消息都没丢。 书末特意提醒两个坑:续传要发 GET 不是 POST (POST 会被当成新会话);内存事件存储不能上生产,得换成数据库持久化16

6. 联网才有的人事②:通知怎么发、怎么收

第 02 章讲过通知这种「不许回答」的消息;到了流式传输上,它有了真正的用武之地—— 上面那些「Processing file 1/3」就是通知。要在自己服务器里发,三步17:

  1. 创建服务器时声明 capabilities: { logging: {} }——没声明这个能力,就不许发;
  2. 工具的处理函数拿到第二个参数(上下文对象),从中解构出 sendNotification;
  3. 在干活途中调用它,发 notifications/message

客户端收通知也不混在普通响应里,要单挂一个处理器18:

client.setNotificationHandler(LoggingMessageNotificationSchema,
(notification) => {
console.log(`Notification: ${notification.params.level}${notification.params.data}`);
});

书里那台客户端跑起来的样子:三条 File N processed 通知先到, All files processed: Process files 的最终结果最后到—— 通知管「过程」,响应管「结果」,两条通道互不耽误18。 配上断线续传,价值就大了:网再差,用户既不会漏进度,也不会漏结果19

7. 作者的判断与证据

有证据的:

  • SSE 于 2025 年 5 月废弃、废弃时的生态规模(20/50+/186),书里给了出处链接78;
  • SSE 双端点、Streamable HTTP 单端点与 Accept 双类型、续传两个头部, 全部代码与 cURL 输出都在书里410111315;
  • 内存事件存储不适合生产,是作者自己的提醒16

作者的判断:

  • 「Inspector 体验更好,cURL 偏底层、但能让你看清协议怎么跑」20——教学取舍;
  • 把 SSE 专列一章(而不是一句话带过)——作者明说这是为「你会去对接存量服务器」准备的8

判断(我们的,不是书里的): 这一章是全书「时间戳」最该被盯紧的一章。 书里教的是 2025-03-26 那一代的 Streamable HTTP:有会话、有 GET 流、有断线续传。 而我们书架上的规范拆解显示,2026-07-28 版把这三样全改了:HTTP 会话头被取消、 独立的 GET 流被取消、Last-Event-ID 续传被砍掉——断流即丢,客户端拿新请求重来; 「服务器主动推变更」改由订阅机制承担21。 也就是说,本章第 4、5 节今天读是考古价值大于使用价值: 懂它能帮你维护 2025 年代写的服务器,但新写的服务器不该再照搬如果错,会错在: 如果你维护的系统钉在老版协议(客户端与服务器协商版本时落在 2025 年的版本号上),那这一章仍然是准的操作手册——协议版本是按握手时协商定的, 老组合不会自动升级。

8. 边界与局限

  • 会话与续传是 2025 年代协议的形态,见上节判断块;书里完全没有提规范后来这次大改 (书写于 2025 年,改发生在出版后);
  • 续传的客户端这一侧,书只讲了「你要监听浏览器的断网事件、自己存编号」, 没有给可运行的客户端续传实现16——SDK 客户端是否自动重放,书里没说;
  • SSE 那套 /messages 路由在多实例部署下有个大坑书里没提: POST 可能落到没存那条会话的另一台机器上(消息要按会话粘住才行)—— 这也是 SSE 被网关嫌弃的实际原因之一(补充,不在书里,来自通用知识);
  • 通知的 notifications/message 只是日志式通知的一种;进度类通知 (notifications/progress进度标识——progressToken,客户端发起请求时给的记号, 服务器靠它把进度通知对回那一次请求)书里第 02 章提过名字,这一章没有展开演示。
  • 两原章各自的作业这一章没有交代:SSE 章留了「自建电商 SSE 服务器」、 Streamable HTTP 章留了「给 process-files 工具加进度通知」——作业是纯练习, 不影响机制理解;想练手的读者直接去原文 (text/06-fm-building-sse-servers.txt:740,搜「Assignment – SSE server」; text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:1328,搜「Assignment」)。

9. 可带走的

  1. STDIO 本机、HTTP 上网;上网的服务器本质上就是一台 Web 服务器;
  2. SSE = 一根只出不进的长管子 + 一个收信的 POST 端点;回信和去信走不同的门;
  3. SSE 已废弃(2025-05),但 2000+ 存量服务器在跑,写客户端要认它78;
  4. Streamable HTTP = 单端点 /mcp;Accept: application/json, text/event-stream 让服务器自选回法;
  5. 会话编号在 2025 代走 mcp-session-id 请求头(不再是网址参数);
  6. 断线续传 = 服务器存消息(event store)+ 客户端重连带 last-event-id 发 GET; 内存存储只能演示;
  7. 发通知先声明 logging 能力,再用 sendNotification;收通知用 setNotificationHandler;
  8. 写新代码前先查协议版本:本章的会话/续传在 2026-07-28 规范里已被移除21

10. 原文地图

主题原书章原文位置
SSE 概念与格式Building SSE Serverstext/06-fm-building-sse-servers.txt:53(搜「unidirectional communication」)
废弃声明同上text/06-fm-building-sse-servers.txt:23(搜「deprecated in favor of Streamable HTTP」)
2000+ 存量同上text/06-fm-building-sse-servers.txt:25(搜「2000+ MCP servers」)
双端点同上text/06-fm-building-sse-servers.txt:93(搜「SSE endpoint」)
Express SSE 代码同上text/06-fm-building-sse-servers.txt:134(搜「SSEServerTransport」)
cURL 三步同上text/06-fm-building-sse-servers.txt:264(搜「event: endpoint」)
add(5,10)=15同上text/06-fm-building-sse-servers.txt:603(搜「Result: 15」)
为什么是新标准Creating MCP Servers for Web Consumption with Streamable HTTPtext/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:93(搜「Single endpoint simplicity」)
废弃时生态规模同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:83(搜「186 community-developed servers」)
Accept 双类型同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:161(搜「application/json, text/event-stream」)
续传概念同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:181(搜「resume the data exchange where it was」)
eventStore 代码同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:225(搜「InMemoryEventStore」)
/mcp 服务器全码同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:514(搜「app.post('/mcp'」)
logging 能力同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:540(搜「capabilities: { logging: {} }」)
sendNotification同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:339(搜「sendNotification」)
setNotificationHandler同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:1060(搜「setNotificationHandler」)
mcp-session-id 是头同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:907(搜「isn't a query parameter」)
断线重放走查同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:1283(搜「last-event-id」)
GET 不是 POST同上text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:1302(搜「GET /mcp」)

Footnotes

  1. 出处:「Building SSE Servers」第 111-129 段(text/06-fm-building-sse-servers.txt:115,搜「expose an SSE server as a web application」)。

  2. 出处:「Building SSE Servers」第 53-83 段(text/06-fm-building-sse-servers.txt:53,搜「unidirectional communication from server to client」;:61,搜「text/event-stream」;:65,搜「EventSource」)。 2 3

  3. 出处:「Building SSE Servers」第 85-105 段(text/06-fm-building-sse-servers.txt:93,搜「SSE endpoint」)。

  4. 出处:「Building SSE Servers」第 131-172 段(text/06-fm-building-sse-servers.txt:134,搜「SSEServerTransport」;:168,搜「handlePostMessage」)。 2

  5. 出处:「Building SSE Servers」第 256-282 段(text/06-fm-building-sse-servers.txt:264,搜「event: endpoint」)与第 673-710 段(text/06-fm-building-sse-servers.txt:675,搜「notifications/initialized」;:710,搜「event:message」)。

  6. 出处:「Building SSE Servers」第 599-636 段(text/06-fm-building-sse-servers.txt:603,搜「Result: 15」)。

  7. 出处:「Building SSE Servers」第 23 段(text/06-fm-building-sse-servers.txt:23,搜「deprecated in favor of Streamable HTTP」)。 2 3

  8. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 77-85 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:83,搜「186 community-developed servers」);「2000+」见「Building SSE Servers」第 25 段(text/06-fm-building-sse-servers.txt:25,搜「2000+ MCP servers」)。 2 3 4

  9. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 91-141 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:93,搜「Single endpoint simplicity」;:113,搜「Last-Event-ID」)。

  10. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 143-167 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:161,搜「application/json, text/event-stream」)。 2 3

  11. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 879-966 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:883,搜「2025-03-26」;:958,搜「Processing file 1/3」)。 2

  12. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 907-915 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:907,搜「isn't a query parameter」)。

  13. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 175-215 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:181,搜「resume the data exchange where it was」;:195,搜「Mcp-Session-Id」)。 2 3

  14. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 217-251 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:225,搜「InMemoryEventStore」)。

  15. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 1230-1294 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:1249,搜「losing connection」;:1283,搜「last-event-id」)。 2

  16. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 1300-1314 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:1302,搜「GET /mcp」;:1312,搜「not good for production」)。 2 3

  17. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 306-369 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:312,搜「sendNotification」)与第 690-706 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:695,搜「logging: {}」)。

  18. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 1048-1140 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:1060,搜「setNotificationHandler」;:1132,搜「File 1 processed」)。 2

  19. 出处:「Creating MCP Servers for Web Consumption with Streamable HTTP」第 424-432 段(text/07-fm-creating-mcp-servers-for-web-consumption-with-st.txt:428,搜「spotty internet connection」)。

  20. 出处:「Building SSE Servers」第 712-724 段(text/06-fm-building-sse-servers.txt:712,搜「I prefer to use the Inspector tool」)。

  21. 补充(不在书里,依据我们的 protocol 书架):2026-07-28 版规范取消了 HTTP 会话与 GET 流,砍掉 Last-Event-ID 可恢复 SSE(断流即丢,客户端拿新 id 重发),服务器主动推变更改由 subscriptions/listen 承担。 依据: shelf=ai-protocol-reference/mcp-spec#05-transports.md 事实=该章记载 2026-07-28 版砍掉 HTTP 会话、GET 流与可恢复 SSE,改用 subscriptions/listen。 2