mcp-multi-bridge
多后端 MCP 桥接(stdio 代理)。
把一个网关后面「多个后端 × 多个租户」的工具集,合并成一个 MCP 服务暴露给客户端;
调用时按 orgKey 选中租户,自动注入凭据后转发。
只做转发,不做登录。 没有 HTTP 服务、不监听端口、不弹浏览器。
最初为 MES 系统而写,现已与具体业务系统解耦:系统名、租户称谓、凭据字段名、 登录失效错误码、登录命令全部可配置(见自定义)。不配置时沿用 MES 的默认值。
安装
npm i -g mcp-multi-bridge mcp-multi-login
两个包配合使用:
mcp-multi-login负责登录,把 token 写入本地文件(旧包mes-login-cli已废弃,命令名改为mcp-multi-login)mcp-multi-bridge负责读取 token 并转发请求
从
mes-mcp-bridge升级是破坏性的,按下面三步走
卸载旧包、装新包:
npm rm -g mes-mcp-bridge npm i -g mcp-multi-bridge改
mcp.json里的 command(这一步不能省——本包只提供mcp-multi-bridge一个命令名,旧的mes-mcp-bridge不再存在):- "command": "mes-mcp-bridge" + "command": "mcp-multi-bridge"重启 MCP 客户端。
配置目录与
config.json结构不变,无需重新登录。
多后端与工具列表
config.json 里的每个组织都带一个 path,指向它在 Higress 上的 MCP 路由。
多个组织可能共享同一个 path(比如测试环境的太仓和具身智能),
它们只是登录身份不同,后台是同一个 MCP Server。
bridge 拉取工具列表的规则:
- 按
path去重,同一 path 只查一次(不是按组织逐个查) - 多个 path 并行查询
- 按工具名合并,同名是否合并看参数结构(见下)
- 单个 path 失败不阻断整体,能拿到多少给多少,失败的写进
login工具的告警里
orgKey 只能选同后端的组织
这是为了避免一种必然失败的情况:工具来自 A 后端,orgKey 却指向 B 后端的组织,
请求发过去那边不认识这个工具。bridge 会在调用前校验,跨后端直接拒绝并列出可用选项。
同名工具怎么合并
不同 path 下出现同名工具时,按 inputSchema 的指纹判断:
| 情况 | 处理 | 例子 |
|---|---|---|
| 结构相同 | 合并成一个工具,orgKey 取并集 | sz / tc / vn 三个 MES 环境共用一套 API |
| 结构不同 | 拆开,加 <系统>__ 前缀 | MES 的 search(rid) 与 ERP 的 search(keyword) |
结构相同说明是同一套后端的不同部署环境,靠 orgKey 就能区分,加前缀反而让模型困惑。
结构不同则是真的两个工具,必须区分,否则模型会拿着 A 系统的参数去调 B 系统。
系统名从 path 末段提取并去掉 -mcp-server / -api 后缀:
/mcp-servers/sz-mes-mcp-server → sz-mes,冲突时暴露为 sz-mes__search。
例外:如果一个实例只连一个后端(一套系统一个 --name),系统名直接取
--name 的值,不再从 path 推导。部署者写什么就是什么,比从 path 猜更准:
--name warehouse,path 是 /mcp-servers/erp-api
→ 系统名取 warehouse,不取 erp
因为这时 --name 和系统是一一对应的,没有歧义。多后端聚合时
--name 代表的是配置目录、下面挂着好几套系统,只能按 path 区分。
三个容易混淆的 name
| 名称 | 在哪 | 作用 |
|---|---|---|
--name | 命令行 | 只用来算配置目录 ~/.<name>-mcp-login,不参与路由 |
orgs[].path | config.json | 决定调用哪个 MCP server,唯一的路由依据 |
| mcp.json 的 key | 客户端配置 | 客户端侧 server 名,工具前缀 mcp__<key>__ 来自它 |
--name 不决定调用哪个后端。一个 --name 可以聚合多个 path
(当前 --name mes 下有 5 个 path、10 个组织),这种情况必须靠 path 区分系统。
工具很多时:按需检索
工具定义要常驻模型上下文。按中等复杂度工具约 350 tokens 估算:
| 工具数 | 常驻开销 | 能否直接用 |
|---|---|---|
| 20 | 7K | ✅ |
| 100 | 35K | ✅ 但选择准确率开始下降 |
| 500 | 175K | ⚠ 接近上限 |
| 1000 | 350K | ❌ 超过多数模型整个窗口 |
所以工具多了不能全塞。bridge 有三种模式:
MCP_BRIDGE_TOOL_MODE=auto 默认。工具数超过 40 自动切 layered
MCP_BRIDGE_TOOL_MODE=layered 强制分层
MCP_BRIDGE_TOOL_MODE=flat 强制全量
默认 auto 而不是一直分层:工具少时分层反而多一层间接,模型要先 search_tools
才能调,纯属负担。只有当全量塞不下时才值得这个代价。
分层时模型看到什么
login 状态查询
search_tools 按关键词检索工具(描述里带系统目录摘要)
describe_tool 看某个工具的完整参数定义
call_tool 调用任意工具
+ 常驻的 N 个高频工具 默认 20 个
search_tools 的返回只给「名称 + 一句话 + 前几个参数名」,不给完整 schema——
一个完整 schema 就几百 token,一次返回 10 个就等于回到全量暴露。需要细节时走 describe_tool。
命中多个时不自动挑一个。 bridge 不知道上下文:用户说「物料」可能是查也可能是建, 猜错会调错工具、产生副作用(误删数据是最坏情况)。所以按分数排好后交给模型选。
返回条数默认 5,可传 limit,上限 10。上限卡在 10 是因为:
一次返回太多,模型的选择准确率会下降,而且每条结果都占上下文。
需要看更多时应该收窄查询词,而不是调大 limit。
排序依据三层:
- 文本相关度 —— 名称命中 > 参数名命中 > 描述命中
- 意图加权 —— 「料号」偏向读操作,「创建料号」偏向写操作
- 名称字典序 —— 完全平票时的兜底,保证同样查询返回同样顺序
实测 1200 个工具(6 套系统 × 200):
flat : 604 KB ≈ 177K tokens
layered : 12 KB ≈ 4K tokens 降幅 98%
检索延迟: 10-40ms
常驻哪些工具
默认按组织覆盖数排序取前 N 个——被多套部署共用的工具更可能是每次都用的。 但真实频次只有日志知道,所以应该按调用统计把名单固化下来:
MCP_BRIDGE_RESIDENT_COUNT=20
MCP_BRIDGE_RESIDENT=searchRidReport,captchaGet
这两个只对分层模式生效。 工具没超过 40 个时走 flat(全量暴露), 它们会被忽略。为免误以为配置写错,这种情况会在 stderr 和
login工具的描述里 各提示一次——login那条是给客户端界面看的,因为多数客户端不显示 stderr。
统计方法(如果接了 Loki,request_body 里有工具名):
sum by (tool) (count_over_time({filename=~".+"} | json | line_format "{{.request_body}}" |= "tools/call" [7d]))
客户端最终看到几个工具
分层模式下是 1 + 3 + N:
| 组成 | 数量 | 能否配置 |
|---|---|---|
login | 1 | 不能,本包提供的状态查询 |
search_tools / describe_tool / call_tool | 3 | 不能,分层机制本身 |
| 常驻业务工具 | 默认 20(上限 200) | MCP_BRIDGE_RESIDENT_COUNT / MCP_BRIDGE_RESIDENT |
所以默认是 24 个。但这是「工具数超过 40」之后才会出现的情况——
工具少的时候走 flat,看到的就是 1 + 全部业务工具。
聚合几十上百套 MCP 时
一个 bridge 可以聚合任意多个 path。数量上去之后有两个成本要处理:
1. 全量拉取很贵
启动时要对每个后端发一次 tools/list。实测(单后端 30ms 延迟):
| 后端数 | 首次拉取 |
|---|---|
| 5 | 约 1 秒 |
| 50 | 约 6 秒 |
| 200 | 约 20 秒 |
所以工具清单按 TTL 缓存,默认 5 分钟:
MCP_BRIDGE_TOOLS_TTL_MS=300000
缓存期必须远大于拉取耗时,否则客户端每次刷新都要重来一遍——实测 200 个后端时,
缓存让重复的 tools/list 从 19 秒降到 0.1 秒。
新增的工具怎么被发现? 不需要把 TTL 调小。tools/call 进来时如果索引里
查不到该工具,会自动强制刷新一次再判断。所以「网关刚加了工具、模型马上要用」
这个场景是通的,代价只有那一次调用多花一轮拉取。
2. 并发要限
不设上限时 200 个后端会在同一瞬间建立 200 个连接,网关和本地 socket 都吃不住, 而且失败会一起失败。
MCP_BRIDGE_FETCH_CONCURRENCY=16
默认 16。实测 200 个后端下,8 并发约 28 秒、16 并发约 18 秒,再往上收益不明显 (压测环境是单进程 mock,序列化大 JSON 会成为新瓶颈,真实网关下最优值可能不同)。
检索是怎么打分的
不是向量检索,是加权关键词命中。1000 个工具用倒排就够,上 embedding 反而多一个服务要维护。
匹配依据只有三处:工具名、描述、参数名。全部来自工具定义本身, bridge 侧不维护任何业务词对照表。
早期版本内置过一份约 30 条的「料号 → material/item」别名表,后来删掉了,因为实测有两个问题:
| 问题 | 说明 |
|---|---|
| 对写全中文描述的工具零贡献 | 真实 MES 的描述里本来就有「料号、批次、仓库、库位」,别名表一条都没用上 |
| 会误匹配 | 「料号」被映射到 item,而别的系统里 item 可能是电商商品,queryItem 就被错误命中 |
所以规则很简单:工具名或描述里有这个词,就能搜到;没有就搜不到。 这是可预期的,也不会出现「bridge 猜错了领域」这种难以排查的情况。
新增工具不需要改代码:
新增 queryCustomsDeclaration,描述「查询报关单」
「报关单」→ 命中(中文来自 description)
「customs」→ 命中(英文来自 name)
「declNo」→ 命中(参数名)
搜不到时,问题在工具定义,不在 bridge。正确的修法是在网关侧补描述:
❌ doZzz / "Process ZZZ" 「质量追溯」搜不到
✅ doZzz / "质量追溯 · Process ZZZ" 「质量追溯」直接命中
描述里带上用户可能说的中文说法(含同义词),比维护一份注定对不齐的词表更省事, 也不会漏掉长尾——业务词根本不可能靠人工枚举穷尽。
2. 意图分级。业务 API 的工具名几乎都是 queryXxx / createXxx / updateXxx /
deleteXxx 四件套,光看业务对象四个分数完全一样。用户说「料号」时想要的是查不是创建,
所以按动作词加权重:
「料号」 → queryMaterial 只说业务对象,偏向读操作
「创建物料」 → createMaterial 明说动作,导向写操作
「导出物料」 → exportMaterial 导出和查询都是读,靠动作词再分一层
只想业务对象时不会优先给出「删除批次」这类写操作——读操作选错代价小,写操作有数据风险。
拆成多个客户端条目
另一种办法是不分层,而是用环境变量限定本实例负责的 path:
{
"mcpServers": {
"mes-test": {
"command": "mcp-multi-bridge",
"args": ["--name", "mes-test"],
"env": { "MCP_BRIDGE_PATHS": "mes-mcp-server" }
},
"mes-prod": {
"command": "mcp-multi-bridge",
"args": ["--name", "mes-prod"],
"env": { "MCP_BRIDGE_PATHS": "tc-mes-mcp-server" }
}
}
}
MCP_BRIDGE_PATHS 用逗号分隔,可以写完整 path(/mcp-servers/xxx),
也可以只写末段(xxx),两种都识别。旧名 MES_MCP_PATHS 仍兼容。
两种做法的取舍:拆 server 简单但要维护多套配置和凭证;分层只维护一套, 代价是每次找工具多一跳。系统数量不多时,拆 server 更省事。
网关侧认证(key-auth)
桥接默认只发 Content-Type,不带任何认证头——业务登录 token 是作为工具参数
传给后端的,不参与网关认证。所以当网关对某个端点开了 Key 认证时,
必须让桥接额外带一个头,否则该端点会直接 401(表现为只剩本地的 login 工具)。
三个来源,后者覆盖前者:
| 来源 | 作用范围 | 适合 |
|---|---|---|
MCP_BRIDGE_HEADERS(环境变量,JSON) | 本实例所有组织 | 一套凭证走全部环境 |
config.json 顶层 headers | 本实例所有组织 | 同上,但想写进配置文件 |
orgs[].headers | 单个组织 | 每个环境用不同凭证 |
最省事的是写在客户端条目的 env 里:
{
"mcpServers": {
"mes": {
"command": "mcp-multi-bridge",
"args": ["--name", "mes"],
"env": {
"MCP_BRIDGE_HEADERS": "{\"Authorization\":\"Bearer <网关消费者凭证>\"}"
}
}
}
}
要给不同环境配不同消费者,就写进 config.json 的组织里(优先级更高):
{
"orgs": [
{ "key": "test", "path": "/mcp-servers/mes-mcp-server" },
{ "key": "prod", "path": "/mcp-servers/tc-mes-mcp-server",
"headers": { "Authorization": "Bearer <prod 的凭证>" } }
]
}
三个来源都没配时,请求头与旧版完全一致,不影响未开认证的网关。
凭证是明文,别提交进仓库;
MCP_BRIDGE_HEADERS不是合法 JSON 时会被忽略并打一行提示, 不会让进程退出——但那个端点会因此 401,排查时先看这条提示。
检查安装的版本
mcp-multi-bridge --version # 只输出版本号,如 1.4.0
mcp-multi-bridge --status # 版本 + 安装位置 + 网关地址 + 后端分组 + 各组织登录状态
mcp-multi-bridge --help # 帮助
--version 只输出纯版本号,方便脚本取值:
mcp-multi-bridge --version | xargs echo "当前版本:"
如果怀疑跑的不是自己想跑的那份(比如同时存在 npm link 的软链和全局安装),
用 --status 看「安装位置」即可确认。
mcp-multi-login 同样支持 --version。
配置 MCP 客户端
任选其一。
方式一:npx(免安装)
{
"mcpServers": {
"mes": {
"command": "npx",
"args": ["-y", "mcp-multi-bridge"]
}
}
}
方式二:全局安装(推荐)
npm i -g mcp-multi-bridge
{
"mcpServers": {
"mes": {
"command": "mcp-multi-bridge",
"args": []
}
}
}
全局安装的好处是升级可控:npx 只跳过安装确认,不会因为发布了新版本就刷新本地缓存。
方式三:绝对路径(最稳)
如果客户端启动子进程时找不到命令(ENOENT),直接用 node 加绝对路径:
{
"mcpServers": {
"mes": {
"command": "node",
"args": ["C:/Users/你的用户名/AppData/Roaming/npm/node_modules/mcp-multi-bridge/bin.js"]
}
}
}
实际路径用这条命令查:
npm root -g
使用流程
-
首次使用,或 token 过期时,执行登录:
mcp-multi-login -
在打开的登录页里完成账号密码和图形验证码
-
回到 MCP 客户端,直接调用工具即可
登录后无需重启客户端。 bridge 每次调用都重新读取 token 文件,所以登录结果立即生效。
未登录时的表现
工具调用会返回明确的引导文案,说明该执行 mcp-multi-login 以及要等人工完成验证码。模型拿到这段文字后可以自行发起登录。
多组织
登录页支持切换组织,每个组织单独保存 token,互不覆盖。
调用工具时可以指定组织:
{ "orgKey": "tc" }
orgKey 的取值就是 config.json 里各组织的 key,不传则用最后一次登录的组织。
自定义系统差异
合并、路由、凭据注入这套逻辑与具体业务系统无关,真正随系统变化的是六件事。
全部在 config.json 里配置,都不写则沿用 MES 的默认值,升级后行为不变。
{
"systemName": "ERP 中台",
"tenantLabel": "租户",
"credentialField": "authToken",
"tenantIdField": "tenantId",
"hiddenArgs": ["authToken", "tenantId"],
"invalidCodes": [401, 403],
"invalidCodeReasons": { "401": "未授权,请重新登录" },
"loginCommand": "erp-login"
}
| 字段 | 默认值 | 作用 |
|---|---|---|
systemName | MES | 系统名,出现在登录引导与状态标题里 |
tenantLabel | 组织 | 租户称谓,出现在 orgKey 参数说明、状态清单与各类提示里 |
credentialField | access_token | 注入到工具参数里的凭据字段名,必须与网关 requestTemplate 取值的字段一致 |
tenantIdField | orgId | 注入租户 ID 时用的参数名,取值来自配置的 orgs[].orgId |
hiddenArgs | 跟随上面两个字段 | 对模型隐藏的参数。凭据与租户 ID 由本层填,露给模型只会引发「我该填什么」 |
invalidCodes | [613,614,615,616] | 视为「登录失效」的业务错误码,命中后返回重新登录引导 |
invalidCodeReasons | MES 的四个码 | 错误码说明,与默认值合并,只写要覆盖的项 |
loginCommand | mcp-multi-login | 登录引导里让模型执行的命令名 |
租户 ID 由本层注入,不让模型填。 网关的工具模板通常是
{{if .args.orgId}}{{.args.orgId}}{{else}}1{{end}}—— 不传就一律按 1 查, 同一个后端下的其他租户会静默查到错误组织的数据。所以本层总是把orgs[].orgId注入进去,客户端传的同名字段会被覆盖。
显示效果对比
| 默认 | 上面那份配置 | |
|---|---|---|
| 工具描述 | 查询 MES 的组织与登录状态 | 查询 ERP 中台的租户与登录状态 |
| 参数说明 | 要操作的组织标识 | 要操作的租户标识 |
| 状态标题 | ## MES 组织状态 | ## ERP 中台租户状态 |
| 更新提示 | 该组织尚未登录 | 该租户尚未登录 |
| 引导命令 | mcp-multi-login --org tc | erp-login --org tc |
orgKey 这个参数名不会变——它是路由用的标识,改文案不影响调用方代码。
容错
所有字段都做了类型校验,配错(null、空串、非数组、非数字)时静默回落默认值,
不会让服务起不来。systemName 以汉字结尾时不加空格(「查询 ERP 中台的租户」),
以拉丁字母结尾时加(「查询 MES 的组织」)。
配置目录与多系统并存
默认目录 ~/.mes-mcp-login,不设环境变量就用它。现有用户升级后行为不变。
目录名带 mes 是历史沿革:mcp-multi-login 的默认值也是它,两边必须一致,
否则登录完桥接层找不到 token。要改名得两个包一起改。
同一个客户端接多套系统时,每个 bridge 实例指向不同目录:
{
"mcpServers": {
"mes": {
"command": "mcp-multi-bridge",
"args": [],
"env": { "MCP_MULTI_HOME": "C:/Users/你/.mes-mcp-login" }
},
"erp": {
"command": "mcp-multi-bridge",
"args": [],
"env": { "MCP_MULTI_HOME": "C:/Users/你/.erp-login" }
}
}
}
工具名由客户端加前缀区分(mcp__mes__captchaGet / mcp__erp__captchaGet),同名工具不冲突。
| 环境变量 | 作用 | 默认 |
|---|---|---|
MCP_MULTI_HOME | 配置目录 | ~/.mes-mcp-login |
MCP_BRIDGE_HOME | 同上(旧名,仍兼容) | — |
MCP_BRIDGE_PATHS | 限定本实例负责的 path,逗号分隔 | 不限制(全部加载) |
MCP_MULTI_HOME优先于MCP_BRIDGE_HOME;MCP_BRIDGE_PATHS优先于旧名MES_MCP_PATHS。
登录引导会带上 --home
未登录或登录失效时,本服务返回的引导命令是完整可执行的:
mcp-multi-login --home "C:/Users/你/.mes-mcp-login" --org tc
--home 指向本实例实际读取的目录,不能省——省了就会写到默认目录去,
表现是「登录成功了但这边还是未登录」,非常难查。
默认用 mcp-multi-login。若你换成了别的登录程序(比如自研的 erp-login),
用 loginCommand 改:
{ "loginCommand": "erp-login" }
登录包只提供
mcp-multi-login一个命令,没有mes-login别名。 从旧包升级的,务必确认loginCommand没被配成旧名。
文件依赖
读取配置目录下的两个文件:
| 文件 | 由谁创建 |
|---|---|
config.json | 登录侧 CLI(mcp-multi-login),也可手写 |
token.json | 登录侧 CLI(登录时写入) |
本包只读不写,不会创建、修改或删除这两个文件。
所以使用前必须先安装并运行一次登录 CLI(mcp-multi-login)。
提供的工具
工具列表由网关返回,本包原样透传。login 会被本包替换成本地状态查询工具。
分层模式下还会额外提供 search_tools / describe_tool / call_tool 三个元工具,
用于按需发现工具,避免把上千个定义全塞进上下文。
工具本身(有哪些、叫什么、什么参数)完全取决于网关侧配置,本包不做增删。 当前 MES 网关提供的是:
login登录态查询(本包接管)captchaGet获取验证码captchaCheck校验验证码searchRidReportRID 明细报表
平台支持
Windows / macOS / Linux。需要 Node.js >= 18。
License
MIT