mcp-proxy
mcp-proxy
Ein härtender MCP-Proxy, der vor einem oder mehreren Upstream-MCP-Servern sitzt und nur die Tools offenlegt, die ein bestimmtes Profil sehen und aufrufen darf.
Eine Konfigurationsdatei beschreibt deine echten Server (GitHub, Dateisystem, Slack, …) und
die Profile (Reviewer, Implementierer, CI-Bot, …). Jeder Agent startet seine eigene Kopie des
Proxys mit --profile <name> – oder, im serve-Modus, bildet ein einzelner gemeinsamer HTTP-Server
jede Verbindung per Authentifizierung auf ein Profil ab – und erhält eine gefilterte, erzwungene
Sicht auf diese Server, was Kontext-Token spart und gefährliche Tool-Aufrufe von vornherein verhindert.
Warum mcp-proxy?
Offizielle MCP-Server legen alle ihrer Tools jedem Agenten offen. Der Client
ruft tools/list ab und injiziert das Schema jedes Tools bei jedem Turn in den Prompt,
was Kontext-Token verbrennt. Und ein Tool, das sichtbar ist, ist ein Tool, das aufgerufen
werden kann – es gibt keine harte Grenze.
mcp-proxy löst beide Probleme gleichzeitig:
Token-Ersparnis – ein Profil bewirbt nur die Tools, die du explizit erlaubst, sodass nur diese Schemas jemals in den Kontext des Agenten gelangen.
Harte Leitplanke – ein Tool, das nicht erlaubt ist, wird weder gelistet noch aufrufbar: selbst ein halluzinierter Aufruf wird zur Ausführungszeit abgewiesen, nicht nur im Menü versteckt.
Related MCP server: Mavryn
Vorteile
Vorteil | Wie es hilft |
🔒 Fail-closed-Leitplanke |
|
📉 Token-Ersparnis | Gefiltertes |
👥 Eine Konfiguration, viele Agenten | Reviewer, Implementierer und CI-Bot teilen sich denselben |
🧩 Multi-Server-Aggregation | Mehrere Upstreams (stdio + HTTP) hinter einem einzigen MCP-Endpunkt zusammenführen. |
🔐 Geheimnisse bleiben außerhalb des Repos |
|
♻️ Resilient | Automatische Wiederverbindung mit exponentiellem Backoff; Live- |
🛡️ Argumentvalidierung |
|
📊 Beobachtbar |
|
🌐 Gemeinsamer Server-Modus |
|
🏷️ Kollisionssicher | Tools mit demselben Namen über mehrere Server werden automatisch mit Präfix versehen ( |
So funktioniert es
Architektur
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"| U3Jeder Agent startet den Proxy als Kindprozess über stdio. Der Proxy verbindet sich mit
jedem im gewählten Profil aufgeführten Upstream, ruft jeweils tools/list ab, wendet
die Allow/Block-Regeln des Profils an und legt nur die überlebenden Tools erneut offen.
Anfragefluss
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_changedFilterentscheidung
Ein Tool ist nur erlaubt, wenn es diese Präzedenzkette übersteht:
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 gewinnt immer. Muster sind Glob-Muster (read_*, {get,list}_*) oder Regexes
(/.*delete.*/i). Ein Server, der in einem Profil nicht aufgeführt ist, legt keines seiner Tools offen.
Beispiel: drei Profile, live gemessen
Derselbe Proxy, gesteuert durch drei Profile, ausgeführt gegen den echten
@modelcontextprotocol/server-filesystem-Upstream (14 Tools). Eine zweite
Filesystem-Instanz vertrat den HTTP-GitHub-Server, damit die Demo kein Token benötigt –
die Filterung pro Server verhält sich für jeden Upstream identisch.
# 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: ["**"] }Gemessen über einen Live-tools/list-Handshake:
Profil | Offengelegte Tools |
| ~Tokens |
| 5 | 2.926 Zeichen | ~732 |
| 26 | 15.762 Zeichen | ~3.941 |
| 0 | 2 Zeichen | ~1 |
Tokens verwenden eine ~4-Zeichen-pro-Token-Heuristik; die tatsächliche Ersparnis ist die Schema-Oberfläche, die der Agent bei jedem Turn neu in den Kontext lädt.
Tools, die jedes Profil tatsächlich erhalten hat:
reviewer(schreibgeschützt, GitHub blockiert):read_file,list_directory,directory_tree,search_files,get_file_infoimplementer(Deny-Liste, GitHub erlaubt):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(alles blockiert): (keine)
Zwei Details, die erwähnenswert sind:
Kollisions-Autoprefix –
read_fileexistiert auf beiden Servern, wird also zufilesystem__read_fileundgithub__read_file. Aberwrite_file/edit_filebehalten ihre einfachen Namen, weil sie auffilesystemblockiert sind, sodassgithubdie einzige Quelle bleibt.Eine leere Profilansicht ist gültig –
noTools(oder jedes Profil mitblock: ["**"], oder ein einfach weggelassener Server) legt null Tools offen; der Agent verbindet sich weiterhin, hat aber nichts aufzurufen.
Schnellstart
1. Installieren und bauen
npm install
npm run build # compiles TypeScript to dist/2. Geheimnisse in .env ablegen (niemals in der Konfiguration)
cp .env.example .env # then fill in your tokens3. mcp-proxy.yaml schreiben
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. Ausführen
node dist/cli/index.js --profile reviewer
# add --verbose for structured debug logging
node dist/cli/index.js --profile reviewer --verboseProfil-Präzedenz: --profile > MCP_PROFILE > defaultProfile.
Konfigurationsreferenz
servers – Upstream-MCP-Server
stdio (als Kindprozess gestartet):
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 – benannte Tool-Ansichten
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 downstream (serve-Modus)
Optionaler Top-Level-Block, der den Proxy in einen gemeinsamen HTTP-Server verwandelt, der viele Agenten aus einem Prozess bedient. Siehe Gemeinsamer Server (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)Wenn tokens gesetzt ist, wird der um das Schema bereinigte Header-Wert in der Map nachgeschlagen.
Ohne tokens wird der bereinigte Header-Wert direkt als Profilname verwendet.
Ein fehlender/unbekannter Selektor fällt auf defaultProfile zurück und wird dann abgewiesen
(401/403), wenn keines zutrifft.
Geheimnisse
${VAR}-Platzhalter werden beim Laden aus der Umgebung (oder .env) aufgelöst.
Das YAML enthält nur den Variablen-Namen, ist also sicher zu committen. Eine fehlende Variable
lässt den Loader sofort fehlschlagen – keine stillen leeren Header.
An deinen Agenten anbinden
Der Proxy ist ein MCP-Server über stdio. Richte deinen Agenten auf den Proxy-Einstiegspunkt statt auf den echten Server aus und übergib das Profil-Flag.
// .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"]
}
}
}Jeder Agent erhält seinen eigenen stdio-Prozess, sodass Profile pro Agent vollständig isoliert sind und Anmeldedaten niemals eine Prozessgrenze überschreiten.
Gemeinsamer Server (HTTP)
Für eine zentrale Bereitstellung führe serve aus, um einen Streamable-HTTP-Server bereitzustellen, den
viele Agenten teilen. Jede Verbindung wird über ihren Auth-Header auf ein Profil abgebildet:
node dist/cli/index.js serve --config mcp-proxy.yaml
# options: --host, --port (override http.host/http.port)Endpunkte:
Pfad | Zweck |
| Streamable-HTTP-MCP-Endpunkt (Sitzung pro Verbindung) |
| Liveness – immer |
| Readiness – |
| Prometheus-Textmetriken (gelistete/aufgerufene/blockierte Tools, Latenz, Upstream-Zustand) |
Die Profilauflösung pro Verbindung ist fail-closed: Eine Verbindung ohne verwendbaren
Selektor wird abgewiesen (401), sofern nicht http.auth.defaultProfile gesetzt ist, und ein Selektor,
der auf ein unbekanntes Profil abgebildet wird, wird abgewiesen (403).
Client-Konfiguration für eine gemeinsame Bereitstellung (jeder Streamable-HTTP-fähige Client):
// .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}" }
}
}
}Der Copilot-Coding-Agent liest die .mcp.json des Repos; für andere Agenten verwende
deren natives MCP-Server-Feld (siehe context/AGENT-SETUP.md
und context/VENDOR-AGENTS.md).
Beobachtbarkeit
Führe mit --verbose aus, um strukturierte JSON-Lines-Logs auf stderr auszugeben (wodurch der
MCP-stdio-Kanal auf stdout sauber bleibt):
{"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"}Jeder Eintrag von tools/list und tools/call trägt die correlationId der MCP-Anfrage,
sodass eine einzelne Anfrage über den Proxy und seine Upstreams hinweg verfolgt werden kann.
Um die Kontextkosten eines Profils zu sehen, vergleiche die Tool-Anzahl und die tools/list-
Nutzlastgröße über Profile hinweg (siehe das gemessene Beispiel
weiter oben): weniger beworbene Tools bedeutet weniger Schemas, die bei jedem Turn in den Prompt injiziert werden.
Im serve-Modus scrappe /metrics für Prometheus-Counter, -Gauges und -Histogramme:
mcp_proxy_tools_listed_total, mcp_proxy_tools_called_total,
mcp_proxy_tools_blocked_total, mcp_proxy_tool_call_duration_seconds und
mcp_proxy_upstream_connections (alle mit den Labels profile/server/tool).
Resilienz
Automatische Wiederverbindung – wenn ein Upstream (insbesondere ein gestarteter stdio-Prozess) stirbt, verbindet sich der Proxy mit exponentiellem Backoff neu (500 ms → 15 s Obergrenze, unbegrenzte Wiederholungen).
Live-Tool-Updates – wenn ein Upstream
notifications/tools/list_changedausgibt, ruft der Proxy erneut ab, filtert neu und gibt die Änderung downstream weiter, sodass Agenten immer eine korrekte Tool-Liste sehen.Argumentvalidierung –
tools/call-Argumente werden vor der Weiterleitung gegen dasinputSchemades Upstreams geprüft; ungültige Aufrufe werden lokal abgewiesen.
Entwicklung
npm run typecheck # tsc --noEmit
npm test # vitest (unit + integration + filesystem smoke)
npm run build # tsc → dist/Mehr
context/DESIGN.md – vollständiges Design, Entscheidungen und Kompromisse.
context/SETUP.md – Schritt-für-Schritt-Einrichtung für echte stdio- und HTTP-Server.
context/ROADMAP.md – v1.0 ausgeliefert (HTTP-Downstream, Profile pro Verbindung, Beobachtbarkeit, Paketierung).
mcp-proxy.yaml– funktionierende Beispielkonfiguration..env.example– Vorlage für Umgebungsvariablen.
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