[MCP] StreamableHTTPServer

news/2025/10/6 14:25:35/文章来源:https://www.cnblogs.com/Answer1215/p/19127599

远程通信方式

通信方式:

  1. Stdio: 推荐,高效、简洁、本地
  2. Streamable HTTP: 远程

前置知识

SSE 全称 Server-Sent Events,中文是“服务器发送事件”。是一种基于 HTTP 的单向通信协议,由浏览器发起连接,服务器可以持续不断地向客户端推送数据

你可以把它想象成:“浏览器打开一个通道,然后服务器不断地往里面发消息。”

SSE 特点

  1. 协议:基于 HTTP(长连接)
  2. 方向:单向:服务器 -> 客户端
  3. 格式:文本流,内容类型为 text/event-stream
  4. 浏览器支持:所有现代浏览器都支持(IE 除外)
  5. 应用场景:单方面需要推送的时候。实时通知、消息流、状态更新、股票/天气数据等

消息格式

SSE 协议规定,服务器以 text/event-stream 格式不断推送消息,每条消息格式如下:

event: 事件名   # 可选,默认是 message 事件
id: 唯一ID     # 可选
retry: 3000   # 客户端断线重连间隔,单位毫秒,可选
data: 内容     # 必需,可以多行

每条消息用空行 \n\n 作为结尾。

事件类型

如果服务器发送的数据中没有指定事件类型,浏览器端会将其作为默认事件类型 message 来处理:

data: 这是默认消息(data 代表要发送的消息)

客户端监听方式:

eventSource.addEventListener("message", (e) => {console.log("默认事件:", e.data);
});

可以自定义事件名:使用 event: 字段

event: update(事件名)
data: 新的更新内容

客户端监听方式:

eventSource.addEventListener("update", (e) => {console.log("收到 update 事件:", e.data);
});

课堂练习

SSE 服务器推送信息示例

StreamableHTTP

Streamable HTTP 是 MCP 中 用于 Web 环境 的通信方式。

客户端基于 HTTP POST 发送 JSON-RPC 请求,例如:

  • initialize
  • callTool
  • listResources
sequenceDiagramparticipant Clientparticipant ServerClient->>Server: POST /mcp(initialize / callTool)Server-->>Client: JSON一次性响应 或者 SSE 流响应

服务端可返回:

  • 普通 JSON 响应(application/json)
  • 流式 SSE 响应(text/event-stream)

另外,客户端还可以和服务端建立 持久 SSE 连接,用于监听以下事件:

  • notifications/resources/list_changed
  • notifications/tools/list_changed
sequenceDiagramparticipant Clientparticipant ServerClient->>Server: GET /mcp(建立 SSE)Server-->>Client: 推送 notifications(如 list_changed)

StreamableHTTP 使用场景

  1. 通过 HTTP 接收 远程 请求(如前端网页、API 网关)
  2. 需要支持 多客户端 并发访问
  3. 浏览器与 MCP Server 通信
  4. 流式响应(如 SSE 推送)需求

官方接口

在官方 SDK 里面,提供了相应的接口:StreamableHTTPServerTransport

使用方式:

import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { randomUUID } from "crypto";const transport = new StreamableHTTPServerTransport({sessionIdGenerator: () => randomUUID(), // 为每个连接生成唯一会话ID
});

该接口内部提供了一系列的方法,其中需要了解的,是 handleRequest 方法。

handleRequest(req: IncomingMessage,res: ServerResponse,parsedBody: unknown // 解析后的请求体
): Promise<void>;

内部处理流程:

  1. 解析 body,识别 JSON-RPC 方法(如 "initialize", "callTool")
  2. 将请求路由给 MCP Server 的对应处理函数
  3. 根据返回结果的 Content-Type 自动来决定是普通 JSON 响应还是流式响应

http-server.js

import express from "express";
import { setCommonHeaders } from "./utils.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { randomUUID } from "crypto";
import { createMCPServer } from "./mcp-server.js";const app = express(); // 创建 express 服务器// 存储所有的连接
// 键:sessionId
// 值:sessionId 对应的连接
const transports = {};// 添加JSON解析中间件
app.use(express.json());app.get("/mcp", (req, res) => {setCommonHeaders(res);// 代表当前的 GET 方法请求不被允许// 服务器支持的方法是 POST 方法,而不是当前请求的方法res.status(405).set("Allow", "POST").send("当前服务器不支持GET方法,仅支持POST方法");
});app.post("/mcp", async (req, res) => {setCommonHeaders(res);// 处理当前的这一次请求try {// 先提取一些信息const body = req.body; // 拿到请求体const method = body?.method; // 拿到这一次请求体里面的方法 initialize、tools/call、tools/listconst sessionId = req.headers["mcp-session-id"]; // 当前这一次连接的 sessionIdconst transport = sessionId && transports[sessionId]; // 拿到 sessionId 对应的连接if (!transport && method === "initialize") {// 进入此分支,说明当前这一次请求是一个初始化请求// 创建一个新的 StreamableHTTP 类型的连接const newTransport = new StreamableHTTPServerTransport({sessionIdGenerator: () => randomUUID(), // 为每个连接生成唯一会话ID});// 为当前的连接绑定一个 close 事件// 会在客户端关闭连接的时候触发newTransport.onclose = () => {// 需要做清理工作if (newTransport.sessionId) {delete transports[newTransport.sessionId];}};// 为当前的这个新连接,创建 MCP Serverconst mcpServer = createMCPServer();await mcpServer.connect(newTransport);// 根据具体的方法,连接 MCP Server 去做处理// 如果是初始化请求,还会生成 sessionIdawait newTransport.handleRequest(req, res, body); // 调用该方法处理这一次的请求if (newTransport.sessionId) {// 进行一个存储transports[newTransport.sessionId] = newTransport;}return;}// 没有进入上面的分支,说明是非初始化请求if (transport) {// 说明连接是存在await transport.handleRequest(req, res, body);return;}// 没有 transport(没有连接),又不是做初始化res.status(400).json({error: "非法的请求",message: "非法的 SessionId 或者非初始化操作",});} catch (err) {console.error(`出错了,对应信息${err.message}`);}
});const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {console.log(`MCP Server 运行在 http://localhost:${PORT}/mcp`);
});

mcp-server.js

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";export const createMCPServer = () => {const server = new McpServer({name: "http-mcp-server",version: "0.1.0",});server.registerTool("两数之和",{title: "数字加法计算器",description: "计算两个数字的和",inputSchema: {num1: z.number().describe("第一个数字"),num2: z.number().describe("第二个数字"),},},async ({ num1, num2 }) => {return {content: [{type: "text",text: `计算结果: ${num1} + ${num2} = ${num1 + num2}!!!`,},],};});return server;
};

utils.js

// 设置响应头的方法
export function setCommonHeaders(res) {// 允许哪些域(Origin)可以访问该服务res.setHeader("Access-Control-Allow-Origin", "*");// 允许客户端在跨域请求中使用哪些 HTTP 方法res.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, OPTIONS");// 指定客户端请求时允许携带的自定义请求头res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");// 允许前端 JS 访问响应中的哪些自定义头res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/929406.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HttpServletResponse 对象用来做什么? - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

谁有国外hs网站手表购物网站排名

核心提示&#xff1a;相信很多机友都会使用UC浏览器&#xff0c;但是其多个功能你们能了解多少? 下面小编就为大家介绍UC浏览器云标签使用方法吧! 1、打开【UC浏览器】&#xff0c;点击【多窗口】按钮&#xff0c;然后点击【云标签】;(如下图所示) 2、登陆微博、QQ等&#…

牛客 周赛109 20250924

牛客 周赛109 20250924 https://ac.nowcoder.com/acm/contest/116945 A: 题目大意: 给定两个坐标,判断和原点一起能否构成一个直角三角形 void solve(){double x, y, u, v;cin >> x >> y >> u &g…

域名网站如何做市场推广长沙网页网站制作

在赋予一个string值的时候&#xff0c;可以用单引号或者双引号。 1.单引号和双引号的区别&#xff1a; 单引号&#xff1a;不会翻译变量。 双引号&#xff1a;会翻译变量&#xff0c;会将变量替换为之前赋予变量的值。 例子&#xff1a; &#xff08;1&#xff09;单引号&a…

建歌网站多少钱温州网站建设价格

💡💡💡本文独家改进:卷积和注意力融合模块(CAFMAttention),增强对全局和局部特征的提取能力,2024年最新的改进思路 💡💡💡创新点:卷积和注意力巧妙设计 💡💡💡如何跟YOLOv8结合:1)放在backbone后增强对全局和局部特征的提取能力;2)放在detect前面,增…

罗技G102螺丝型号

共有三种,都是PH00螺丝(反正就就是很小的十字螺丝) 外壳固定用  M1.4*4mm  直径3mm厚0.5的平头螺丝  共三个 侧键固定用  M2*4mm   直径5mm厚0.5的平头螺丝  共俩个 主板固定用  M1.4*3mm  直径4…

详细介绍:深入剖析C#构造函数执行:基类调用、初始化顺序与访问控制

详细介绍:深入剖析C#构造函数执行:基类调用、初始化顺序与访问控制pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: &qu…

公司网站建设进度vitality wordpress

TCP与UDP TCP 是面向连接的、可靠的流协议&#xff0c;通过三次握手建立连接&#xff0c;通讯完成时要拆除连接。 UDP是面向无连接的通讯协议&#xff0c;UDP通讯时不需要接收方确认&#xff0c;属于不可靠的传输&#xff0c;可能会出现丢包现象 端口号&#xff1a; 端口号用…

上海网站建设 销售营销型网站开发推荐

一、产品介绍 此次发布一款7寸高清全新外壳产品&#xff0c;让HMI人机界面家族再添一新成员。该产品相比其他外壳有以下5个大改动&#xff1a; 1 表面玻璃盖板使用2.5D立体结构&#xff1b; 2 液晶盖板采用一体黑设计&#xff0c;且液晶屏与触摸板是全贴合结构&#xff1b; …

广州网站建设培训学校seo优化外包

了解动态内存函数 前言&#xff1a;一、malloc函数二、calloc函数三、realloc函数四、free函数 前言&#xff1a; 在C语言中&#xff0c;动态内存函数是块重要的知识点。以往&#xff0c;我们开辟空间都是固定得&#xff0c;数组编译结束后就不能继续给它开辟空间了&#xff0…

[LUCKY」在Windows下使用STUN穿透实现Minecraft联机并设置SRV记录

[LUCKY」在Windows下使用STUN穿透实现Minecraft联机并设置SRV记录2024.02.18 本教程目标 在无公网环境低成本的实现较为稳定的 Minecraft Java版联机(服务器) 目前常见的联机(服务器)方法及其问题(针对好友联机和小…

系统管理员的日常困境与幽默自嘲

这篇文章通过Reddit论坛上系统管理员们的真实讨论,展现了IT专业人员面对"请联系系统管理员"这类提示时的无奈与幽默,揭示了技术支持工作中的常见困境和职业共鸣。这篇文章通过Reddit论坛上系统管理员们的真…

46设计网站官网网站开发项目团队人员

前言 如果你被hutool坑过、被fastjson坑过&#xff0c;nicetool帮你解脱&#xff01; 如果你想用稳定、Spring原生的工具类&#xff0c;nicetool已帮你封装&#xff01; nicetool不生产工具&#xff0c;只是JDK和Spring的封装侠&#xff01; 介绍 nicetool&#xff1a;超好…

实用指南:Docker 在 AI 开发中的实践:GPU 支持与深度学习环境的容器化

实用指南:Docker 在 AI 开发中的实践:GPU 支持与深度学习环境的容器化pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: …

电子商务网站类型培训学校 网站费用

处理域名连接nacos读取配置异常 1 项目启动报错2 问题处理3 刷新依赖重启问题解决 1 项目启动报错 使用ip可以正在启动&#xff0c;但是使用域名报下面的错误 2024-06-15 17:37:22.981 ERROR 29268 --- [ main] c.a.c.n.c.NacosPropertySourceBuilder : parse …

视频素材网站建设有没有专门做印刷图的网站

前言 对于使用jmeter工具完成接口测试的测试工程师而言。在工作中&#xff0c;或者在面试中&#xff0c;都会遇到一个问题。 CSV文档做了一大笔测试数据后&#xff0c;怎么去校验这个结果呢&#xff1f; 现在大部分测试工程师可能都是通过人工的方法去查看结果&#xff0c;十几…

AI数据标注平台获融资挑战行业巨头

一家AI数据平台公司获得1300万美元融资,专注于为开发者提供数据标注、管理和模型评估工具,帮助构建高质量AI训练数据集,客户包括多家知名科技企业。AI数据平台获得1300万美元融资挑战行业竞争对手 人工智能数据平台…

详细介绍:如何用 pnpm patch 给 element-plus 打补丁修复线上 bug(以 2.4.4 修复 PR#15197 为例)

详细介绍:如何用 pnpm patch 给 element-plus 打补丁修复线上 bug(以 2.4.4 修复 PR#15197 为例)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block…

广州网站ui设计贵港免费的网站建设

我是个前端菜鸡&#xff0c;最近准备学习一点前端知识&#xff0c;先从Bootstrap5开始&#xff0c;毕竟早期Bootstrap还是比较火的。推出的Bootstrap5不再和jQery强制绑定&#xff0c;这里直接按照官方文档上来操作&#xff0c;打包工具我们选择Webpack。 一 前期准备 前期准备…