mcp-proxy
mcp-proxy
一个加固型 MCP 代理,位于一个或多个上游 MCP 服务器之前,只暴露给定配置文件允许查看和调用的工具。
一个配置文件描述你的真实服务器(GitHub、filesystem、Slack 等)以及配置文件(reviewer、implementer、ci-bot 等)。每个代理使用 --profile <name> 启动自己的代理副本——或者,在 serve 模式下,一个共享的 HTTP 服务器通过认证将每个连接映射到配置文件——并获得这些服务器的过滤后、强制执行的视图,从而节省上下文令牌并从构造上防止危险的工具调用。
为什么使用 mcp-proxy?
官方 MCP 服务器将所有工具暴露给每个代理。客户端获取 tools/list 并将每个工具的模式注入到提示中,在每一轮都如此,消耗上下文令牌。而且,可见的工具就是可调用的工具——没有硬性边界。
mcp-proxy 一次性解决这两个问题:
令牌节省 —— 配置文件只宣传你明确允许的工具,因此只有这些模式会进入代理的上下文。
硬性护栏 —— 不允许的工具既不会被列出,也不会被调用:即使是幻觉调用也会在执行时被拒绝,而不仅仅是从菜单中隐藏。
Related MCP server: sovereign-mcp-gateway
优势
优势 | 帮助方式 |
🔒 故障关闭护栏 |
|
📉 令牌节省 | 过滤后的 |
👥 一个配置,多个代理 | Reviewer、implementer 和 CI bot 共享相同的 |
🧩 多服务器聚合 | 在单个 MCP 端点后面合并多个上游(stdio + HTTP)。 |
🔐 密钥不进入仓库 |
|
♻️ 弹性 | 自动重连,指数退避;实时 |
🛡️ 参数验证 |
|
📊 可观测性 |
|
🌐 共享服务器模式 |
|
🏷️ 冲突安全 | 跨服务器共享名称的工具自动加前缀( |
工作原理
架构
flowchart TB
subgraph agents["🤖 Agents (MCP clients)"]
direction LR
A1["reviewer agent<br/><code>--profile reviewer</code>"]
A2["implementer agent<br/><code>--profile implementer</code>"]
end
subgraph proxy["mcp-proxy — one stdio process per agent"]
direction TB
D1["stdio transport"]
D2["tool filter<br/>(allow/block · globs + regex)"]
D3["call-time guardrail<br/>+ argument validation"]
D4["upstream registry<br/>(discovery · reconnect · list_changed)"]
end
subgraph up["Upstream MCP servers"]
direction LR
U1["filesystem<br/>(stdio)"]
U2["github<br/>(HTTP)"]
U3["slack<br/>(HTTP)"]
end
A1 -->|"stdin/stdout"| D1
A2 -->|"stdin/stdout"| D1
D1 --> D2 --> D3 --> D4
D4 -->|"spawn"| U1
D4 -->|"connect"| U2
D4 -->|"connect"| U3每个代理将代理作为子进程通过 stdio 生成。代理连接到所选配置文件中列出的每个上游,获取每个 tools/list,应用配置文件的允许/阻止规则,并仅重新暴露存活的工具。
请求流程
sequenceDiagram
autonumber
participant A as Agent
participant P as mcp-proxy
participant U as Upstream MCP server
A->>P: tools/list
P->>U: tools/list (every upstream in profile)
U-->>P: full tool set
P->>P: filter + collision resolve
P-->>A: allowed tools only
A->>P: tools/call (allowed tool)
P->>P: guardrail re-check<br/>+ schema validation
P->>U: forward call
U-->>P: result
P-->>A: result
A->>P: tools/call (blocked tool)
P-->>A: ❌ rejected with error
U-->>P: notifications/tools/list_changed
P->>U: re-fetch tools/list
P->>P: re-filter
P-->>A: notifications/tools/list_changed过滤决策
只有当工具通过以下优先级链时,它才被允许:
flowchart LR
T["tool name"] --> B{"matches a<br/><code>block</code> pattern?"}
B -- "yes" --> DENY["🔒 DENY"]
B -- "no" --> A{"matches an<br/><code>allow</code> pattern?"}
A -- "yes" --> OK["✅ ALLOW"]
A -- "no" --> D["fallback:<br/>server <code>default</code><br/>→ profile <code>default</code><br/>→ <code>block</code>"]
D --> F{"fallback is <code>allow</code>?"}
F -- "yes" --> OK
F -- "no" --> DENYblock 始终优先。模式是通配符(read_*、{get,list}_*)或正则表达式(/.*delete.*/i)。配置文件中省略的服务器不会暴露其任何工具。
示例:三个配置文件,实时测量
同一个代理通过三个配置文件驱动,针对真实的 @modelcontextprotocol/server-filesystem 上游(14 个工具)运行。第二个 filesystem 实例代替 HTTP GitHub 服务器,因此演示不需要令牌——每个服务器的过滤行为对任何上游都是相同的。
# mcp-proxy.yaml (demo)
version: 1
servers:
filesystem:
type: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "C:/data"]
github: # HTTP in real life; filesystem stand-in in this demo
type: http
url: https://api.github.com/mcp
headers: { Authorization: "${GITHUB_TOKEN}" }
profiles:
reviewer:
default: block
servers:
filesystem:
allow: ["read_file", "list_directory", "search_files", "directory_tree", "get_file_info"]
github:
block: ["**"] # GitHub fully disabled for this agent
implementer:
default: allow
servers:
filesystem:
block: ["/.*delete.*/i", "remove_*", "edit_file", "write_file"]
github: {} # all GitHub tools allowed
noTools:
default: block
servers:
filesystem: { block: ["**"] }
github: { block: ["**"] }在实时 tools/list 握手中测量:
配置文件 | 暴露的工具 |
| ~令牌 |
| 5 | 2,926 字符 | ~732 |
| 26 | 15,762 字符 | ~3,941 |
| 0 | 2 字符 | ~1 |
令牌使用 ~4 字符/令牌的启发式方法;真正的节省是代理每轮重新加载到上下文中的模式表面。
每个配置文件实际收到的工具:
reviewer(只读,GitHub 被阻止):read_file、list_directory、directory_tree、search_files、get_file_infoimplementer(拒绝列表,GitHub 允许):filesystem__read_file、github__read_file、filesystem__read_text_file、github__read_text_file、filesystem__read_media_file、github__read_media_file、filesystem__read_multiple_files、github__read_multiple_files、filesystem__create_directory、github__create_directory、filesystem__list_directory、github__list_directory、filesystem__list_directory_with_sizes、github__list_directory_with_sizes、filesystem__directory_tree、github__directory_tree、filesystem__move_file、github__move_file、filesystem__search_files、github__search_files、filesystem__get_file_info、github__get_file_info、filesystem__list_allowed_directories、github__list_allowed_directories、write_file、edit_filenoTools(所有内容被阻止):(无)
两个值得注意的细节:
冲突自动加前缀 ——
read_file存在于两个服务器上,因此它变成filesystem__read_file和github__read_file。但write_file/edit_file保留其裸名称,因为它们在filesystem上被阻止,留下github作为唯一来源。空配置文件视图是有效的 ——
noTools(或任何带有block: ["**"]的配置文件,或省略的服务器)暴露零个工具;代理仍然连接,只是没有可调用的内容。
快速开始
1. 安装并构建
npm install
npm run build # compiles TypeScript to dist/2. 将密钥放入 .env(切勿放入配置中)
cp .env.example .env # then fill in your tokens3. 编写 mcp-proxy.yaml
version: 1
servers:
filesystem:
type: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "C:/repo"]
env:
ROOT: "C:/repo"
github:
type: http
url: https://api.github.com/mcp
headers:
Authorization: "${GITHUB_TOKEN}" # env-var reference, not a literal secret
profiles:
reviewer: # read-only, fail-closed
description: "Read-only agent"
default: block
servers:
filesystem:
allow: ["read_file", "list_directory", "directory_tree", "get_file_info"]
github:
allow: ["get_*", "list_*", "search_*"]
implementer: # deny-list, fail-open minus dangerous ops
description: "Full access minus destructive ops"
default: allow
servers:
filesystem:
block: ["/.*delete.*/i", "edit_file", "write_file"]
github:
block: ["merge_pull_request", "delete_*"]
defaultProfile: reviewer4. 运行
node dist/cli/index.js --profile reviewer
# add --verbose for structured debug logging
node dist/cli/index.js --profile reviewer --verbose配置文件优先级:--profile > MCP_PROFILE > defaultProfile。
配置参考
servers —— 上游 MCP 服务器
stdio(作为子进程生成):
filesystem:
type: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "C:/repo"]
env: { ROOT: "C:/repo" }
prefix: fs__ # optional: override collision-prefix namespacehttp(Streamable HTTP):
github:
type: http
url: https://api.github.com/mcp
headers:
Authorization: "${GITHUB_TOKEN}"
prefix: gh__ # optionalprofiles —— 命名工具视图
profiles:
my-profile:
description: "..." # optional
default: allow # allow | block (fallback when no rule matches)
servers:
github:
allow: ["get_*"] # optional allow-list
block: ["delete_*"] # optional block-list (always wins)
default: block # optional per-server fallback override
# filesystem omitted → none of its tools are exposedhttp —— Streamable HTTP 下游(serve 模式)
可选的顶级块,将代理变成共享 HTTP 服务器,从一个进程服务多个代理。请参阅 共享服务器(HTTP)。
http:
host: 0.0.0.0 # default 127.0.0.1
port: 3000 # default 3000
path: /mcp # MCP endpoint (default /mcp)
metricsPath: /metrics # Prometheus metrics (default /metrics)
healthPath: /health # liveness (default /health)
readyPath: /ready # readiness (default /ready)
auth:
header: authorization # selector header (default authorization)
scheme: Bearer # optional prefix to strip
tokens: # token -> profile map (values may use ${VAR})
tok-reviewer: reviewer
tok-impl: implementer
defaultProfile: reviewer # optional fallback (fail-closed without it)当设置了 tokens 时,去除方案后的头值在映射中查找。没有 tokens 时,去除方案后的头值直接用作配置文件名称。缺失/未知的选择器回退到 defaultProfile,如果没有适用则被拒绝(401/403)。
密钥
${VAR} 占位符在加载时从环境(或 .env)解析。YAML 只保存变量名称,因此提交是安全的。缺少变量会使加载器快速失败——不会出现静默空头。
连接到你的代理
代理就是一个通过 stdio 的 MCP 服务器。将你的代理指向代理入口点,而不是真实服务器,并传递配置文件标志。
// .mcp.json — reviewer agent
{
"mcpServers": {
"proxy": {
"command": "node",
"args": ["C:/Dev/mcp-proxy/dist/cli/index.js", "--profile", "reviewer"]
}
}
}// .mcp.json — implementer agent (same proxy, different profile)
{
"mcpServers": {
"proxy": {
"command": "node",
"args": ["C:/Dev/mcp-proxy/dist/cli/index.js", "--profile", "implementer"]
}
}
}每个代理都有自己的 stdio 进程,因此配置文件在每个代理之间完全隔离,凭据永远不会跨进程边界。
共享服务器(HTTP)
对于集中部署,运行 serve 以暴露一个 Streamable HTTP 服务器,供多个代理共享。每个连接根据其认证头映射到配置文件:
node dist/cli/index.js serve --config mcp-proxy.yaml
# options: --host, --port (override http.host/http.port)端点:
路径 | 用途 |
| Streamable HTTP MCP 端点(每个连接一个会话) |
| 存活——进程启动后始终 |
| 就绪——仅当每个配置文件的上游都连接时返回 |
| Prometheus 文本指标(列出的/调用的/阻止的工具、延迟、上游状态) |
每个连接的配置文件解析是故障关闭的:没有可用选择器的连接被拒绝(401),除非设置了 http.auth.defaultProfile,并且映射到未知配置文件的选择器被拒绝(403)。
共享部署的客户端配置(任何支持 Streamable-HTTP 的客户端):
// .mcp.json — reviewer agent (token maps to the `reviewer` profile)
{
"mcpServers": {
"proxy": {
"type": "http",
"url": "https://proxy.example.com/mcp",
"headers": { "Authorization": "Bearer ${PROXY_TOKEN}" }
}
}
}// .mcp.json — implementer agent (same server, different token/profile)
{
"mcpServers": {
"proxy": {
"type": "http",
"url": "https://proxy.example.com/mcp",
"headers": { "Authorization": "Bearer ${PROXY_TOKEN_IMPL}" }
}
}
}Copilot 编码代理读取仓库的 .mcp.json;对于其他代理,使用其原生 MCP 服务器字段(请参阅 context/AGENT-SETUP.md 和 context/VENDOR-AGENTS.md)。
可观测性
使用 --verbose 运行以向 stderr 输出结构化 JSON 行日志(保持 MCP stdio 通道在 stdout 上干净):
{"timestamp":"2026-08-23T17:22:26.976Z","level":"info","message":"connected to upstream","server":"filesystem","tools":14}
{"timestamp":"2026-08-23T17:22:26.980Z","level":"debug","message":"tools/call","correlationId":"42","tool":"read_file","server":"filesystem"}每个 tools/list 和 tools/call 条目都携带 MCP 请求的 correlationId,因此可以在代理及其上游之间跟踪单个请求。
要查看配置文件的上下文成本,请比较不同配置文件之间的工具数量和 tools/list 负载大小(请参阅上面的测量示例):宣传的工具越少,意味着每轮注入到提示中的模式越少。
在 serve 模式下,抓取 /metrics 获取 Prometheus 计数器、仪表和直方图:mcp_proxy_tools_listed_total、mcp_proxy_tools_called_total、mcp_proxy_tools_blocked_total、mcp_proxy_tool_call_duration_seconds 和 mcp_proxy_upstream_connections(全部按 profile/server/tool 标记)。
弹性
自动重连 —— 如果上游(尤其是生成的 stdio 进程)死亡,代理以指数退避重连(500ms → 15s 上限,无限重试)。
实时工具更新 —— 当上游发出
notifications/tools/list_changed时,代理重新获取、重新过滤并将更改转发到下游,因此代理始终看到准确的工具列表。参数验证 ——
tools/call参数在转发前根据上游的inputSchema进行检查;无效调用在本地被拒绝。
开发
npm run typecheck # tsc --noEmit
npm test # vitest (unit + integration + filesystem smoke)
npm run build # tsc → dist/更多
context/DESIGN.md —— 完整设计、决策和权衡。
context/SETUP.md —— 真实 stdio + HTTP 服务器的逐步设置。
context/ROADMAP.md —— v1.0 已发布(HTTP 下游、每连接配置文件、可观测性、打包)。
mcp-proxy.yaml—— 工作示例配置。.env.example—— 环境变量模板。
This server cannot be deployed
Maintenance
Related MCP Connectors
Stateless MCP gateway and OTel span-streaming bridge for hosted MCP servers.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
Streamable HTTP MCP gateway for NV oOS fleets — one API key, namespaced tools, quotas and policy.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceCentralized MCP control plane that proxies multiple upstream MCP servers with tool namespacing, filtering, policy enforcement, audit logging, and health checks.6 npmMIT
- FlicenseNot gradedqualityBmaintenanceActs as a gating proxy for MCP servers, merging tools and applying policy, verification, and audit layers.40 PyPI-
- AlicenseNot gradedqualityBmaintenanceProvides a governance proxy layer for MCP servers, enforcing per-tool allowlists, human approval for write operations, quotas, secret redaction, and a hash-chained audit log of all calls.MIT
- AlicenseNot gradedqualityCmaintenanceFronts many downstream MCP servers through one session, presenting a unified tool namespace with collision resolution, fleet-wide allow/deny/hide policy, per-tool rate limits, centralized audit logging, Prometheus metrics, and bearer-key auth at the edge. Supports both stdio drop-in and Streamable HTTP deployment so agents point at a single endpoint instead of N servers.MIT