ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MCP协议Streamable HTTP实战:用Node.js搭建SSE与HTTP双通道服务

MCP协议Streamable HTTP实战:用Node.js搭建SSE与HTTP双通道服务 1. 为什么 MCP 的传输层要从 HTTPSSE 换成 Streamable HTTP如果你最近在折腾 MCPModel Context Protocol大概率会遇到一个绕不开的问题服务端到底该用哪种传输方式。早期远程 MCP 走的是 HTTP SSE客户端先连一个/sse端点拿到会话再往/message发请求服务端通过那条 SSE 长连接把结果推回来。这套方案能跑但用久了问题就冒出来了。最典型的是连接不可恢复。SSE 一旦断开客户端没法从断点续传只能重新建连之前会话里的上下文就丢了。其次是服务端压力每个客户端都要挂一条长期占用的连接连接数一上去资源消耗很可观。还有就是消息方向受限服务端基本只能被动响应想主动推点东西得依赖专门的通道。Streamable HTTP 就是冲着这些痛点来的。它把基础通信拉回到普通 HTTP 请求上客户端用 POST/GET 发消息服务端在需要流式返回时把这一次响应升级成 SSE 流。注意是「这一次响应」升级而不是维持一条永久长连接。这样一来无状态服务器成为可能纯 HTTP 服务也能承载 MCP中间件和现有基础设施都能直接复用。这篇文章我会用 Node.js 从零搭一个同时支持 SSE 和普通 HTTP 双通道的 MCP 服务把服务端骨架、客户端连接、双通道切换验证都跑一遍。适合已经了解 MCP 基本概念、想在本地把 Streamable HTTP 落地的开发者。下面所有代码都可以直接复制运行。2. 动手前的准备Node.js 环境与 TaoToken 接入信息先把运行环境理清楚。Node.js 建议用 LTS 版本18 以上都行我本地用的是 20.x。装完之后node -v和npm -v确认一下版本。MCP 官方提供了 TypeScript SDK我们直接基于它来写省得自己处理协议细节。node -v npm -v mkdir mcp-streamable-demo cd mcp-streamable-demo npm init -y npm install modelcontextprotocol/sdk express zod npm install -D typescript tsx types/node types/express这里解释下几个依赖modelcontextprotocol/sdk是官方 SDK封装了 Streamable HTTP 的服务端和客户端express用来起 HTTP 服务zod用来定义工具的入参 schema。tsx让我们能直接跑 TypeScript不用先编译。如果你打算把服务接到真实的模型能力上做联调需要一个能统一管理模型调用和密钥的地方。我平时用 TaoToken 来做这层接入它的 API 地址是https://taotoken.net/api控制台在https://taotoken.net/console。先去控制台创建一个 API Key后面客户端验证和模型对话都会用到。密钥管理页面在https://taotoken.net/api-keys建议单独建一个给这个 demo 用方便随时吊销。注意API Key 不要硬编码进提交到仓库的代码里用环境变量或者.env文件管理.env记得加进.gitignore。3. 服务端骨架用 Node.js 实现 SSE 与 HTTP 双通道3.1 初始化 Streamable HTTP 服务先建一个server.ts。核心思路是用 SDK 的McpServer注册工具再用StreamableHTTPServerTransport把 MCP 协议挂到 Express 的路由上。Streamable HTTP 的关键点在于同一个端点既能处理普通 POST 请求也能在需要时把响应升级成 SSE 流。import express from express; import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StreamableHTTPServerTransport } from modelcontextprotocol/sdk/server/streamableHttp.js; import { z } from zod; const app express(); app.use(express.json()); // 创建 MCP 服务实例 const server new McpServer({ name: demo-streamable-server, version: 1.0.0, }); // 注册一个加法工具走普通 HTTP 通道即可 server.tool( add, 计算两个数字之和, { a: z.number(), b: z.number() }, async ({ a, b }) ({ content: [{ type: text, text: 结果: ${a b} }], }) ); // 注册一个长任务工具用来演示 SSE 流式进度 server.tool( long_task, 模拟一个带进度反馈的长任务, { steps: z.number().min(1).max(10) }, async ({ steps }, extra) { for (let i 1; i steps; i) { await new Promise((r) setTimeout(r, 500)); // 通过 progress 通知客户端走 SSE 通道推送 await extra.sendNotification({ method: notifications/progress, params: { progress: i, total: steps }, }); } return { content: [{ type: text, text: 任务完成共 ${steps} 步 }], }; } );3.2 挂载双通道路由接下来把 transport 挂到/mcp路由上。Streamable HTTP 的约定是POST 用来发消息GET 用来建立 SSE 流接收服务端推送DELETE 用来结束会话。SDK 的 transport 已经帮我们处理了这些方法的区分。const transport new StreamableHTTPServerTransport({ sessionIdGenerator: () crypto.randomUUID(), // 开启 SSE 支持长任务会走流式返回 enableJsonResponse: false, }); // 把 MCP 服务连接到 transport await server.connect(transport); // 统一端点POST/GET/DELETE 都走这里 app.post(/mcp, async (req, res) { await transport.handleRequest(req, res, req.body); }); app.get(/mcp, async (req, res) { // 客户端用 GET 建立 SSE 流 await transport.handleRequest(req, res); }); app.delete(/mcp, async (req, res) { await transport.handleRequest(req, res); }); const PORT 3088; app.listen(PORT, () { console.log(MCP Streamable HTTP Server listening on port ${PORT}); });这里有个细节值得说清楚enableJsonResponse设为false时服务端在需要流式返回的场景下会把响应升级为 SSE如果设为true则强制用普通 JSON 响应。这就是「双通道」的开关所在——同一个端点根据请求内容和配置自动选择走 SSE 还是普通 HTTP。3.3 两种通道的适用场景对照通道类型触发条件适用场景连接特征普通 HTTP请求-响应即结束数学计算、文本处理、单次查询无状态请求完即释放SSE 流服务端需推送进度/通知长任务、多轮对话、实时反馈单次响应内流式可断可续理解这张表你就明白为什么 Streamable HTTP 比老的 HTTPSSE 灵活它不强制你选一种而是让服务端按需决定。4. 客户端连接验证确认双通道切换正常4.1 用 SDK 客户端连接服务端起好后写个客户端脚本client.ts来验证。SDK 的StreamableHTTPClientTransport会自动处理 SSE 和普通 HTTP 的切换。import { Client } from modelcontextprotocol/sdk/client/index.js; import { StreamableHTTPClientTransport } from modelcontextprotocol/sdk/client/streamableHttp.js; const transport new StreamableHTTPClientTransport( new URL(http://localhost:3088/mcp) ); const client new Client( { name: demo-client, version: 1.0.0 }, { capabilities: {} } ); await client.connect(transport); console.log(已连接到 MCP 服务); // 列出可用工具 const tools await client.listTools(); console.log(可用工具:, tools.tools.map((t) t.name));4.2 验证普通 HTTP 通道先调add工具这个走普通 HTTP 请求-响应不会有流式推送。const addResult await client.callTool({ name: add, arguments: { a: 5, b: 6 }, }); console.log(add 结果:, addResult.content); // 预期输出: 结果: 114.3 验证 SSE 流式通道再调long_task这个会触发进度通知走 SSE 流。我们注册一个进度回调来观察。client.setNotificationHandler( { method: notifications/progress }, (notification) { console.log(收到进度推送:, notification.params); } ); const taskResult await client.callTool({ name: long_task, arguments: { steps: 4 }, }); console.log(长任务结果:, taskResult.content);跑起来后你应该能看到类似这样的输出进度是逐步推过来的而不是一次性返回收到进度推送: { progress: 1, total: 4 } 收到进度推送: { progress: 2, total: 4 } 收到进度推送: { progress: 3, total: 4 } 收到进度推送: { progress: 4, total: 4 } 长任务结果: [ { type: text, text: 任务完成共 4 步 } ]看到进度一条条出来就说明 SSE 通道工作正常而add工具秒回结果说明普通 HTTP 通道也没问题。双通道切换验证完成。4.4 用 curl 直接验证端点不想写客户端脚本的话用 curl 也能快速确认服务活着curl -X POST http://localhost:3088/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d {jsonrpc:2.0,id:1,method:tools/list,params:{}}注意Accept头里同时带上application/json和text/event-stream这是 Streamable HTTP 的协商机制——服务端会根据自己是否需要流式返回选择对应的响应类型。5. 本篇常见错误排查报错一Session not found或 404。多半是客户端没带Mcp-Session-Id头。Streamable HTTP 在有状态模式下服务端初始化时会返回一个 session id后续请求都要带上。检查你的 transport 是否正确保存并回传了这个头。如果不需要状态把sessionIdGenerator设为undefined走无状态模式。报错二SSE 流建立后收不到任何消息。先确认Accept头包含text/event-stream很多 HTTP 客户端默认只接受application/json协商失败就不会升级成流。另外检查服务端enableJsonResponse的配置是否符合预期。报错三Cannot find module modelcontextprotocol/sdk/server/streamableHttp.js。SDK 版本太旧。Streamable HTTP 是较新加入的能力升级到最新版npm install modelcontextprotocol/sdklatest。报错四长任务进度通知收不到。确认客户端调用了setNotificationHandler并且 method 名称匹配。服务端发的是notifications/progress客户端注册的也必须是这个字符串大小写和斜杠都不能错。报错五端口被占用。3088 被别的进程占了就换个端口同时记得改客户端里的 URL。用lsof -i :3088查一下谁占着。报错六连接模型时报鉴权失败。如果你把服务接到了真实模型调用上检查 API Key 是否正确、有没有过期。密钥可以在https://taotoken.net/api-keys重新生成接入细节参考https://taotoken.net/doc。6. 把双通道服务接到真实模型与编码工作流本地跑通只是第一步。实际项目里你往往需要把这个 MCP 服务接到真实的模型对话或者编码 Agent 上。这时候有几个方向可以走。如果你只是想验证模型能不能正确调用你注册的工具可以直接在模型对话里挂上这个 MCP 服务观察工具调用链路是否顺畅入口在https://taotoken.net/model-chat。这种方式适合快速验证工具描述、参数 schema 写得对不对。如果你是要做长期的编码辅助或者 Agent 工作流比如让模型在写代码时自动调用你的工具那更适合用 Coding Plan 这类持续性的方案配置入口在https://taotoken.net/coding-plan。它更适合需要稳定调用、长期运行的场景而不是一次性的对话验证。至于 Claude Code 这类工具的接入可以参考https://taotoken.net/claude-code-anthropic里的说明把 MCP 服务和编码环境串起来。回到技术本身Streamable HTTP 的双通道设计本质上是把「什么时候需要流」的决定权交给了服务端。普通请求走轻量 HTTP长任务和实时反馈走 SSE两者共用一个端点既省去了维护/sse和/message两套逻辑的麻烦也让断线恢复变得可行——客户端重新发个 GET 就能续上流。这套模式在弱网、多实例部署的场景下优势会越来越明显。把上面的骨架跑通之后你可以试着把long_task换成真实的文件处理或模型推理观察进度推送在真实负载下的表现。
返回列表