Skip to main content
Glama

mcp-proxy

Un proxy MCP de endurecimiento que se sitúa delante de uno o más servidores MCP upstream y expone únicamente las herramientas que un perfil determinado puede ver y llamar.

Un único archivo de configuración describe tus servidores reales (GitHub, filesystem, Slack, …) y los perfiles (reviewer, implementer, ci-bot, …). Cada agente lanza su propia copia del proxy con --profile <name> — o, en modo serve, un único servidor HTTP compartido asigna cada conexión a un perfil mediante autenticación — y obtiene una vista filtrada y reforzada de esos servidores, ahorrando tokens de contexto y evitando llamadas peligrosas a herramientas por construcción.


¿Por qué mcp-proxy?

Los servidores MCP oficiales exponen todas sus herramientas a todos los agentes. El cliente obtiene tools/list e inyecta el esquema de cada herramienta en el prompt en cada turno, consumiendo tokens de contexto. Y una herramienta que es visible es una herramienta que puede llamarse — no hay un límite estricto.

mcp-proxy resuelve ambos problemas a la vez:

  • Ahorro de tokens — un perfil solo anuncia las herramientas que permites explícitamente, de modo que solo esos esquemas entran en el contexto del agente.

  • Barrera de protección estricta — una herramienta que no está permitida no está listada ni invocable: incluso una llamada alucinada se rechaza en tiempo de ejecución, no solo se oculta del menú.


Related MCP server: Mavryn

Beneficios

Beneficio

Cómo ayuda

🔒 Barrera de protección de cierre ante fallos

block gana; las herramientas desconocidas se deniegan por defecto. Visibilidad y capacidad de llamada se mantienen sincronizadas.

📉 Ahorro de tokens

Un tools/list filtrado significa prompts más pequeños y sesiones más baratas y enfocadas.

👥 Una configuración, muchos agentes

Reviewer, implementer y el bot de CI comparten el mismo bloque servers pero obtienen perfiles diferentes mediante --profile.

🧩 Agregación multi-servidor

Combina varios upstreams (stdio + HTTP) tras un único endpoint MCP.

🔐 Los secretos no entran en el repositorio

Placeholders ${VAR} + .env; el cargador falla rápido si falta una variable.

♻️ Resiliente

Reconexión automática con backoff exponencial; las actualizaciones en vivo de tools/list_changed se vuelven a filtrar y se propagan downstream.

🛡️ Validación de argumentos

Los argumentos de tools/call se validan contra el inputSchema del upstream antes de reenviarlos.

📊 Observable

--verbose emite registros estructurados en JSON-lines con ids de correlación por petición; el servidor compartido también expone /metrics de Prometheus.

🌐 Modo servidor compartido

serve ejecuta un único servidor Streamable HTTP para muchos agentes; la autenticación por conexión asigna tokens/cabeceras a perfiles.

🏷️ A prueba de colisiones

Las herramientas que comparten nombre entre servidores reciben automáticamente un prefijo (github__read_file), las demás conservan sus nombres simples.


Cómo funciona

Arquitectura

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

Cada agente lanza el proxy como proceso hijo a través de stdio. El proxy se conecta a cada upstream listado en el perfil seleccionado, obtiene cada tools/list, aplica las reglas allow/block del perfil y re-expone solo las herramientas que sobreviven.

Flujo de peticiones

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

Decisión de filtrado

Una herramienta solo se permite si sobrevive a esta cadena de precedencia:

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 siempre gana. Los patrones son globs (read_*, {get,list}_*) o expresiones regulares (/.*delete.*/i). Un servidor omitido de un perfil no expone ninguna de sus herramientas.


Ejemplo: tres perfiles, medidos en vivo

El mismo proxy dirigido a través de tres perfiles, ejecutado contra el upstream real @modelcontextprotocol/server-filesystem (14 herramientas). Una segunda instancia de filesystem hizo las veces del servidor HTTP de GitHub para que la demo no necesite token — el filtrado por servidor se comporta de forma idéntica para cualquier upstream.

# 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: ["**"] }

Medido durante un handshake real de tools/list:

Perfil

Herramientas expuestas

Carga útil de tools/list

~tokens

reviewer

5

2,926 caracteres

~732

implementer

26

15,762 caracteres

~3,941

noTools

0

2 caracteres

~1

Los tokens usan una heurística de ~4 caracteres/token; el ahorro real es la superficie de esquemas que el agente vuelve a cargar en el contexto en cada turno.

Herramientas que cada perfil recibió realmente:

  • reviewer (solo lectura, GitHub bloqueado): read_file, list_directory, directory_tree, search_files, get_file_info

  • implementer (lista de denegación, GitHub permitido): 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 (todo bloqueado): (ninguna)

Dos detalles que vale la pena señalar:

  • Auto-prefijado por colisiónread_file existe en ambos servidores, por lo que se convierte en filesystem__read_file y github__read_file. Pero write_file/edit_file conservan sus nombres simples porque están bloqueadas en filesystem, dejando github como única fuente.

  • Una vista de perfil vacía es válidanoTools (o cualquier perfil con block: ["**"], o un servidor simplemente omitido) expone cero herramientas; el agente sigue conectándose, solo que no tiene nada que llamar.


Inicio rápido

1. Instala y compila

npm install
npm run build          # compiles TypeScript to dist/

2. Pon los secretos en .env (nunca en la configuración)

cp .env.example .env   # then fill in your tokens

3. Escribe 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. Ejecuta

node dist/cli/index.js --profile reviewer
# add --verbose for structured debug logging
node dist/cli/index.js --profile reviewer --verbose

Precedencia de perfiles: --profile > MCP_PROFILE > defaultProfile.


Referencia de configuración

servers — servidores MCP upstream

stdio (lanzado como proceso hijo):

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 — vistas de herramientas con nombre

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 — downstream Streamable HTTP (modo serve)

Bloque opcional de nivel superior que convierte el proxy en un servidor HTTP compartido que atiende a muchos agentes desde un único proceso. Consulta Servidor compartido (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)

Cuando tokens está definido, el valor de la cabecera sin esquema se busca en el mapa. Sin tokens, el valor de cabecera sin esquema se usa directamente como nombre de perfil. Un selector ausente/desconocido recurre a defaultProfile y luego se rechaza (401/403) si ninguno aplica.

Secretos

Los placeholders ${VAR} se resuelven desde el entorno (o .env) en tiempo de carga. El YAML solo contiene el nombre de la variable, por lo que es seguro hacer commit de él. Una variable que falta hace que el cargador falle rápido — sin cabeceras silenciosamente vacías.


Conéctalo a tu agente

El proxy es un servidor MCP sobre stdio. Apunta tu agente al punto de entrada del proxy en lugar del servidor real, pasando el flag de perfil.

// .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"]
    }
  }
}

Cada agente obtiene su propio proceso stdio, por lo que los perfiles están completamente aislados por agente y las credenciales nunca cruzan un límite de proceso.

Servidor compartido (HTTP)

Para un despliegue centralizado, ejecuta serve para exponer un servidor Streamable HTTP que comparten muchos agentes. Cada conexión se asigna a un perfil a partir de su cabecera de autenticación:

node dist/cli/index.js serve --config mcp-proxy.yaml
# options: --host, --port (override http.host/http.port)

Endpoints:

Ruta

Propósito

/mcp

Endpoint MCP Streamable HTTP (sesión por conexión)

/health

Liveness — siempre 200 una vez que el proceso está activo

/ready

Readiness — 200 solo cuando los upstreams de cada perfil están conectados

/metrics

Métricas de texto de Prometheus (herramientas listadas/llamadas/bloqueadas, latencia, estado de upstreams)

La resolución de perfil por conexión es fail-closed: una conexión sin un selector utilizable se rechaza (401) a menos que http.auth.defaultProfile esté definido, y un selector que mapea a un perfil desconocido se rechaza (403).

Configuración de cliente para un despliegue compartido (cualquier cliente compatible con 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}" }
    }
  }
}

El agente de codificación Copilot lee el .mcp.json del repositorio; para otros agentes usa su campo nativo de servidor MCP (consulta context/AGENT-SETUP.md y context/VENDOR-AGENTS.md).


Observabilidad

Ejecuta con --verbose para emitir registros estructurados en JSON-lines a stderr (manteniendo el canal MCP stdio en stdout limpio):

{"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"}

Cada entrada de tools/list y tools/call lleva el correlationId de la petición MCP, de modo que una única petición se puede rastrear a través del proxy y sus upstreams.

Para ver el coste de contexto de un perfil, compara el número de herramientas y el tamaño de la carga útil de tools/list entre perfiles (consulta el ejemplo medido anterior): menos herramientas anunciadas significa menos esquemas inyectados en el prompt en cada turno.

En modo serve, scrapea /metrics para contadores, gauges e histogramas de Prometheus: mcp_proxy_tools_listed_total, mcp_proxy_tools_called_total, mcp_proxy_tools_blocked_total, mcp_proxy_tool_call_duration_seconds, y mcp_proxy_upstream_connections (todos etiquetados por profile/server/tool).


Resiliencia

  • Reconexión automática — si un upstream (especialmente un proceso stdio lanzado) muere, el proxy se reconecta con backoff exponencial (500ms → límite de 15s, reintentos infinitos).

  • Actualizaciones de herramientas en vivo — cuando un upstream emite notifications/tools/list_changed, el proxy vuelve a obtener, vuelve a filtrar y reenvía el cambio downstream, de modo que los agentes siempre ven una lista de herramientas precisa.

  • Validación de argumentos — los argumentos de tools/call se comprueban contra el inputSchema del upstream antes de reenviarlos; las llamadas inválidas se rechazan localmente.


Desarrollo

npm run typecheck    # tsc --noEmit
npm test             # vitest (unit + integration + filesystem smoke)
npm run build        # tsc → dist/

Más

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Self-hosted MCP proxy and aggregation platform. Register multiple upstream MCP servers and expose them through a single unified endpoint with namespace routing, multi-transport support (HTTP/SSE, stdio, OpenAPI→MCP), per-tool overrides, and a web admin UI.
    16
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Centralized MCP control plane that proxies multiple upstream MCP servers with tool namespacing, filtering, policy enforcement, audit logging, and health checks.
    16
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    An authorizing reverse proxy for MCP servers that enforces per-call policy rules on tool arguments with audit logging, dry-run, and rate limiting.
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables serving multiple MCP toolkits behind one server with capability-based access control, so different callers see and can call only the tools they are authorized for, over stdio or streamable HTTP with bearer-token auth.
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DawidNowak/mcp-proxy'

If you have feedback or need assistance with the MCP directory API, please join our Discord server