ARTICLE DETAIL

资讯详情

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

UI-TARS 桌面端开源 mcp-http-server:构建 HTTP+SSE 双协议 MCP 服务端的完整指南

UI-TARS 桌面端开源 mcp-http-server:构建 HTTP+SSE 双协议 MCP 服务端的完整指南 UI-TARS 桌面端开源 mcp-http-server构建 HTTPSSE 双协议 MCP 服务端的完整指南【免费下载链接】UI-TARS-desktopThe Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-desktopmcp-http-server是 UI-TARS-desktop 仓库中packages/agent-infra/mcp-http-server目录下的一个可独立发布的高性能 MCPModel Context ProtocolHTTP 服务库它同时支持 Streamable HTTP 与 Server-Sent EventsSSE两种传输协议并把「HTTP 请求头透传到 MCP Server 上下文」作为一等特性。本文以该库的官方 README 为骨架结合其 核心实现源码 与 端到端测试完整讲解如何从零启动一个带鉴权中间件、可自定义路由前缀、支持无状态高并发与有状态会话两种模式的服务端读者读完可直接在自己的 MCP Server 项目中落地部署并理解其底层调用链与边界行为。功能总览按 README 的 Features 清单该库提供五类核心能力能力说明HTTP/SSE 双传输同时支持 HTTP POST 与 Server-Sent Events 两种 MCP 传输灵活路由端点路径与前缀全部可配置支持默认/mcp、/message、/sse布局请求头透传在createMcpServer工厂函数中直接拿到本次 HTTP 请求的 Headers双运行模式Streamable HTTP 支持有状态stateful与无状态stateless两种模式中间件支持可注入自定义 Express 中间件用于鉴权、日志、限流等横切逻辑从源码看该库本质上是基于expressv5与官方modelcontextprotocol/sdk~1.15.1见 package.json之上的「轻封装调度层」它替你管理StreamableHTTPServerTransport与SSEServerTransport的生命周期、路由注册与错误兜底让开发者只需关注「如何根据请求上下文创建一个业务 MCP Server」。安装与最小启动安装依赖npm i mcp-http-server -S官方 README 给出的 Quick Start 非常精简import { startSseAndStreamableHttpMcpServer } from mcp-http-server; import { createServer } from ./server.js; await startSseAndStreamableHttpMcpServer({ port: 3000, createMcpServer: async (params) { console.log(Request headers:, params.headers); return createServer(); }, });这里有两个关键点值得展开createMcpServer是工厂而非实例。源码中每次新连接到来时都会调用该工厂并传入{ headers: req.headers }见 startServer.ts因此每个客户端会话可以拿到彼此隔离、且携带本次 HTTP 请求头信息的 Server 实例也自然可以做到「每个请求头对应一个独立 MCP 会话 / 鉴权上下文」。这也是 CHANGELOG 中feat: support request headers params、feat: support getRequestContext等迭代引入的能力。HTTP 传输与 SSE 传输共用同一个工厂。无论是客户端 POST 到/mcp还是通过GET /sse建立事件流底层都会走createMcpServer({ headers: req.headers })。包入口文件 src/index.ts 只做了一件事export * from ./startServer即对外暴露的核心 API 就是startSseAndStreamableHttpMcpServer。一个完整的可运行示例基于 README 的 Basic HTTP Server 示例用官方 SDK 直接构造一个带 tools 能力的 Serverimport { startSseAndStreamableHttpMcpServer } from mcp-http-server; import { Server } from modelcontextprotocol/sdk/server/index.js; const server await startSseAndStreamableHttpMcpServer({ port: 3000, createMcpServer: async () { return new Server( { name: my-server, version: 1.0.0 }, { capabilities: { tools: {} } } ); }, }); console.log(Server running at ${server.url});启动成功后即可在 MCP 客户端中把服务地址指向返回的url默认是http://127.0.0.1:3000/mcp客户端既可以是支持 Streamable HTTP 的新版客户端也可以是支持 SSE 的客户端——服务端对两者同时开放。API 参考startSseAndStreamableHttpMcpServer(params)该函数是库的唯一入口返回一个PromiseMcpServerEndpoint。其全部参数见 README 的表格并结合 源码接口定义 可整理出精确的类型语义参数类型默认值说明portnumber8080或环境变量PORT监听端口。源码中取值为Number(port \|\| process.env.PORT \|\| 8080)即三个来源的优先级依次是显式port→process.env.PORT→8080hoststring127.0.0.1绑定主机。源码const HOST host \|\| 127.0.0.1statelessbooleantrue是否启用 Streamable HTTP 的无状态模式middlewaresMiddlewareFunction[]无自定义 Express 中间件数组按数组顺序挂载routesRoutesConfig{ prefix: /, mcp: /mcp, message: /message, sse: /sse }路由配置loggerLoggerConsoleLogger实例自定义日志器类型来自agent-infra/loggercreateMcpServer(req: RequestContext) PromiseMcpServer \| Server必填MCP Server 工厂函数其中RequestContext在源码中被定义为PickRequest, headers即当前 HTTP 请求的完整请求头对象IncomingHttpHeaders这是实现「按请求头鉴权 / 多租户隔离」的关键数据来源。路由配置服务端默认注册了四条路由见 README 的 Default Routes 及测试对默认路径的断言方法与路径用途POST /mcpMCP HTTP transport 主入口接收 JSON-RPC 请求GET /mcp返回405Method Not AllowedGET /sseSSE 连接端点客户端在此建立事件流POST /messageSSE 消息端点客户端把请求 POST 到这里SSE 的通信模型是「GET 建流、POST 发消息」客户端先GET /sse拿到服务端在SSEServerTransport中生成的sessionId之后所有请求 POST 到/message?sessionIdxxx服务端通过 sessionId 找到对应 transport 再转发给 MCP Server。源码中对缺失或未知 sessionId 的/message请求会返回400见 startServer.ts。自定义路由与路径规范化当你的服务需要挂到网关上时可以用routes整体重命名路径比如 README 的例子await startSseAndStreamableHttpMcpServer({ port: 3000, routes: { prefix: /api/v1, mcp: /custom-mcp, message: /custom-message, sse: /custom-sse }, createMcpServer: async () createServer(), });最终端点会变成POST /api/v1/custom-mcp—— MCP HTTP 传输入口GET /api/v1/custom-sse—— SSE 连接端点POST /api/v1/custom-message—— SSE 消息端点路径拼接有明确的规范化规则见源码中的buildPath辅助函数startServer.tsprefix末尾的/会被裁掉根前缀/会被特殊处理为「不拼前缀」各子路径缺省开头的/会被自动补上因此下面两种写法完全等价都会得到/api/v2/mcp-endpoint与/api/v2/sse-endpoint/// 写法一 routes: { prefix: /api/v2/, mcp: mcp-endpoint, sse: /sse-endpoint/ } // 写法二 routes: { prefix: /api/v2, mcp: /mcp-endpoint, sse: sse-endpoint }路由配置同样支持部分覆盖未配置的子项回落默认值。例如只配{ prefix: /api, mcp: /custom-mcp }则实际生效为POST /api/custom-mcpGET /api/sse这一点在 routes 测试 中被明确验证。中间件支持中间件在框架内部挂载点早于任何 MCP 路由源码中middlewares.forEach((middleware) app.use(middleware))位于 SSE / HTTP 路由注册之前因此可以拦截、改写或终结请求。README 给出了一个典型的 Bearer Token 鉴权示例import { startSseAndStreamableHttpMcpServer } from mcp-http-server; const authMiddleware (req, res, next) { const token req.headers.authorization; if (!token) { return res.status(401).json({ error: Unauthorized }); } next(); }; await startSseAndStreamableHttpMcpServer({ port: 3000, host: localhost, routes: { prefix: /api/v1, mcp: /mcp, sse: /events }, middlewares: [authMiddleware], createMcpServer: async (context) { console.log(User agent:, context.headers[user-agent]); return createServer(); }, });鉴权最佳实践由于鉴权中间件在 MCP 路由之前执行未携带Authorization的请求会被直接以401拦截根本不会走到createMcpServer而通过鉴权的请求其 headers 又会经RequestContext传给工厂函数。两者组合即可实现「中间件负责拦、工厂函数负责读」的分层鉴权。仓库内的浏览器 MCP 服务 mcp-servers/browser/src/index.ts 就实际引用了startSseAndStreamableHttpMcpServer并配合commander解析--host、--port等参数是生产级调用范本。传输模式无状态与有状态无状态模式推荐默认开启await startSseAndStreamableHttpMcpServer({ stateless: true, // 默认值 createMcpServer: async () createServer(), });无状态模式下服务端不维护任何客户端会话每次请求互相独立天然适合横向扩容与负载均衡每个请求都会创建全新的 transport 与 MCP Server 实例适合无状态工具类服务。对应到源码无状态分支创建 transport 时显式传入sessionIdGenerator: undefined与enableJsonResponse: true即 SDK 文档规定的无状态 Streamable HTTP Server 配置见 startServer.ts。有状态模式await startSseAndStreamableHttpMcpServer({ stateless: false, // 关闭无状态启用有状态 createMcpServer: async () createServer(), });有状态模式下每个客户端会获得唯一的 session ID同一会话的后续请求会复用已建立的 transport状态如长连接、订阅、上下文跨请求保持会话需要被正确清理。其底层逻辑较为精巧见 startServer.ts请求头带mcp-session-id且命中内存表transports.streamable→复用已有 transport无 sessionId 且请求体是initialize用 SDK 的isInitializeRequest判断→ 新建 transportsessionIdGenerator: () randomUUID()生成 UUID并在onsessioninitialized回调中把 transport 登记到 Map其余情况带未知 sessionId、或初始化前直接发非 initialize 请求→ 返回400错误码为ErrorCode.ConnectionClosed。同时transport 在onclose时会把自身从 Map 中移除避免内存泄漏。生产环境建议若使用有状态模式且需要多实例水平扩展应把会话存储外置如 Redis因为当前实现把会话保存在进程内存中无状态模式则没有此顾虑。返回值McpServerEndpoint与优雅关闭函数返回一个McpServerEndpoint对象方便外部获取实际监听地址与关闭句柄interface McpServerEndpoint { url: string; // 完整的 MCP HTTP 端点 URL sseUrl: string; // 完整的 SSE 端点 URL port: number; // 实际监听端口 close: () void; // 关闭服务器 }示例摘自 README并补充了优雅关闭的惯用法const endpoint await startSseAndStreamableHttpMcpServer({ port: 3000, routes: { prefix: /api }, createMcpServer: async () createServer(), }); console.log(MCP endpoint:, endpoint.url); // http://localhost:3000/api/mcp console.log(SSE endpoint:, endpoint.sseUrl); // http://localhost:3000/api/sse // 优雅关闭响应 SIGTERM / SIGINT 等信号 process.on(SIGTERM, () { endpoint.close(); });注意url/sseUrl中的 host 取自实际绑定结果当监听地址为 IPv6 时会以[addr]形式包裹见 startServer.ts返回的 URL 直接可交给 MCP 客户端使用。命令行集成同时支持 HTTP 与 Stdio很多 MCP Server 需要同时支持「远程 HTTP 接入」与「本地 stdio 接入」后者被桌面客户端 / Agent 作为子进程拉起。README 给出的commander模式即为此设计——根据是否传入--port/--host自动选择传输import { program } from commander; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; program .name(mcp-server) .description(MCP server with HTTP and stdio support) .version(1.0.0) .option(--host host, host to bind server to) .option(--port port, port to listen on for HTTP transport) .option(--prefix prefix, route prefix for HTTP endpoints, /api) .action(async (options) { try { if (options.port || options.host) { // HTTP/SSE transport await startSseAndStreamableHttpMcpServer({ host: options.host, port: options.port ? parseInt(options.port) : undefined, routes: { prefix: options.prefix, }, createMcpServer: async (params) { console.log(HTTP request from:, params.headers[user-agent]); return createServer(); }, }); } else { // Stdio transport const server createServer(); const transport new StdioServerTransport(); await server.connect(transport); console.debug(MCP Server running on stdio); } } catch (error) { console.error(Error:, error); process.exit(1); } }); program.parse();值得注意的一个工程细节HTTP 分支中日志要使用console.log/ 自定义 logger 而避免在 stdio 分支用console.log打印非调试内容——stdio 通道本身就是 MCP 消息管道任何多余输出都会污染协议数据stdio 分支使用console.debug可被--debug之类开关关闭正是为了规避这一点。仓库中 mcp-servers/browser/src/index.ts 等已落地的 MCP Server 均采用了类似的 HTTP/stdio 双通道 CLI 结构。源码视角错误处理与协议合规从 核心实现 可以看出除了路由与传输管理框架还内置了三层协议合规处理这部分是 README 未展开但值得写进生产认知的JSON 解析错误兜底express.json()解析失败的请求非法 JSON body会被错误中间件捕获统一返回400 JSON-RPC 格式的ParseError响应而不是 Node 默认的 HTML 错误页。测试中向/mcpPOST 一段invalid json{会得到400且 body 含jsonrpc: 2.0与error.code、error.message字段见 mcpServer 测试。GET/DELETE请求/mcp统一返回405错误码为ErrorCode.ConnectionClosed消息Method not allowed.这是 Streamable HTTP 规范要求的「只有 POST 才被允许」的明确拒绝信号。内部异常兜底transport.handleRequest抛错时若响应头尚未发送则回写500InternalError的 JSON-RPC 响应否则只能记录日志避免出现半截响应悬挂。这套行为在 startServer-server.test.ts 与上述两个测试文件中都有对应的端到端覆盖可视为框架对外承诺的稳定契约。请求头透传的典型落地请求头透传配合createMcpServer工厂能解锁三类典型场景多租户 / 多配置隔离依据x-tenant-id之类的请求头为不同租户创建不同配置模型、权限、插件集合的 MCP Server 实例实现单端口多租户用户态识别读取authorization或自定义x-user-id把当前用户信息注入 Server 的 tool 执行上下文设备 / 渠道审计像 README CLI 示例那样记录user-agent或在日志中带上req.ip源码对 SSE 与 HTTP 请求均有logger.info记录来源 IP。测试中对这一特性有直接验证通过StreamableHTTPClientTransport发送自定义头x-custom-header、x-client-id、authorization服务端在createMcpServer收到的req.headers能逐一还原见 mcpServer 测试证明请求头在任何 MCP 调用包括工具执行、资源读取时都会在工厂处可读。小结mcp-http-server的核心价值在于把官方 MCP SDK 中繁琐的 HTTP/SSE transport 接线、session 管理、JSON-RPC 错误规约统一收敛为一个函数startSseAndStreamableHttpMcpServer同时保留了 Express 中间件生态与按请求头创建 Server 的扩展空间。你可以把它当作独立的 npm 依赖使用也可以直接参考仓库内 mcp-servers/browser、mcp-servers/filesystem、mcp-servers/commands 等生产级调用方的写法以及 create-new-mcp 脚手架模板快速搭建你自己的多协议 MCP Server。需要进一步深挖时可直接阅读以下文件入口类型与默认路由定义 startServer.ts、路由与路径规范化测试 startServer-routes.test.ts、请求头与错误处理测试 startServer-mcpServer.test.ts以及依赖与版本声明 package.json。本文档对应源码遵循 MIT License详见包内 package.json 与仓库根目录 LICENSE。【免费下载链接】UI-TARS-desktopThe Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-desktop创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表