Streamable HTTP transport + 官方 TS SDK v1.x
Day 51 我们读懂了 stateless 规范并列出进程内 mock 缺的 5 项,第 1 项就是 transport(没有 HTTP)。今天补这一项:用官方 @modelcontextprotocol/sdk 的 McpServer + StreamableHTTPServerTransport 真起一个监听本地端口的 HTTP server,并用 curl 打通 initialize 握手
阶段: B6 · 真 MCP server (2026-07-28 spec)(Day 51-60) 标签: #mcp #streamable-http #typescript-sdk #transport
今日导引(由浅入深)
Day 51 我们读懂了 stateless 规范并列出进程内 mock 缺的 5 项,第 1 项就是 transport(没有 HTTP)。今天补这一项:用官方 @modelcontextprotocol/sdk 的 McpServer + StreamableHTTPServerTransport 真起一个监听本地端口的 HTTP server,并用 curl 打通 initialize 握手。这是 B6「把工具抬到网络边界」从纸面落到进程的第一锤——从今天起,工具调用要走真实的 HTTP request/response,而不是 handle(req) 函数调用。今日最小可判定产出:一段终端 transcript,curl 收到合法 initialize 响应(含 protocolVersion/serverInfo)。
0. 协议背景速记
- 承上:Day 51 差距表第 1 项是「transport(无 HTTP,只是函数调用)」——今天就补这一项。
- 手段:不再自己写 HTTP 框架,直接用官方
@modelcontextprotocol/sdk,把协议层交给 SDK。 - 判定点:curl 打通
initialize握手(拿到protocolVersion/serverInfo)即证明 transport 通了。
1. 机理精读
官方 SDK 是什么:
@modelcontextprotocol/sdk(TypeScript)把 MCP 协议的客户端/服务端实现都封好了。- 开发者只需三步:声明工具、挂一个 transport、listen 端口。
- 它把我们 Day 51 在
toolRegistry.ts里手写的 JSON-RPC 信封/校验/dispatch 全部内置——等于把「协议层」交给 SDK,自己只写「业务层」。
StreamableHTTPServerTransport 的报文模型:单一 HTTP endpoint(约定路径如 /mcp),承载两种 HTTP 动作——
- POST:客户端把 JSON-RPC 请求体(
tools/list、tools/call等)POST 上来,server 同步在响应体里回 JSON-RPC 结果。这是主路径。 - GET:升级为 SSE(Server-Sent Events),用作 server→client 的推送通道——发通知(notifications)、进度(progress)。这是 Day 51 说的「stateless 不禁止 SSE,只是不强制 session 绑定」的落地点。
为什么是「单一 endpoint + 两动作」而不是多路径 REST?因为 MCP 是 RPC 语义不是资源语义——方法名在 JSON-RPC 体里(method 字段),不在 URL 里,于是一个 endpoint 足矣,路由/缓存交给 HTTP 基础设施按需做。
生命周期三步:initialize 握手 → initialized 通知 → 进入请求循环。
initialize:客户端发首个请求,告知自己支持的protocolVersion和 capabilities;server 回自己的protocolVersion+serverInfo(name/version)+ 它提供的 capabilities(如tools)。这一步是能力协商——双方确认彼此说同一版协议。initialized:客户端确认握手完成,发一个 notification(无需响应)。- 请求循环:之后才能发
tools/list/tools/call。
为什么先打通 initialize 而不是直接 tools/call:
- 握手是协议合法性的第一道门。
- 如果
initialize都回不出合法的protocolVersion/serverInfo,说明 transport 或 SDK 版本没接对,后面tools/list/tools/call一定也是错的。 - 所以今天的判定点定在
initialize响应——它是「transport 真的通了」的最小充分证据,先验通路、再验内容。
与进程内 mock 的边界:
- 今天补的是 Day 51 差距表的第 1 项(transport)。auth(第 2 项)、跨进程业务工具(Day 56 才迁)暂不碰。
- 今天的 server 可以先不挂任何业务工具,光验证 transport 生命周期。
McpServer与toolRegistry.ts的关系:SDK 的McpServer将取代手写的McpToolRegistry成为真 server 的注册表,但toolRegistry.ts作为「我们理解协议形状」的对照基线保留不删。
2. 推导 / 手算 / 代码走读
今天没有现成 src 文件可走读(新建 server 进程是待建产出)。给一个最小握手 transcript 的预期形状,逐步说明每个字段从哪来:
- 客户端 POST 请求体:
{"jsonrpc":"2.0","id":1,"method":"initialize", "params":{"protocolVersion":"2026-07-28","capabilities":{}, "clientInfo":{"name":"curl","version":"0"}}}method:"initialize"→ 触发握手分支。params.protocolVersion→ 客户端声明自己说哪版协议。
- server(
McpServer({name,version})+StreamableHTTPServerTransport)回响应体:{"jsonrpc":"2.0","id":1, "result":{"protocolVersion":"2026-07-28", "serverInfo":{"name":"<你给的name>","version":"<你给的version>"}, "capabilities":{"tools":{}}}}result.serverInfo直接来自构造函数new McpServer({name,version})的入参。result.capabilities.tools表示 server 提供 tools 能力(即便此刻还没注册工具,capability 已声明)。
- 判定:HTTP 状态 200 + 响应体是合法 JSON-RPC(
jsonrpc:"2.0"+ 同一个id:1+result里有protocolVersion和serverInfo)即通过。
对照 toolRegistry.ts 的 handle()(line 199):手写版没有 initialize 分支(它只认 tools/list/tools/call),因为进程内 mock 不需要握手——这正是「真 server 比 mock 多出来的生命周期」。
为什么进程内 mock 可以省掉握手:
- 握手的本质是「跨网络的两端协商协议版本与能力」;进程内两端是同一份代码、同一版协议,协商无意义。
- 一旦上了网络(异构客户端、不同 SDK 版本),版本协商才变成刚需——这就是为什么 transport 层一引入,
initialize就成了必经第一步。 - 因此今天的工作不只是「加个 HTTP」,而是补上 mock 因「同进程」而合法省略的整套生命周期。
curl 验证的几个实操要点:
- 加
-H 'Content-Type: application/json'与-H 'Accept: application/json, text/event-stream',让 server 正确解析请求体并知道客户端能收 SSE。 - 看 HTTP 状态码(应 200)+ 响应体是否含
result.protocolVersion。 - 若返回 4xx/握手错误,多半是 SDK 版本与请求里
protocolVersion不匹配——回到npm view核对版本。
3. 今日实战
- 安装依赖:
npm i @modelcontextprotocol/sdk zod(仓库用 pnpm 则pnpm add @modelcontextprotocol/sdk zod)。 - 新建最小 server 进程文件:
new McpServer({ name, version }),挂StreamableHTTPServerTransport,listen一个本地端口。 - 终端验证:
确认返回 200 + 合法 JSON-RPC 响应(含curl -X POST localhost:PORT/mcp \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'protocolVersion/serverInfo)。 - 执行当周用
npm view @modelcontextprotocol/sdk version重验 SDK 版本号,记进 transcript。
4. 今日实测 / 产出
- 待建——终端 transcript:curl 收到合法
initialize响应(含protocolVersion/serverInfo)。 - 无需 key(纯本地 server,不涉及 LLM)。
- SDK 版本号执行当周
npm view @modelcontextprotocol/sdk version重验。
5. 常见误区 / 陷阱
- 跳过
initialize直接tools/call:未握手就调用,规范要求 server 拒绝;先把握手打通再说。 - 混用 transport:把
StreamableHTTPServerTransport(网络部署主线)和老的SSEServerTransport/stdio-only(已不适合网络部署)搞混。 - 照抄 SDK 旧版样例:v1.x 仍在快速迭代,
StreamableHTTPServerTransport的构造/挂载 API 可能微调——以安装当周 changelog 为准,不要照搬几个月前的博客。 - 以为
capabilities.tools为空就是出错:握手阶段还没注册业务工具是正常的,capability 声明与工具注册是两件事。
6. 学习资源(每条带 YYYY-MM)
- MCP TypeScript SDK — server 文档(
McpServer/StreamableHTTPServerTransport,2026-03) - MCP Specification — Streamable HTTP transport / 生命周期章节(草案,2026-07-28 预定定稿)
@modelcontextprotocol/sdknpm changelog(执行当周查最新版本)- Anthropic《Model Context Protocol》官方介绍(2024-11 首发,持续更新)
7. 一句话回顾
- transport 补齐:用
McpServer+StreamableHTTPServerTransport把工具从「进程内函数调用」抬到「单一 HTTP endpoint(POST 主路径 + GET SSE)」。 - 生命周期:
initialize(能力协商)→initialized(通知)→ 请求循环;今天只验第一步。 - 判定锚点:curl 拿到含
protocolVersion/serverInfo的合法initialize响应 = transport 真通了。 - 诚实状态:server 进程与 transcript 均为「待建」,纯本地、无需 key。
8. 在 B1→B18 能力曲线上的位置
- B1-B5(Day 1-50):进程内能力——评测/attention/agent loop/reasoning/tool engineering,所有工具都是本进程函数。
- B6(Day 51-60,当前):把工具抬到 MCP 网络边界。Day 51 读规范、列差距;今天(Day 52)补 transport;后续补 Zod 声明 / list / call / 业务工具。
- B7(下一批):OAuth 2.1 鉴权 + MCP 安全(工具投毒防护)+ CI gate——把今天裸跑的 server 加上授权与安全闸门。
- 一句话:今天是「工具能被远端 HTTP 调用」这条能力的第一块地基,没有它,后面 list/call/AML 工具/鉴权全无处可挂。
SOTA检查 (2026-06 更新)
- 当前主流:官方
@modelcontextprotocol/sdk+StreamableHTTPServerTransport是 TS 侧搭 MCP server 的标准路径,仍是 SOTA。 - 是否仍 SOTA:TS SDK 仍在 v1.x 快速迭代,
StreamableHTTPServerTransportAPI 可能微调;以安装当周 changelog 为准。 - 过时黑名单(AVOID):用已弃用的 stdio-only 或老
SSEServerTransport(非 Streamable)作网络部署主线。 - 下次复查点:安装当周
npm view @modelcontextprotocol/sdk version+ changelog;07-28 规范定稿后复验生命周期字段。
衔接
- 昨天:Day 51 — MCP 2026-07-28 stateless 规范精读(列出进程内 mock 缺的 5 项,第 1 项 transport)
- 今天:用官方 SDK 起一个真 HTTP server,curl 打通
initialize握手——补齐 transport 项 - 明天:Day 53 — Zod 工具 schema vs 现有 JsonSchema 子集(用 Zod 定义工具输入,替代手写 JsonSchema 校验)