mcp-tool-proxy
mcp-proxy
A lightweight proxy that consolidates multiple MCP servers behind a single endpoint. Each child MCP server is exposed as one meta-tool — the agent sees N tools instead of hundreds, while retaining full access to every sub-tool.
The Problem
MCP clients like Claude Desktop, Antigravity, and others enforce a tool limit (commonly 100). A single MCP server like @digitalocean/mcp alone registers 190+ tools. Add Playwright, Serena, or any others and you quickly exceed the limit — making it impossible to use multiple MCP servers together.
The Solution
mcp-proxy sits between the agent and your MCP servers. It spawns each child MCP, discovers their tools, then exposes one meta-tool per server with tool_name and tool_input as parameters. The agent calls the meta-tool, and the proxy transparently routes the request to the correct child MCP.
Agent mcp-proxy Child MCP Servers
────── ───────── ─────────────────
"playwright" ──────► route ──────────────► @playwright/mcp (23 tools)
"serena" ──────► route ──────────────► serena (28 tools)
"digitalocean"──────► route ──────────────► @digitalocean/mcp (193 tools)
Agent sees: 3 tools Reality: 244 tools
Quick Start
1. Install
Via npx (no local clone needed):
npx mcp-tool-proxy
Or clone and install for local development:
git clone https://github.com/sevket/mcp-proxy.git
cd mcp-proxy
npm install
2. Configure your MCP servers
Copy the example config and add your MCP servers:
cp proxy-config.example.json proxy-config.json
Edit proxy-config.json with your actual MCP servers. The format is identical to Claude Desktop's claude_desktop_config.json:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp"],
"env": {}
},
"digitalocean": {
"command": "npx",
"args": ["-y", "@digitalocean/mcp", "--services", "apps,droplets,databases"],
"env": {
"DIGITALOCEAN_API_TOKEN": "your_token_here"
}
}
}
}
3. Point your MCP client to the proxy
Instead of registering each MCP server individually, register only the proxy. Here is an example for Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"mcp-proxy": {
"command": "npx",
"args": ["-y", "mcp-tool-proxy"],
"env": {}
}
}
}
(Or "command": "node", "args": ["/absolute/path/to/mcp-proxy/index.js"] for a local clone.)
Restart your MCP client — you should see one tool per child MCP server.
How It Works
- Startup — The proxy reads
proxy-config.jsonand spawns each child MCP server as a subprocess using stdio transport. - Discovery — It calls
tools/liston each child to collect their available tools. - Meta-tool generation — For each child MCP, it creates a single meta-tool. The meta-tool's
descriptionlists all available sub-tools so the agent knows what's available. Thetool_nameparameter is anenumof all sub-tool names for precise routing. - Request routing — When the agent calls a meta-tool, the proxy extracts
tool_nameandtool_input, forwards the request to the correct child MCP, and returns the response as-is.
Meta-tool Schema
Each meta-tool accepts two parameters:
| Parameter | Type | Description |
|---|---|---|
tool_name |
string |
Name of the sub-tool to call (enum) |
tool_input |
object |
Arguments to pass to the sub-tool |
Adding a New MCP Server
Add a new entry to proxy-config.json:
{
"mcpServers": {
"existing-mcp": { "..." : "..." },
"new-mcp": {
"command": "npx",
"args": ["-y", "@some/mcp-package"]
}
}
}
The proxy watches proxy-config.json and picks up changes live — no restart needed. The new server connects and appears as a meta-tool within a second or so of saving the file.
Troubleshooting
| Issue | Solution |
|---|---|
| Child MCP fails to connect | Check the command path and env.PATH in proxy-config.json. Use absolute paths if needed. |
| Tool not found | Check proxy logs in stderr. Verify the tool exists in the child MCP. |
| Agent doesn't see updated tools | Config changes hot-reload automatically; if it still doesn't show up, check stderr for a "config reload failed" line (invalid JSON keeps the previous config running). |
| Partial startup | The proxy continues even if some children fail — check logs for errors. |
Security Model
proxy-config.json entries are spawned directly (command + args, with env merged into the child's environment) — the proxy does not sandbox or validate what a command actually does. Treat this file like a shell script you'd run yourself: only put servers in it that you configured, and never point it at a proxy-config.json you downloaded or received from someone else without reading it first. There's no per-server permission model beyond toolAllowlist (which filters which tool names are reachable, not what those tools are allowed to do).
Requirements
- Node.js 18+
- npm
Contributing
See CONTRIBUTING.md.
License
MIT