【免费下载链接】mcp-use
The fullstack MCP framework to develop MCP Apps for ChatGPT / Claude & MCP Servers for AI Agents.
项目地址:
https://gitcode.com/gh_mirrors/mc/mcp-use
点击查看 免费下载
导读
libraries/typescript/packages/server/examples/proxy 是 mcp-use TypeScript Server 包内置的一个多服务器代理示例:它同时启动两个上游 MCP 服务器(天气服务与库存服务),再通过一个网关将两者的工具(Tool)、提示词(Prompt)与资源(Resource)统一暴露到单一 HTTP 端点,同时保留网关自身的本地工具。读完本文,你将掌握 MCPServer.proxy() 的两种调用形态、命名空间映射规则、上游能力自动发现(introspection)机制、可选的连接配置项,以及如何把这一模式复用到自己的多服务聚合场景中。
示例要解决的问题
真实业务中,AI Agent 往往需要访问多个相互独立的 MCP 服务器:一个负责天气查询、一个负责库存检索、一个负责文档检索……如果让每个服务器都暴露独立端点,客户端就要维护多份连接配置。mcp-use 的 proxy() 提供了一种"网关聚合"方案:一个网关端点 = 多个上游服务器能力的并集。
该示例的核心目标非常明确:
- 启动两个"上游"MCP 服务器(weather、inventory),它们使用临时本地端口,无需手工配置端口;
- 通过 MCPServer.proxy() 把两个上游挂载到同一个网关服务器上;
- 网关为每个上游自动生成 weather_ 与 inventory_ 前缀命名空间;
- 网关自身保留一个本地工具 gateway_status,与代理能力共存;
- 整个进程负责上游与网关的启动和关闭,无需多个终端分别管理。
运行网关
示例的 README(libraries/typescript/packages/server/examples/proxy/README.md)给出了最简启动方式。在仓库的 libraries/typescript 目录下执行:
pnpm install
pnpm –filter mcp-use-example-proxy start
启动后网关监听在 http://localhost:3000/mcp。如需更换端口,通过环境变量 PORT 指定:
PORT=8080 pnpm –filter mcp-use-example-proxy start
端口校验逻辑在 src/index.ts 中实现:PORT 必须是一个 0~65535 的整数,否则进程会抛出 PORT must be an integer from 0 to 65535 的错误并退出。port 传 0 表示由操作系统随机分配空闲端口。
启动脚本定义在 package.json:
| dev | tsx watch src/index.ts | 开发模式,文件变更自动重启 |
| start | tsx src/index.ts | 正常运行 |
| typecheck | tsc –noEmit | 仅做类型检查(noEmit 开启) |
示例要求 Node.js >= 22.22.2(见 package.json 的 engines 字段),并使用 tsx 直接运行 TypeScript。
值得注意的一点是:示例同时在依赖中声明了 mcp-use(服务端框架)与 @mcp-use/client(客户端包),原因见下文"可选客户端依赖"一节。
上游服务器:weather 与 inventory
两个上游服务器的定义都在 src/upstreams.ts 中,它们本身是普通的 MCPServer 实例,演示了 mcp-use 的四种核心能力注册 API。
weather 上游:工具 + 提示词
const server = new MCPServer({ name: "weather-upstream", version: "1.0.0" });
server.tool(
{
name: "forecast",
description: "Return a sample forecast for a city.",
inputSchema: z.object({ city: z.string() }),
},
async ({ city }) => ({
content: [{ type: "text", text: `${city}: 21°C, clear skies` }],
})
);
server.prompt(
{
name: "plan_trip",
description: "Create a prompt for planning around the weather.",
schema: z.object({ city: z.string() }),
},
async ({ city }) => ({
messages: [
{
role: "user",
content: { type: "text", text: `Plan a one-day trip to ${city} for clear, 21°C weather.` },
},
],
})
);
server.tool() 的第二个参数是回调函数,返回 MCP 标准 content 结构;server.prompt() 则返回 messages 数组,供客户端(如 Claude/ChatGPT 类应用)在对话中直接渲染。这里使用了 zod 定义输入模式(zod 是示例的显式依赖)。
inventory 上游:工具 + 资源
const server = new MCPServer({ name: "inventory-upstream", version: "1.0.0" });
server.tool(
{
name: "find_product",
description: "Look up a sample product by SKU.",
inputSchema: z.object({ sku: z.string() }),
},
async ({ sku }) => ({
content: [{ type: "text", text: JSON.stringify({ sku, name: "Travel umbrella", inStock: true }) }],
})
);
server.resource(
{
name: "featured_product",
uri: "inventory://featured",
description: "The featured product in the sample inventory.",
mimeType: "application/json",
},
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({ sku: "UMBRELLA-01", name: "Travel umbrella", inStock: true }),
},
],
})
);
server.resource() 的注册信息包含自定义 uri(这里是 inventory://featured)与 mimeType。注意:这个自定义 uri 在被网关代理后会被重写,细节见下文"资源 URI 的重写规则"。
网关组装:createProxyExample()
网关的完整逻辑集中在 src/server.ts 的 createProxyExample() 函数中,它返回一个 ProxyExample 接口(包含 server 与 close() 两个成员)。
const weather = createWeatherServer();
const inventory = createInventoryServer();
// 1. 两个上游先各自监听随机端口(listen(0) 表示随机端口)
const [weatherAddress, inventoryAddress] = await Promise.all([
weather.listen(0),
inventory.listen(0),
]);
// 2. 创建网关服务器
gateway = new MCPServer({ name: "multi-server-proxy", version: "1.0.0" });
// 3. 注册网关本地工具
gateway.tool(
{
name: "gateway_status",
description: "Report whether the proxy gateway is running.",
},
async () => ({ content: [{ type: "text", text: "Proxy gateway is running" }] })
);
// 4. 挂载两个上游
await gateway.proxy({
weather: { url: weatherAddress.url },
inventory: { url: inventoryAddress.url },
});
关键点:
- listen(0) 让操作系统分配随机端口,因此两个上游无需预先占用固定端口,也无需用户手动配置;
- proxy() 必须在 listen() 之前调用。源码 src/mcp-proxy.ts 中对此有硬性校验:如果服务器已启动仍调用 proxy(),会抛出 Cannot call proxy() after the server has started: register upstream servers before listen()/server.fetch;
- 传给 proxy() 的对象键 weather / inventory 即命名空间,上游暴露的每个能力都会被加上对应前缀。
客户端如何看到聚合后的能力
README 中给出了挂载完成后,MCP 客户端连接 http://localhost:3000/mcp 能看到的完整能力清单:
| Tool | gateway_status | 网关自身 |
| Tool | weather_forecast | weather 上游 |
| Prompt | weather_plan_trip | weather 上游 |
| Tool | inventory_find_product | inventory 上游 |
| Resource | inventory_featured_product | inventory 上游 |
这张表清晰地展示了命名空间映射规则:上游的 forecast 变成 weather_forecast,plan_trip 变成 weather_plan_trip,find_product 变成 inventory_find_product,featured_product 变成 inventory_featured_product;而网关本地注册的 gateway_status 保持原名。启动日志中也会打印前三个工具名,方便快速确认。
proxy() 的底层原理
proxy() 的实现位于 libraries/typescript/packages/server/src/mcp-proxy.ts,整体流程可以概括为三步:
具体到能力类型,转发时各有讲究:
- 工具(Tool):工具回调把参数原样透传给上游 callTool(),同时把下游请求的取消信号(ctx.signal)与进度(onprogress)转发给上游——上游的进度通知会通过 ctx.reportProgress() 回传给下游客户端;
- 资源(Resource):readResource 回调接收上游的原始 uri 并透传读取;
- 提示词(Prompt):getPrompt 回调把参数透传给上游渲染。上游 prompt 的 arguments 会被转换成 JSON Schema(见 promptArgsToJsonSchema(),第 211-228 行),从而兼容网关的能力描述。
proxy() 的两种调用形态
从 src/server.ts 的方法签名可以看出,proxy() 有两个重载:
// 形态一:命名空间键控的上游配置(示例采用)
await server.proxy({
weather: { url: weatherAddress.url },
inventory: { url: inventoryAddress.url },
});
// 形态二:直接挂载一个已存在的连接对象
await server.proxy(connection);
第二种形态要求连接对象是"已就绪的 @mcp-use/client v2 连接"(结构上需具备 listTools、callTool、readResource、listPrompts、getPrompt 等方法,源码通过 isConnection() 做鸭子类型判断,见第 238-248 行)。此时命名空间取自连接上报的 info.server.name;如果上游服务器没有上报名称,会抛出 Cannot proxy an anonymous MCP connection directly 的错误。第一种形态(配置对象)则直接以配置键作为命名空间,这也是示例选择它的原因——命名空间完全由网关掌控,与上游内部名称解耦。
失败隔离与命名冲突
proxy() 在设计上做了两处容错:
- 发现失败不阻断整体:introspect() 对每种能力的 list* 调用都用 try/catch 包裹,单个上游的某个能力列表失败只会打印 [mcp-use] Failed to introspect … 诊断日志,其余能力照常挂载;
- 命名冲突跳过而非覆盖:mountPlan() 挂载每个能力前会用 hasTool/hasResource/hasPrompt 检查名称是否已被占用,若冲突则跳过并输出诊断日志(例如 Skipping proxied tool "weather_forecast" from upstream "weather" because that name is already registered)。这意味着多个上游之间、或上游与本地能力之间出现同名时,先注册者优先,网关不会静默覆盖。
资源 URI 的重写规则
代理资源时,上游的原始 URI 会被重写为 mcp-use-proxy:///<namespace>/<encoded-uri> 形式(见 proxiedResourceUri(),第 234-236 行),例如示例中的 inventory://featured 在网关侧会表现为 mcp-use-proxy:///inventory/inventory%3A%2F%2Ffeatured。这样设计既避免了多上游 URI 冲突,又保留了上游原始 URI 的完整信息,读取时网关再把它还原成上游地址透传。示例表格中的 inventory_featured_product 即这个重写后的资源名。
可选配置项详解:ProxyHttpConfig
示例只用了最简的 { url },但 proxy() 的连接配置远不止于此。源码 src/mcp-proxy.ts 定义了 ProxyHttpConfig:
| url | string | 上游 MCP 端点 URL(必填) |
| headers | Record<string, string> | 每次上游请求附加的 HTTP 头,可用于自定义鉴权头或跟踪头 |
| authToken | string | 发送给上游服务器的 Bearer Token |
| timeout | number | 连接超时(毫秒) |
| fetch | typeof fetch | 自定义 fetch 实现(如代理、注入测试桩) |
| protocolNegotiation | "auto" \\| "legacy" \\| { pin: string } | 协议协商模式,转发给 @mcp-use/client;pin 用于锁定特定协议版本 |
这些配置在 toClientConfig()(第 479-491 行)中被映射为 @mcp-use/client 的 HTTP 服务器配置,同时强制 oauth: false(代理场景默认关闭 OAuth 流程)。也就是说,认证、超时、协议协商都可以在网关侧统一配置,对下游客户端透明。仓库中还有 tests/proxy-auth.test.ts 专门覆盖带认证的上游代理场景,可作为扩展参考。
可选客户端依赖:为什么需要 @mcp-use/client
README 特别说明了一个设计细节:server.proxy() 依赖可选的 @mcp-use/client 包,因此示例的 package.json 把它列为显式依赖;如果项目不使用 proxy(),则无需安装这个可选 peer。
从实现看,loadProxyClient()(第 275-285 行)通过动态 import("@mcp-use/client") 按需加载客户端;如果加载失败且错误码为 ERR_MODULE_NOT_FOUND / MODULE_NOT_FOUND 且信息包含 @mcp-use/client,会抛出带安装指引的错误:
[mcp-use] server.proxy() requires the optional @mcp-use/client package.
Install it in your project:
npm install @mcp-use/client
相关的可选 peer 安装行为有专门测试覆盖(tests/proxy-optional-peer.test.ts),确保缺包时给出的是可操作的错误提示而非晦涩的模块加载异常。
生命周期:启动、优雅退出与清理
示例把"进程级生命周期"封装得很完整,这也是它不需要多终端的原因。
启动顺序
入口 src/index.ts 在 createProxyExample() 内部完成"上游监听 → 网关创建 → proxy 挂载",之后才调用 example.server.listen(getPort()) 让网关对外提供服务。整个过程没有阻塞等待,createProxyExample() 返回的 ProxyExample 对象让调用方完全掌控网关何时开始监听。
优雅关闭
入口注册了 SIGINT / SIGTERM 信号处理器(第 23-31 行),收到信号后调用 example.close()。close() 的实现(src/server.ts)使用 Promise.allSettled 并发关闭网关和两个上游,并用 closed 标志保证可重复调用(幂等);任何服务器关闭失败都会汇总成 AggregateError 抛出,避免静默吞掉错误。
在网关层面,MCPServer 的关闭逻辑(src/server.ts)会等待所有进行中的代理操作(#proxyOperations)结束,再关闭它创建的所有 @mcp-use/client 实例(#proxyOwners),确保上游连接资源被完整释放。
失败清理
createProxyExample() 内部还有一层 try/catch:如果网关创建或 proxy() 挂载中途失败,会立刻用 Promise.allSettled 关闭已启动的上游(第 51-58 行),然后重新抛出错误,防止进程残留孤儿服务器。
运行指标与可观测性
从 src/server.ts 可以看到,MCPServer 会为代理链路维护一组运行指标,供监控与调试使用:
- proxy_servers_configured / proxy_servers_connected:配置的上游数 / 实际连接成功数;
- proxy_tools_discovered / proxy_resources_discovered / proxy_prompts_discovered:各上游发现的能力总数;
- proxy_tools_mounted / proxy_resources_mounted / proxy_prompts_mounted:实际挂载到网关的能力数。
"发现数"与"挂载数"的差异往往来自命名冲突跳过(被 Skipping proxied tool … 诊断日志记录)。这些指标为排查"为什么某个上游工具没出现在网关里"提供了直接依据。
测试验证
仓库为代理功能提供了完整的测试覆盖,主要文件:
- tests/proxy.test.ts(566 行):验证多上游挂载、工具/资源/提示词转发、错误透传(isError)、命名空间前缀、mountProxyConnection 与低层 ProxyMountHost 等;
- tests/proxy-auth.test.ts:带鉴权的上游代理场景;
- tests/proxy-optional-peer.test.ts:缺包时的安装提示错误。
这些测试直接以 MCPServer + @modelcontextprotocol/client 的真实客户端连接方式运行,验证了"从网关连接一个客户端 → 调用 weather_forecast / inventory_find_product 这类前缀化能力 → 收到上游真实返回"的完整闭环。对照它们可以确认本文所述行为与当前仓库实现一致。
扩展到你的项目
将示例模式落地到自己的服务,只需三步:
需要注意的前提条件:
- Node.js >= 22.22.2,并使用 ESM("type": "module");
- 必须安装 @mcp-use/client 作为依赖(proxy() 的可选 peer);
- proxy() 的调用时机必须在服务器启动之前;
- 命名空间请使用客户端可读的短前缀(如 weather、inventory),它直接成为所有暴露能力名的组成部分。
通过这个示例可以看到,mcp-use 的 proxy() 把"多上游服务器聚合成单一 MCP 端点"从手工胶水代码变成了框架级能力:命名空间自动生成、能力自动发现、进度与取消透传、资源 URI 重写、失败隔离、生命周期托管一应俱全。对于需要为 Agent 整合多个领域服务的团队,这是一种开箱即用的网关方案。
赞
【免费下载链接】mcp-use
The fullstack MCP framework to develop MCP Apps for ChatGPT / Claude & MCP Servers for AI Agents.
项目地址:
https://gitcode.com/gh_mirrors/mc/mcp-use
点击查看 免费下载
相关推荐
OpenSpout:PHP开发者的表格数据处理革命
RookieAI_yolov8游戏AI自瞄系统:2025终极配置完全指南
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心




评论前必须登录!
注册