mcp-proxy
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 |
|
📉 Ahorro de tokens | Un |
👥 Una configuración, muchos agentes | Reviewer, implementer y el bot de CI comparten el mismo bloque |
🧩 Agregación multi-servidor | Combina varios upstreams (stdio + HTTP) tras un único endpoint MCP. |
🔐 Los secretos no entran en el repositorio | Placeholders |
♻️ Resiliente | Reconexión automática con backoff exponencial; las actualizaciones en vivo de |
🛡️ Validación de argumentos | Los argumentos de |
📊 Observable |
|
🌐 Modo servidor compartido |
|
🏷️ A prueba de colisiones | Las herramientas que comparten nombre entre servidores reciben automáticamente un prefijo ( |
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"| U3Cada 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_changedDecisió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" --> DENYblock 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 | ~tokens |
| 5 | 2,926 caracteres | ~732 |
| 26 | 15,762 caracteres | ~3,941 |
| 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_infoimplementer(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_filenoTools(todo bloqueado): (ninguna)
Dos detalles que vale la pena señalar:
Auto-prefijado por colisión —
read_fileexiste en ambos servidores, por lo que se convierte enfilesystem__read_fileygithub__read_file. Perowrite_file/edit_fileconservan sus nombres simples porque están bloqueadas enfilesystem, dejandogithubcomo única fuente.Una vista de perfil vacía es válida —
noTools(o cualquier perfil conblock: ["**"], 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 tokens3. 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: reviewer4. Ejecuta
node dist/cli/index.js --profile reviewer
# add --verbose for structured debug logging
node dist/cli/index.js --profile reviewer --verbosePrecedencia 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 namespacehttp (Streamable HTTP):
github:
type: http
url: https://api.github.com/mcp
headers:
Authorization: "${GITHUB_TOKEN}"
prefix: gh__ # optionalprofiles — 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 exposedhttp — 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 |
| Endpoint MCP Streamable HTTP (sesión por conexión) |
| Liveness — siempre |
| Readiness — |
| 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/callse comprueban contra elinputSchemadel 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
context/DESIGN.md — diseño completo, decisiones y compensaciones.
context/SETUP.md — configuración paso a paso para servidores stdio + HTTP reales.
context/ROADMAP.md — v1.0 publicada (HTTP downstream, perfiles por conexión, observabilidad, empaquetado).
mcp-proxy.yaml— ejemplo de configuración funcional..env.example— plantilla de variables de entorno.
This server cannot be installed
Maintenance
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
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Guarded MCP server for agent-readable business truth, provenance, readiness, and discovery.
MCP Gateway: wrap any MCP server with cold-start retries, uptime SLA, and per-execution MPP billing.
Remote MCP server exposing SMI Aware tools, resources, and skills over Streamable HTTP.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceSelf-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.16MIT
- AlicenseNot gradedqualityBmaintenanceCentralized MCP control plane that proxies multiple upstream MCP servers with tool namespacing, filtering, policy enforcement, audit logging, and health checks.16MIT
- AlicenseNot gradedqualityAmaintenanceAn 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
- AlicenseNot gradedqualityBmaintenanceEnables 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
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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