Skip to main content
Glama

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

Преимущества

Преимущество

Как помогает

🔒 Защита fail-closed

Правило block имеет приоритет; неизвестные инструменты по умолчанию запрещены. Видимость и возможность вызова синхронизированы.

📉 Экономия токенов

Отфильтрованный tools/list означает более короткие промпты и более дешёвые, сфокусированные сессии.

👥 Один конфиг, много агентов

Reviewer, implementer и CI-бот используют один и тот же блок servers, но получают разные профили через --profile.

🧩 Агрегация нескольких серверов

Объединение нескольких вышестоящих серверов (stdio + HTTP) за одной MCP-конечной точкой.

🔐 Секреты не попадают в репозиторий

Плейсхолдеры ${VAR} + .env; загрузчик быстро завершается с ошибкой при отсутствующей переменной.

♻️ Устойчивость

Автопереподключение с экспоненциальной задержкой; живые обновления tools/list_changed повторно фильтруются и передаются дальше по цепочке.

🛡️ Проверка аргументов

Аргументы tools/call проверяются по inputSchema вышестоящего сервера перед пересылкой.

📊 Наблюдаемость

--verbose выводит структурированные журналы в формате JSON-lines с идентификаторами корреляции на каждый запрос; общий сервер также предоставляет Prometheus /metrics.

🌐 Режим общего сервера

serve запускает один 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 всегда побеждает. Шаблоны — это glob-выражения (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:

Профиль

Инструментов доступно

Полезная нагрузка 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 (всё заблокировано): (нет)

Два момента, на которые стоит обратить внимание:

  • Автопрефикс при коллизиях — 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)

Необязательный блок верхнего уровня, который превращает прокси в общий 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 хранится только имя переменной, поэтому его безопасно коммитить. Отсутствующая переменная заставляет загрузчик быстро завершаться с ошибкой — никаких молча пустых заголовков.


Подключите его к своему агенту

Прокси является MCP-сервером через stdio. Направьте своего агента на точку входа прокси вместо реального сервера, передав флаг профиля.

// .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)

Конечные точки:

Путь

Назначение

/mcp

Конечная точка MCP Streamable HTTP (сессия на каждое подключение)

/health

Живучесть — всегда 200, как только процесс запущен

/ready

Готовность — 200, только когда все вышестоящие серверы каждого профиля подключены

/metrics

Текстовые метрики Prometheus (перечисленные/вызванные/заблокированные инструменты, задержка, состояние вышестоящих серверов)

Разрешение профиля на каждое подключение работает по принципу fail-closed: подключение без пригодного селектора отклоняется (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, чтобы выводить структурированные журналы 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 несёт correlationId MCP-запроса, поэтому отдельный запрос можно проследить через прокси и его вышестоящие серверы.

Чтобы оценить стоимость профиля в контексте, сравните количество инструментов и размер полезной нагрузки 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-процесс) умирает, прокси переподключается с экспоненциальной задержкой (500 мс → максимум 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