mcp-proxy
mcp-proxy
1つ以上の上流MCPサーバーの前に配置され、指定されたプロファイルが参照・呼び出しを許可されたツールのみを公開する、堅牢化MCPプロキシです。
1つの設定ファイルに実際のサーバー(GitHub、filesystem、Slack、…)とプロファイル(reviewer、implementer、ci-bot、…)を記述します。各エージェントは --profile <name> を付けてプロキシのコピーを起動するか、serve モードでは単一の共有HTTPサーバーが各接続を認証経由でプロファイルにマッピングし、それらのサーバーのフィルタリング済みかつ強制適用されたビューを取得します。これによりコンテキストトークンを節約し、危険なツール呼び出しを構造的に防ぎます。
mcp-proxy を使う理由
公式MCPサーバーはすべてのツールをすべてのエージェントに公開します。クライアントは tools/list を取得し、毎回のターンですべてのツールのスキーマをプロンプトに注入するため、コンテキストトークンを消費します。また、表示されているツールは呼び出せるツールであり、そこに厳格な境界はありません。
mcp-proxy は両方の問題を同時に解決します:
トークン節約 — プロファイルは明示的に許可したツールだけを宣伝するため、そのスキーマだけがエージェントのコンテキストに入ります。
厳格なガードレール — 許可されていないツールは一覧表示も呼び出しもされません。幻覚による呼び出しでさえ、メニューから隠すだけでなく実行時に拒否されます。
Related MCP server: sovereign-mcp-gateway
メリット
メリット | 効果 |
🔒 フェイルクローズのガードレール |
|
📉 トークン節約 | フィルタリングされた |
👥 1つの設定で複数のエージェント | レビュアー、実装者、CIボットは同じ |
🧩 マルチサーバー集約 | 複数の上流サーバー(stdio + HTTP)を単一のMCPエンドポイントの背後に統合します。 |
🔐 シークレットをリポジトリ外に保持 |
|
♻️ 耐障害性 | 指数バックオフによる自動再接続。ライブの |
🛡️ 引数検証 |
|
📊 可観測性 |
|
🌐 共有サーバーモード |
|
🏷️ 衝突安全 | サーバー間で名前が重複するツールは自動的にプレフィックスが付き( |
動作の仕組み
アーキテクチャ
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)です。プロファイルに含まれないサーバーは、そのツールを一切公開しません。
例: 3つのプロファイルの実測
同じプロキシを3つのプロファイルで実行し、実際の @modelcontextprotocol/server-filesystem 上流サーバー(14ツール)に対して実行しました。デモではトークンが不要なように、2つ目の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(すべてブロック): (なし)
注目すべき2つの詳細:
衝突時の自動プレフィックス付与 —
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 モード)
プロキシを1つのプロセスから多くのエージェントにサービスを提供する共有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 を実行して多くのエージェントが共有する1つのStreamable HTTPサーバーを公開します。各接続は認証ヘッダーからプロファイルにマッピングされます:
node dist/cli/index.js serve --config mcp-proxy.yaml
# options: --host, --port (override http.host/http.port)エンドポイント:
パス | 用途 |
| Streamable HTTP MCPエンドポイント(接続ごとにセッション) |
| 死活監視 — プロセスが起動していれば常に |
| 準備完了 — すべてのプロファイルの上流サーバーが接続されている場合のみ |
| Prometheusテキストメトリクス(ツールの一覧表示/呼び出し/ブロック、レイテンシ、上流状態) |
接続ごとのプロファイル解決はフェイルクローズです。使用可能なセレクターがない接続は、http.auth.defaultProfile が設定されていない限り (401 で) 拒否され、未知のプロファイルにマッピングされるセレクターは (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 を付けて実行すると、構造化されたJSON-linesログが stderr に出力されます(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 モードでは、Prometheusのカウンター、ゲージ、ヒストグラムを取得するために /metrics をスクレイプします: 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 → 最大15秒、無限リトライ)で再接続します。
ライブツール更新 — 上流サーバーが
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