mcpgw 是一个用 Rust 编写的 MCP(Model Context Protocol)网关,它将多个上游 MCP server 聚合在一起,但不会把成百上千的工具一次性塞进 LLM 的上下文中,而是只对下游客户端暴露 3 个稳定的「元工具」,让模型能够渐进式地发现并调用工具:
| 元工具 | 作用 |
|---|---|
search_tools(query) |
用自然语言查询,返回最相关的工具(BM25 / 向量 / 混合 / subagent 重排,纯本地默认 BM25) |
get_tool_details(name) |
获取某个工具的完整定义(schema + 所属上游) |
call_tool(name, arguments) |
按限定名(server__tool)调用,网关转发到对应上游 |
run_code(code)(可选) |
在 deny-by-default QuickJS 沙箱里运行模型编写的 JS,通过注入的 callTool / 类型化 <server>.<tool> / listTools 编排多个工具(支持 Promise.all 并行),每次调用仍然经过既有的 RBAC→审批→审计门;仅在配置 [code_mode] 时暴露(默认关闭) |
这样无论后端接入了多少上游、多少工具,模型看到的永远只是 3 个(开启 code-mode 后为 4 个)工具,上下文不会随工具数量膨胀。
在此基础上,mcpgw 还提供了一整套面向安全公网暴露的能力(全部 opt-in、默认关闭、fail-closed): 内置 TLS、API-Key / OAuth 2.1 鉴权、细粒度 RBAC、危险操作的人工审批、只读可视化 dashboard、Prometheus metrics,以及第 4 个元工具 code-mode(在沙箱里运行模型编写的 JS 来编排多个工具调用)。
flowchart LR
subgraph Clients["下游客户端(LLM / MCP client)"]
C1["stdio"]
C2["streamable-HTTP"]
end
subgraph mcpgw["mcpgw 进程"]
DS["downstream<br/>(rmcp Server: 4 元工具)"]
AUTH["鉴权 + RBAC + 审批<br/>(api-key / OAuth → Principal)"]
GW["gateway<br/>ArcSwap 快照 + 重建"]
RET["retrieval<br/>bm25 / vector / hybrid / subagent"]
OBS["observe / metrics / dashboard<br/>(审计 · /metrics · 只读面板)"]
end
subgraph Upstreams["上游 MCP servers"]
U1["stdio 子进程"]
U2["streamable-HTTP"]
end
C1 & C2 --> DS --> AUTH --> GW
GW <-->|聚合工具 / 转发调用| U1 & U2
GW --- RET
DS -.-> OBS
下游 = 网关对客户端暴露的 MCP server;上游 = 网关连接的那些 MCP server。
一次 call_tool 的完整路径详见 L1 概览 与 RBAC+审批 L3。
cargo build # 产出 target/debug/mcpgw(加 --release 出优化版)某些配置项(API-Key 密钥、dashboard 管理员 token、上游 credential、检索后端 API key 等)通过 TOML 中引用环境变量名 来传递,实现"配置与机密分离":
# 示例:mcpgw.toml 中不写明文密钥,而是写环境变量名
[[server.http.api_key]]
name = "demo"
env = "MCPGW_DEMO_KEY" # 密钥本身从环境变量读取mcpgw 启动时会自动从工作目录(及所有父目录)加载 .env 文件(使用
dotenvy),无需手动 export:
cp .env.example .env # 从模板复制
# 编辑 .env,填入你的密钥
./target/debug/mcpgw --config mcpgw.toml serve # 直接启动即可
.env已加入.gitignore,不会被误提交。团队协作时参考.env.example了解需要哪些变量。
新建 mcpgw.toml:
[retrieval]
strategy = "bm25" # 纯本地,无需任何 API key
[server]
stdio = true # 下游使用 stdio(供同机 MCP 客户端 spawn)
# 一个 stdio 上游(示例:官方 everything server;替换成你自己的命令即可)
[[upstream]]
name = "demo" # 命名空间前缀 → 工具名形如 demo__<tool>
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-everything"]
env_passthrough = ["PATH", "HOME"] # 只透传白名单环境变量给子进程
call_timeout_ms = 30000默认的 bm25 策略是纯本地、无需外部服务的。如果你的工具数量较大,或者希望更精准的
语义搜索,可以切换到基于 embedding 的策略。
将工具描述转为 embedding,查询时做余弦相似度匹配。embedding 失败时自动降级为 BM25。
前置条件:需要一个 OpenAI 兼容的 embedding endpoint(OpenAI、Azure、vLLM 等)。
[retrieval]
strategy = "vector"
top_k = 10
[retrieval.vector]
base_url = "https://api.openai.com/v1" # 或其他兼容 endpoint
model = "text-embedding-3-small"
api_key_env = "OPENAI_API_KEY" # 从环境变量读取,不写明文
# dim = 1536 # 可选:维度校验(不填则自动推断)
# timeout_ms = 30000 # 可选:请求超时(默认 30s)
# batch_size = 32 # 可选:批量大小.env 中设置:
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx同时运行 BM25 和向量检索,用 倒数排名融合(RRF) 合并两个排名,兼具 关键词匹配和语义理解的优势。同样需要 embedding endpoint。
[retrieval]
strategy = "hybrid"
top_k = 10
[retrieval.vector]
base_url = "https://api.openai.com/v1"
model = "text-embedding-3-small"
api_key_env = "OPENAI_API_KEY"先用 BM25 粗筛候选,再让一个小型 chat 模型从候选中选出最相关的工具。 适合有大量工具、希望借助 LLM 理解能力的场景。
前置条件:需要一个 OpenAI 兼容的 chat API。
[retrieval]
strategy = "subagent"
top_k = 10
[retrieval.subagent]
base_url = "https://api.openai.com/v1"
model = "gpt-4o-mini" # 小模型即可,只做重排
api_key_env = "OPENAI_API_KEY"
# candidates = 20 # 可选:预筛选候选数(默认 20)| 策略 | 外部依赖 | 适用场景 |
|---|---|---|
bm25 |
无 | 工具较少(<50),关键词匹配足够好用 |
vector |
Embedding API | 工具较多,需要语义搜索 |
hybrid |
Embedding API | 推荐:兼顾关键词 + 语义,最稳健 |
subagent |
Chat API | 工具非常多(>100),需要 LLM 理解力 |
./target/debug/mcpgw --config mcpgw.toml serve
# stderr 是日志;stdout 是 MCP 协议帧。把它配置到你的 MCP 客户端即可使用。将下游改为 HTTP + 只读面板(适合浏览器查看、或远程客户端连接):
[server]
stdio = false # 只开 HTTP,后台运行不依赖 stdin
[server.http]
enabled = true
bind = "127.0.0.1:8970" # 下游 MCP 端点:http://127.0.0.1:8970/mcp
path = "/mcp"
[dashboard]
enabled = true
bind = "127.0.0.1:8971" # 浏览器打开 http://127.0.0.1:8971(只读)默认端口约定:下游 MCP 8970、dashboard 8971、metrics 8972,均默认仅绑定
127.0.0.1。 绑定非 loopback 地址且无鉴权会被启动期 fail-closed 拒绝(除非显式设置allow_public_unauthenticated = true)——详见 公网暴露指南。
# search / get-details 在工作区根目录运行,默认使用 --catalog tests/fixtures/tools.json
./target/debug/mcpgw search "weather forecast"
./target/debug/mcpgw get-details github__create_issue| 能力 | 说明 | 文档 |
|---|---|---|
| 渐进式发现 | 3 元工具 + 可插拔检索(BM25 / 向量 / 混合 / subagent 重排,远端后端默认 30s 超时) | retrieval L2 |
| 上游聚合 | stdio 子进程与 streamable-HTTP 上游,env 白名单、每调用超时、tools/list_changed 热重建 |
upstream L2 · gateway L2 |
| 公网暴露安全 | 内置 TLS、API-Key / OAuth 2.1 资源服务器、fail-closed 暴露门 | 公网暴露指南 |
| RBAC | 角色 default-deny + deny-overrides,发现过滤 + 调用拦截 + 决策审计 | RBAC+审批 L3 · rbac L2 |
| 人工审批 | 危险工具调用在 RBAC 放行后再经过人审第二道门(阻塞→批准/拒绝/超时) | RBAC+审批 L3 · gateway 审批 L4 |
| 幂等调用 | 副作用工具显式 key、SQLite 持久化、并发 single-flight 与结果/墓碑重放;提供保留期内 at-most-once dispatch(非 exactly-once) | idempotency L2 |
| 可视化面板 | 只读 dashboard:调用/追踪/指标/活动 + 上游/工具,可选 Bearer 鉴权的运行时禁用 / 在线改配 / 审批裁决 | dashboard L2 · L3 |
| 可观测性 | 仅元数据的结构化审计 JSONL + Prometheus /metrics 导出 |
observe L2 · metrics L2 |
| code-mode 沙箱 | 第 4 个元工具 run_code:deny-by-default QuickJS(rquickjs)沙箱运行模型编写的 JS,通过 callTool/类型化 <server>.<tool>/listTools 编排上游(支持 Promise.all 并行),每次调用复用既有 RBAC→审批→审计门不旁路;双重时限 + 4 资源闸 + code_busy 并发封顶;[code_mode] opt-in、restart-required(L3 会话/L4 流式延后) |
codemode L2 · L3 · L4 |
幂等保护默认关闭,使用独立 SQLite 文件显式开启。terminal 结果默认保留 24 小时;retention_hours = 0
表示永久保留。默认最多保存 1 MiB 结果,超限时保存不可重放墓碑,而不是截断结果。
[idempotency]
enabled = true
store_path = "mcpgw-idempotency.sqlite"
retention_hours = 24
max_result_bytes = 1048576
store_failure_mode = "reject" # reject | warn
require = ["payments__*", "github__create_*"]direct call_tool 在 envelope 顶层传 idempotency_key。code-mode 可用
callTool(name, args, {idempotencyKey: "..."}) 或 <server>.<tool>(args, {idempotencyKey: "..."});options
只接受 idempotencyKey,不会向上游传播该 key。已认证 HTTP 和固定 stdio identity 可用 key;匿名 HTTP keyed call
会被拒绝。
网关在保留期内提供 at-most-once dispatch,不是 exactly-once。timeout、transport 未知和进程在 durable started
之后崩溃都会返回 indeterminate;同 key 不再下发。不要自动重试,也不要直接换新 key。必须先通过上游查询、事件或
人工流程 reconcile 原业务意图,确认需要再次执行后才能使用新 key。多进程共享同一个幂等 SQLite 文件、自动重试、
upstream key propagation 均不在支持范围内。详见 idempotency L2。
cargo build # 构建
cargo test --all-features # 全部测试(含 mock-stdio / HTTP e2e 需 --all-features)
cargo test --all-features -- --ignored # 门控的真实冒烟 / 二进制 e2e(绑端口/启进程)
cargo clippy --all-targets --all-features -- -D warnings # 静态检查,零告警
cargo fmt --all # 格式化dashboard 前端(Svelte 5 + Vite,dist/ 已入库故 cargo 不依赖 node):
cd crates/dashboard/ui && npm install && npm test && npm run build # 改 UI 源码后须重建 dist/(字节可复现)完整的分层文档(L1 概览 → L2 组件 → L3 内部细节 → L4 逐文件 API)详见 docs/:
- L1 概览 —— 整体架构、数据流、里程碑状态
- 公网暴露指南 —— TLS / 隧道 / 反代 / API-Key / OAuth / RBAC / 审批的安全配置
- RBAC + 审批 L3 —— auth → RBAC → 审批流水线与 fail-closed/fail-fast 决策矩阵
- 幂等调用 L2 —— 显式 key、SQLite 状态机、single-flight、indeterminate 与保证边界
- 过程产物(spec / plan / 路线图)见
docs/superpowers/
- 渐进式工具发现:将数百个工具压缩为 3 个元工具,避免上下文爆炸
- 可插拔检索策略:BM25(默认)、向量、混合(RRF)、subagent 重排
- 多传输协议:stdio 子进程与 HTTP 上游/下游,可混合使用
- 企业级安全:
- 内置 TLS、API-Key / OAuth 2.1 鉴权
- 细粒度 RBAC(角色 default-deny + deny-overrides)
- 危险操作人工审批(human-in-the-loop)
- fail-closed / fail-fast 设计
- 可观测性:
- 结构化审计日志(仅元数据,不含敏感内容)
- Prometheus metrics 导出
- 只读可视化 dashboard(可选 Bearer 鉴权的写能力)
- code-mode 沙箱:QuickJS 沙箱中运行模型生成的 JS,安全编排多个工具调用
- 生产就绪:
- 热重载上游配置
- 优雅关闭与降级启动
- 资源限制与并发控制
- 全面的测试覆盖(578 passed / 11 ignored)
详见仓库 LICENSE 文件(若有)。
