mcp-proxy
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-proxyList only the tools allowed for my reviewer profile."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-proxy
A hardening MCP proxy that sits in front of one or more upstream MCP servers and exposes only the tools a given profile is allowed to see and call.
One config file describes your real servers (GitHub, filesystem, Slack, …) and the
profiles (reviewer, implementer, ci-bot, …). Each agent launches its own copy of the
proxy with --profile <name> — or, in serve mode, a single shared HTTP server
maps each connection to a profile via auth — and gets a filtered, enforced
view of those servers, saving context tokens and preventing dangerous tool calls by
construction.
Why mcp-proxy?
Official MCP servers expose all of their tools to every agent. The client
fetches tools/list and injects every tool's schema into the prompt on every
turn, burning context tokens. And a tool that's visible is a tool that can be
called — there is no hard boundary.
mcp-proxy fixes both problems at once:
Token savings — a profile only advertises the tools you explicitly allow, so only those schemas ever enter the agent's context.
Hard guardrail — a tool that isn't allowed is neither listed nor callable: even a hallucinated call is rejected at execution time, not just hidden from the menu.
Related MCP server: Mavryn
Benefits
Benefit | How it helps |
🔒 Fail-closed guardrail |
|
📉 Token savings | Filtered |
👥 One config, many agents | Reviewer, implementer, and CI bot share the same |
🧩 Multi-server aggregation | Merge several upstreams (stdio + HTTP) behind a single MCP endpoint. |
🔐 Secrets stay out of the repo |
|
♻️ Resilient | Auto-reconnect with exponential backoff; live |
🛡️ Argument validation |
|
📊 Observable |
|
🌐 Shared server mode |
|
🏷️ Collision-safe | Tools that share a name across servers are auto-prefixed ( |
How it works
Architecture
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"| U3Each agent spawns the proxy as a child process over stdio. The proxy connects to
every upstream listed in the selected profile, fetches each tools/list, applies
the profile's allow/block rules, and re-exposes only the surviving tools.
Request flow
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_changedFilter decision
A tool is allowed only if it survives this precedence chain:
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 always wins. Patterns are globs (read_*, {get,list}_*) or regexes
(/.*delete.*/i). A server omitted from a profile exposes none of its tools.
Example: three profiles, measured live
The same proxy driven through three profiles, run against the real
@modelcontextprotocol/server-filesystem upstream (14 tools). A second
filesystem instance stood in for the HTTP GitHub server so the demo needs no
token — per-server filtering behaves identically for any 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: ["**"] }Measured over a live tools/list handshake:
Profile | Tools exposed |
| ~tokens |
| 5 | 2,926 chars | ~732 |
| 26 | 15,762 chars | ~3,941 |
| 0 | 2 chars | ~1 |
Tokens use a ~4 chars/token heuristic; the real saving is the schema surface the agent re-loads into context on every turn.
Tools each profile actually received:
reviewer(read-only, GitHub blocked):read_file,list_directory,directory_tree,search_files,get_file_infoimplementer(deny-list, GitHub allowed):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(everything blocked): (none)
Two details worth noting:
Collision auto-prefixing —
read_fileexists on both servers, so it becomesfilesystem__read_fileandgithub__read_file. Butwrite_file/edit_filekeep their bare names because they're blocked onfilesystem, leavinggithubas the only source.An empty profile view is valid —
noTools(or any profile withblock: ["**"], or a server simply omitted) exposes zero tools; the agent still connects, it just has nothing to call.
Quick start
1. Install and build
npm install
npm run build # compiles TypeScript to dist/2. Put secrets in .env (never in the config)
cp .env.example .env # then fill in your tokens3. Write 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. Run
node dist/cli/index.js --profile reviewer
# add --verbose for structured debug logging
node dist/cli/index.js --profile reviewer --verboseProfile precedence: --profile > MCP_PROFILE > defaultProfile.
Configuration reference
servers — upstream MCP servers
stdio (spawned as a child process):
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 — named tool views
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 mode)
Optional top-level block that turns the proxy into a shared HTTP server serving many agents from one process. See Shared 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)When tokens is set, the scheme-stripped header value is looked up in the map.
Without tokens, the stripped header value is used directly as the profile name.
A missing/unknown selector falls back to defaultProfile, then is rejected
(401/403) if none applies.
Secrets
${VAR} placeholders are resolved from the environment (or .env) at load time.
The YAML holds only the variable name, so it's safe to commit. A missing variable
makes the loader fail fast — no silently-empty headers.
Wire it to your agent
The proxy is an MCP server over stdio. Point your agent at the proxy entrypoint instead of the real server, passing the profile 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"]
}
}
}Each agent gets its own stdio process, so profiles are fully isolated per agent and credentials never cross a process boundary.
Shared server (HTTP)
For a central deployment, run serve to expose one Streamable HTTP server that
many agents share. Each connection is mapped to a profile from its auth header:
node dist/cli/index.js serve --config mcp-proxy.yaml
# options: --host, --port (override http.host/http.port)Endpoints:
Path | Purpose |
| Streamable HTTP MCP endpoint (session-per-connection) |
| Liveness — always |
| Readiness — |
| Prometheus text metrics (tools listed/called/blocked, latency, upstream state) |
Per-connection profile resolution is fail-closed: a connection with no usable
selector is rejected (401) unless http.auth.defaultProfile is set, and a selector
that maps to an unknown profile is rejected (403).
Client config for a shared deployment (any Streamable-HTTP-capable 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}" }
}
}
}The Copilot coding agent reads the repository's .mcp.json; for other agents use
their native MCP server field (see context/AGENT-SETUP.md
and context/VENDOR-AGENTS.md).
Observability
Run with --verbose to emit structured JSON-lines logs to stderr (keeping the
MCP stdio channel on stdout clean):
{"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"}Every tools/list and tools/call entry carries the MCP request's correlationId,
so a single request can be traced across the proxy and its upstreams.
To see the context cost of a profile, compare the tool count and tools/list
payload size across profiles (see the measured example
above): fewer advertised tools means fewer schemas injected into the prompt each turn.
In serve mode, scrape /metrics for Prometheus counters, gauges, and histograms:
mcp_proxy_tools_listed_total, mcp_proxy_tools_called_total,
mcp_proxy_tools_blocked_total, mcp_proxy_tool_call_duration_seconds, and
mcp_proxy_upstream_connections (all labelled by profile/server/tool).
Resilience
Auto-reconnect — if an upstream (especially a spawned stdio process) dies, the proxy reconnects with exponential backoff (500ms → 15s cap, infinite retries).
Live tool updates — when an upstream emits
notifications/tools/list_changed, the proxy re-fetches, re-filters, and forwards the change downstream, so agents always see an accurate tool list.Argument validation —
tools/callarguments are checked against the upstream'sinputSchemabefore forwarding; invalid calls are rejected locally.
Development
npm run typecheck # tsc --noEmit
npm test # vitest (unit + integration + filesystem smoke)
npm run build # tsc → dist/More
context/DESIGN.md — full design, decisions, and tradeoffs.
context/SETUP.md — step-by-step setup for real stdio + HTTP servers.
context/ROADMAP.md — v1.0 shipped (HTTP downstream, per-connection profiles, observability, packaging).
mcp-proxy.yaml— working example config..env.example— environment variable template.
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