Self-hosted MCP Code Mode gateway — aggregate multiple MCP servers behind 2 tools with V8 isolate execution
mcpx
Collapse multiple MCP servers into 2 tools. Your agent writes code to call them instead of loading 100+ tool schemas into context.
mcpx sits between your agent and your MCP servers. Instead of registering every tool (which eats context tokens), it exposes just search and execute. The agent discovers tools via search, writes JavaScript to call them, and the code runs in a sandboxed V8 isolate.
Without mcpx: agent ← 120 tool schemas (~84,000 tokens)
With mcpx: agent ← 2 tools (~1,000 tokens) → writes JS → mcpx executes → calls real tools
Why
| Approach | Context tokens | | ---------------------------- | -------------- | | 120 tools (full schemas) | ~84,000 | | 120 tools (minimal schemas) | ~17,000 | | mcpx Code Mode (2 tools) | ~1,000 |
Inspired by Cloudflare's Code Mode pattern — self-hosted, no Cloudflare dependency.
Works with
Claude Code | Cursor | Codex | Amp | OpenCode |
Supports stdio and HTTP (Streamable HTTP) — works with any MCP-compatible agent.
Solo developer
Run mcpx locally as a stdio subprocess. No server, no Docker, no Kubernetes.
1. Create config
# Import your existing MCP servers from .mcp.json
bunx mcpx-tools init
# Or start with a blank template
bunx mcpx-tools init --empty
Edit mcpx.json to add your backends:
{
"port": 3100,
"backends": {
"my-server": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "some-mcp-server"],
"env": { "API_KEY": "${MY_API_KEY}" }
}
}
}
2. Connect your agent
Claude Code
claude mcp add mcpx -- bunx mcpx-tools stdio mcpx.json
Or add to .mcp.json (checked into git):
{
"mcpServers": {
"mcpx": {
"command": "bunx",
"args": ["mcpx-tools", "stdio", "mcpx.json"]
}
}
}
Cursor
Add to ~/.cursor/mcp.json or .cursor/mcp.json:
{
"mcpServers": {
"mcpx": {
"command": "bunx",
"args": ["mcpx-tools", "stdio", "mcpx.json"]
}
}
}
Codex
Add to ~/.codex/config.toml:
[mcp_servers.mcpx]
command = "bunx"
args = ["mcpx-tools", "stdio", "mcpx.json"]
Amp / OpenCode
Any agent that supports stdio MCP:
bunx mcpx-tools stdio mcpx.json
Point your agent's MCP config at this command.
Team deployment
Run mcpx as an HTTP server — one endpoint for your whole team. Agents connect over the network. Deploy on Kubernetes, Docker, or a VPS.
1. Deploy
Docker:
docker compose up -d
Kubernetes (Helm):
helm install mcpx ./helm/mcpx \
--namespace mcpx --create-namespace \
--set existingSecret=mcpx-secrets \
--set ingress.enabled=true \
--set ingress.host=mcp.yourcompany.com
Standalone:
bunx mcpx-tools mcpx.json
# → http://localhost:3100/mcp
2. Connect your agent
All agents point at the same HTTP endpoint. Auth is handled by a bearer token.
Claude Code
claude mcp add-json mcpx '{"type":"http","url":"https://mcp.yourcompany.com/mcp","headers":{"Authorization":"Bearer YOUR_TOKEN"}}'
Or in .mcp.json:
{
"mcpServers": {
"mcpx": {
"type": "http",
"url": "https://mcp.yourcompany.com/mcp",
"headers": { "Authorization": "Bearer ${MCPX_AUTH_TOKEN}" }
}
}
}
Cursor
{
"mcpServers": {
"mcpx": {
"url": "https://mcp.yourcompany.com/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
Codex
[mcp_servers.mcpx]
url = "https://mcp.yourcompany.com/mcp"
bearer_token_env_var = "MCPX_AUTH_TOKEN"
Any agent (HTTP)
Point at https://mcp.yourcompany.com/mcp with header Authorization: Bearer YOUR_TOKEN.
Team benefits
- One config, all tools — Grafana, Plane, GitHub, K8s behind one endpoint
- Credentials stay on the server — devs never see API keys
- RBAC via gateway — pair with Pomerium or agentgateway for per-team tool access
- 50 tools → 2 tools — same context savings for every team member
Configuration
mcpx.json:
{
"port": 3100,
"authToken": "${MCPX_AUTH_TOKEN}",
"backends": {
"my-server": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "some-mcp-server"],
"env": {
"API_KEY": "${MY_API_KEY}"
}
}
}
}
Environment variables in ${VAR} syntax are interpolated from process.env.
How it works
- On startup: mcpx connects to all configured backend MCP servers (via stdio subprocesses or HTTP)
- Tool discovery: collects all tools from all backends, generates TypeScript type definitions
- Exposes 2 tools via MCP Streamable HTTP or stdio:
search— fuzzy search across all backend tools, returns matching type definitions + paramsexecute— runs JavaScript in a V8 isolate with access to all backend tools as functions
- Execution: code runs in secure-exec with deny-by-default permissions (no fs, no network, no child process). Tool calls are intercepted and routed to the real backend MCP servers.
Architecture
┌─────────────────────────────────────────┐
│ mcpx │
│ │
│ MCP server (stdio or HTTP) │
│ ├── search → schema lookup │
│ └── execute → secure-exec V8 │
│ ↓ │
│ V8 isolate (3.4MB, 16ms boot) │
│ deny-by-default permissions │
│ ↓ │
│ routes tool calls to backends: │
│ ├── grafana (stdio subprocess) │
│ ├── plane (stdio subprocess) │
│ └── github (stdio subprocess) │
└─────────────────────────────────────────┘
Security
- Code runs in V8 isolates (same isolation as Chromium browser tabs)
- Deny-by-default: no filesystem, no network, no child process, no env access
- Tool calls are the only way code can interact with the outside world
- Each execution gets a fresh isolate (destroyed after completion)
- Backend credentials never enter the isolate — injected by the gateway process
Stack
- Bun — runtime
- Hono — HTTP framework (14KB)
- secure-exec — V8 isolate sandbox
- neverthrow — typed error handling
- @modelcontextprotocol/sdk — MCP protocol
License
Apache-2.0