Skip to main content
Glama

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

メリット

メリット

効果

🔒 フェイルクローズのガードレール

block が優先されます。未知のツールは既定で拒否されます。可視性と呼び出し可能性は同期が保たれます。

📉 トークン節約

フィルタリングされた tools/list により、プロンプトが小さくなり、より安価で焦点を絞ったセッションになります。

👥 1つの設定で複数のエージェント

レビュアー、実装者、CIボットは同じ servers ブロックを共有しつつ、--profile で異なるプロファイルを取得します。

🧩 マルチサーバー集約

複数の上流サーバー(stdio + HTTP)を単一のMCPエンドポイントの背後に統合します。

🔐 シークレットをリポジトリ外に保持

${VAR} プレースホルダーと .env を使用。ローダーは変数が欠けていると即座に失敗します。

♻️ 耐障害性

指数バックオフによる自動再接続。ライブの tools/list_changed 更新は再フィルタリングされて下流に伝播します。

🛡️ 引数検証

tools/call の引数は転送前に上流の inputSchema に対して検証されます。

📊 可観測性

--verbose はリクエストごとの相関IDを持つ構造化JSON-linesログを出力します。共有サーバーは Prometheus /metrics も公開します。

🌐 共有サーバーモード

serve は多数のエージェント向けに1つのStreamable HTTPサーバーを実行します。接続ごとの認証でトークン/ヘッダーをプロファイルにマッピングします。

🏷️ 衝突安全

サーバー間で名前が重複するツールは自動的にプレフィックスが付き(github__read_file)、それ以外は素の名前のままです。


動作の仕組み

アーキテクチャ

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" --> DENY

block は常に優先されます。パターンはグロブ(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 ハンドシェイクで測定した結果:

プロファイル

公開ツール数

tools/list ペイロード

~トークン

reviewer

5

2,926 文字

~732

implementer

26

15,762 文字

~3,941

noTools

0

2 文字

~1

トークン数は ~4文字/トークンのヒューリスティックを使用しています。実際の節約は、エージェントが毎ターンコンテキストに再読み込みするスキーマの総量にあります。

各プロファイルが実際に受け取ったツール:

  • reviewer (読み取り専用、GitHubブロック): read_file, list_directory, directory_tree, search_files, get_file_info

  • implementer (拒否リスト、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_file

  • noTools (すべてブロック): (なし)

注目すべき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 tokens

3. 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: reviewer

4. 実行する

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 namespace

http (Streamable HTTP):

github:
  type: http
  url: https://api.github.com/mcp
  headers:
    Authorization: "${GITHUB_TOKEN}"
  prefix: gh__          # optional

profiles — 名前付きツールビュー

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 exposed

http — 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)

エンドポイント:

パス

用途

/mcp

Streamable HTTP MCPエンドポイント(接続ごとにセッション)

/health

死活監視 — プロセスが起動していれば常に 200

/ready

準備完了 — すべてのプロファイルの上流サーバーが接続されている場合のみ 200

/metrics

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 — 環境変数テンプレート。

Maintenance

ActivityStale
ResponsivenessNo recent community issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Centralized MCP control plane that proxies multiple upstream MCP servers with tool namespacing, filtering, policy enforcement, audit logging, and health checks.
    6 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides 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
  • A
    license
    Not graded
    quality
    C
    maintenance
    Fronts 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