news 2026/9/10 12:07:45

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

作者头像

张小明

前端开发工程师

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

UI-TARS 桌面端开源 mcp-http-server:构建 HTTP+SSE 双协议 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-desktop

mcp-http-server是 UI-TARS-desktop 仓库中packages/agent-infra/mcp-http-server目录下的一个可独立发布的高性能 MCP(Model Context Protocol)HTTP 服务库,它同时支持 Streamable HTTP 与 Server-Sent Events(SSE)两种传输协议,并把「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 中间件,用于鉴权、日志、限流等横切逻辑

从源码看,该库本质上是基于express(v5)与官方@modelcontextprotocol/sdk~1.15.1,见 package.json)之上的「轻封装调度层」:它替你管理StreamableHTTPServerTransportSSEServerTransport的生命周期、路由注册与错误兜底,让开发者只需关注「如何根据请求上下文创建一个业务 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(); }, });

这里有两个关键点值得展开:

  1. createMcpServer是工厂而非实例。源码中每次新连接到来时都会调用该工厂并传入{ headers: req.headers }(见 startServer.ts),因此每个客户端会话可以拿到彼此隔离、且携带本次 HTTP 请求头信息的 Server 实例,也自然可以做到「每个请求头对应一个独立 MCP 会话 / 鉴权上下文」。这也是 CHANGELOG 中feat: support request headers paramsfeat: support getRequestContext等迭代引入的能力。
  2. HTTP 传输与 SSE 传输共用同一个工厂。无论是客户端 POST 到/mcp,还是通过GET /sse建立事件流,底层都会走createMcpServer({ headers: req.headers })

包入口文件 src/index.ts 只做了一件事:export * from './startServer',即对外暴露的核心 API 就是startSseAndStreamableHttpMcpServer

一个完整的可运行示例

基于 README 的 Basic HTTP Server 示例,用官方 SDK 直接构造一个带 tools 能力的 Server:

import { 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)

该函数是库的唯一入口,返回一个Promise<McpServerEndpoint>。其全部参数见 README 的表格,并结合 源码接口定义 可整理出精确的类型语义:

参数类型默认值说明
portnumber8080或环境变量PORT监听端口。源码中取值为Number(port \|\| process.env.PORT \|\| 8080),即三个来源的优先级依次是显式portprocess.env.PORT8080
hoststring'127.0.0.1'绑定主机。源码const HOST = host \|\| '127.0.0.1'
statelessbooleantrue是否启用 Streamable HTTP 的无状态模式
middlewaresMiddlewareFunction[]自定义 Express 中间件数组,按数组顺序挂载
routesRoutesConfig{ prefix: '/', mcp: '/mcp', message: '/message', sse: '/sse' }路由配置
loggerLoggerConsoleLogger实例自定义日志器,类型来自@agent-infra/logger
createMcpServer(req: RequestContext) => Promise<McpServer \| Server>必填MCP Server 工厂函数

其中RequestContext在源码中被定义为Pick<Request, 'headers'>,即当前 HTTP 请求的完整请求头对象(IncomingHttpHeaders),这是实现「按请求头鉴权 / 多租户隔离」的关键数据来源。

路由配置

服务端默认注册了四条路由(见 README 的 Default Routes 及测试对默认路径的断言):

方法与路径用途
POST /mcpMCP HTTP transport 主入口,接收 JSON-RPC 请求
GET /mcp返回405(Method Not Allowed)
GET /sseSSE 连接端点,客户端在此建立事件流
POST /messageSSE 消息端点,客户端把请求 POST 到这里

SSE 的通信模型是「GET 建流、POST 发消息」:客户端先GET /sse拿到服务端在SSEServerTransport中生成的sessionId,之后所有请求 POST 到/message?sessionId=xxx,服务端通过 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.ts):

  • prefix末尾的/会被裁掉,根前缀/会被特殊处理为「不拼前缀」;
  • 各子路径缺省开头的/会被自动补上;
  • 因此下面两种写法完全等价,都会得到/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-mcp+GET /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: undefinedenableJsonResponse: true,即 SDK 文档规定的无状态 Streamable HTTP Server 配置(见 startServer.ts)。

有状态模式

await startSseAndStreamableHttpMcpServer({ stateless: false, // 关闭无状态,启用有状态 createMcpServer: async () => createServer(), });

有状态模式下:

  • 每个客户端会获得唯一的 session ID;
  • 同一会话的后续请求会复用已建立的 transport,状态(如长连接、订阅、上下文)跨请求保持;
  • 会话需要被正确清理。

其底层逻辑较为精巧,见 startServer.ts:

  1. 请求头带mcp-session-id且命中内存表transports.streamable复用已有 transport
  2. 无 sessionId 且请求体是initialize(用 SDK 的isInitializeRequest判断)→ 新建 transport,sessionIdGenerator: () => randomUUID()生成 UUID,并在onsessioninitialized回调中把 transport 登记到 Map;
  3. 其余情况(带未知 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 未展开但值得写进生产认知的:

  1. JSON 解析错误兜底express.json()解析失败的请求(非法 JSON body)会被错误中间件捕获,统一返回400+ JSON-RPC 格式的ParseError响应,而不是 Node 默认的 HTML 错误页。测试中向/mcpPOST 一段invalid json{会得到400且 body 含jsonrpc: '2.0'error.codeerror.message字段(见 mcpServer 测试)。
  2. GET/DELETE请求/mcp统一返回405:错误码为ErrorCode.ConnectionClosed,消息Method not allowed.,这是 Streamable HTTP 规范要求的「只有 POST 才被允许」的明确拒绝信号。
  3. 内部异常兜底transport.handleRequest抛错时,若响应头尚未发送,则回写500+InternalError的 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-headerx-client-idauthorization,服务端在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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 12:02:10

LSTM-SVR组合模型权重优化:多输入单输出回归预测实战

简介&#xff1a;面向多输入单输出回归预测任务&#xff0c;这份资源提供基于LSTM与SVR的MATLAB组合模型实现&#xff0c;重点解决两种模型融合时的权重优化问题&#xff0c;适合有一定编程基础的研究生、工程师用于预测建模实验、算法对比或课程设计参考。压缩包共14个文件&am…

作者头像 李华