๐ MCPlex โ The MCP Smart Gateway
Semantic tool routing โข Security guardrails โข Real-time observability
Stop dumping 50k tokens of tool definitions into your LLM's context window.MCPlex intelligently routes only the tools your agent actually needs.
The Problem
Every developer building multi-agent AI systems with MCP hits the same wall:
| Pain Point | Impact |
|---|---|
| ๐ง Context Bloat | 20+ MCP servers = 50k+ tokens of tool definitions consuming your context window |
| ๐ No Security | No RBAC, no audit trails, tool poisoning vulnerabilities |
| ๐๏ธ Blind Operations | Can't track costs, latency, or debug wrong tool selection |
| ๐ Restart Required | Config changes require full restart in production |
| ๐ธ๏ธ NรM Complexity | Orchestrating dozens of servers is an integration nightmare |
The Solution
MCPlex is a single-binary Rust gateway that sits between your AI agent and MCP servers:
Your Agent โโโ MCPlex Gateway โโโ GitHub MCP (stdio โ persistent)
โ โโโ Slack MCP (stdio โ persistent)
โ โโโ Database MCP (HTTP)
โ โโโ Filesystem MCP (stdio โ persistent)
โผ
๐ง Smart Routing (70-90% token savings)
๐ RBAC + Audit Logs + API Key Auth
๐ Real-time Dashboard + Prometheus
๐ฆ Response Caching (auto-detect read-only)
๐ Multi-Tenant (API key โ role mapping)
๐ฅ Hot-reload Config
Transport Support
MCPlex supports both MCP transport types as a first-class citizen:
| Transport | Discovery | Runtime Calls | Connection Model |
|---|---|---|---|
| Stdio | โ Full MCP handshake | โ Multiplexed JSON-RPC | Persistent child process (long-lived) |
| Streamable HTTP | โ Full MCP handshake | โ Standard HTTP POST | Stateless (connection pooling) |
Stdio servers are spawned at startup and kept alive for the gateway's lifetime. The MCP handshake (initialize โ notifications/initialized) runs once, then all subsequent tools/call, resources/read, and prompts/get requests are multiplexed over the same stdin/stdout pipe using JSON-RPC ID correlation.
โก Quick Start
1. Install (Pre-built Binary)
Download the latest release from GitHub Releases:
# Linux / macOS
curl -LO https://github.com/ModernOps888/mcplex/releases/latest/download/mcplex-linux-x86_64
chmod +x mcplex-linux-x86_64
sudo mv mcplex-linux-x86_64 /usr/local/bin/mcplex
2. Build from Source
git clone https://github.com/modernops888/mcplex.git
cd mcplex
cargo build --release
3. Configure
cp mcplex.toml my-config.toml
# Edit my-config.toml with your MCP servers
Minimal config for stdio servers:
[gateway]
listen = "127.0.0.1:3100"
dashboard = "127.0.0.1:9090"
[router]
strategy = "semantic"
[[servers]]
name = "filesystem"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
[[servers]]
name = "memory"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-memory"]
4. Run
./target/release/mcplex --config my-config.toml
# Expected output:
# ๐ Spawning stdio server 'filesystem': npx ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
# ๐ค MCP handshake complete for 'filesystem'
# ๐ก Server 'filesystem': 11 tools, 0 resources, 0 prompts
# ๐ Spawning stdio server 'memory': npx ["-y", "@modelcontextprotocol/server-memory"]
# ๐ค MCP handshake complete for 'memory'
# ๐ก Server 'memory': 3 tools, 0 resources, 0 prompts
# โก MCPlex gateway listening on 127.0.0.1:3100
5. Connect Your Agent
Point your MCP client to http://127.0.0.1:3100/mcp and open the dashboard at http://127.0.0.1:9090.
๐ Running as a Service (Deployment)
For persistent environments, run MCPlex as a background service to ensure it starts automatically on boot and restarts if it crashes.
Linux (systemd)
Create a systemd unit file at /etc/systemd/system/mcplex.service:
[Unit]
Description=MCPlex Gateway
After=network.target
[Service]
Type=simple
User=your_user
WorkingDirectory=/path/to/mcplex
ExecStart=/usr/local/bin/mcplex --config /path/to/mcplex/mcplex.toml
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable mcplex
sudo systemctl start mcplex
sudo systemctl status mcplex
macOS (launchd)
Create a launchd plist file at ~/Library/LaunchAgents/com.modernops.mcplex.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.modernops.mcplex</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/mcplex</string>
<string>--config</string>
<string>/path/to/mcplex.toml</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/mcplex.log</string>
<key>StandardErrorPath</key>
<string>/tmp/mcplex-error.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
</dict>
</plist>
Load and start the service:
launchctl load ~/Library/LaunchAgents/com.modernops.mcplex.plist
launchctl start com.modernops.mcplex
Log Rotation
When running as a service, ensure you implement log rotation to prevent infinite log growth. For macOS, add an entry to /etc/newsyslog.conf; for Linux, use logrotate. MCPlex also has built-in audit log rotation configured via max_log_size_mb.
Service Troubleshooting
| Issue | Resolution |
|---|---|
| Service fails to start immediately | Check your configuration file syntax by running mcplex --check --config <path> manually. |
| Port already in use | Verify no other service is bound to the listen port. Change gateway.listen in your config. |
launchd permission errors |
Ensure the ProgramArguments path is absolute and executable by the user. |
| Server respawn loop | Check the StandardErrorPath log for fatal bootstrap errors or missing dependencies (e.g., Node.js for npx servers). |
๐ How to Connect Your Agent
MCPlex is a transparent MCP proxy โ any MCP client that supports Streamable HTTP can connect to it. Your agent talks to MCPlex as if it were a single MCP server, and MCPlex handles multiplexing, routing, and security behind the scenes.
Claude Code / Claude Desktop (Recommended โ stdio bridge)
Claude Code and Claude Desktop use stdio transport. MCPlex ships a cross-platform bridge (bridge.mjs) that translates stdio โ HTTP. Works on macOS, Windows, and Linux.
Add a .mcp.json to your project root (Claude Code auto-discovers it):
{
"mcpServers": {
"mcplex": {
"command": "node",
"args": ["/path/to/mcplex/bridge.mjs"],
"env": {
"MCPLEX_GATEWAY": "http://127.0.0.1:3100/mcp"
}
}
}
}
For Claude Desktop, add the same config to claude_desktop_config.json.
VS Code / GitHub Copilot (agent mode)
VS Code supports MCP servers natively. Add a .vscode/mcp.json to your workspace (or run MCP: Add Server from the Command Palette) pointing at the MCPlex bridge:
{
"servers": {
"mcplex": {
"type": "stdio",
"command": "node",
"args": ["/path/to/mcplex/bridge.mjs"],
"env": {
"MCPLEX_GATEWAY": "http://127.0.0.1:3100/mcp"
}
}
}
}
A ready-to-copy template lives at examples/vscode-mcp.json.
With the default meta-tool mode, Copilot's agent sees just 3 gateway tools (~200 tokens) instead of every tool definition from every connected server โ the same 70โ90% context savings apply inside your IDE session. Tool discovery happens on demand via mcplex_find_tools, and every call is still routed through RBAC, allowlists, audit logging, and the response cache.
Cursor / Windsurf / HTTP-capable MCP Clients
Clients that support streamable HTTP can connect directly:
{
"mcpServers": {
"mcplex-gateway": {
"url": "http://127.0.0.1:3100/mcp"
}
}
}
Custom Python Agent
import requests
GATEWAY = "http://127.0.0.1:3100/mcp"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"} # Optional
# Initialize
resp = requests.post(GATEWAY, json={
"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2025-11-25", "capabilities": {},
"clientInfo": {"name": "my-agent", "version": "1.0"}}
}, headers=HEADERS)
# List all tools (MCPlex aggregates from all servers)
resp = requests.post(GATEWAY, json={
"jsonrpc": "2.0", "id": 2, "method": "tools/list"
}, headers=HEADERS)
tools = resp.json()["result"]["tools"]
# Call a tool (MCPlex routes to the right server automatically)
resp = requests.post(GATEWAY, json={
"jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": {"name": "create_issue", "arguments": {"repo": "my-repo", "title": "Bug fix"}}
}, headers=HEADERS)
How It Catches Your Agent's Calls
MCPlex acts as a man-in-the-middle proxy for all MCP traffic:
Your Agent โโPOST /mcpโโโ MCPlex Gateway โโโ Upstream MCP Server
โ (persistent stdio or HTTP)
โโ โ
Auth check (constant-time API key compare)
โโ ๐ฆ Rate limit check
โโ ๐งช Input validation (name charset, 64KB / depth-16 args cap)
โโ ๐ RBAC + allowlist/blocklist (role bound to API key)
โโ ๐ Audit log (every call)
โโ ๐ Metrics (latency, tokens, security events)
Every tools/call goes through the security engine and is logged. Every tools/list goes through the semantic router. There's no way to bypass it โ if your agent uses MCPlex as its MCP endpoint, all calls are intercepted, checked, and logged.
๐ค Compatible AI Models
MCPlex is model-agnostic โ it routes any MCP-compliant client traffic regardless of which LLM is driving it. These are the frontier models actively tested with MCPlex as of July 2026:
| Model | Provider | MCP Client | Best For |
|---|---|---|---|
| GPT-5.6 Sol | OpenAI | ChatGPT Work, custom | Flagship reasoning + agentic tasks |
| GPT-5.6 Terra | OpenAI | ChatGPT Work, custom | Balanced performance / cost |
| GPT-5.6 Luna | OpenAI | ChatGPT Work, custom | Cost-efficient everyday tasks |
| Claude Fable 5 | Anthropic | Claude Code, Claude Desktop | Extended reasoning + code |
| Claude Mythos 5 | Anthropic | Claude Code, Claude Desktop | Complex multi-step agent workflows |
| Claude Sonnet 5 | Anthropic | Claude Code, Claude Desktop | High-capability, broad availability |
| Gemini 3.5 Flash | Gemini Spark, custom | High-throughput, cost-efficient | |
| Gemini 3.1 Pro | Gemini Spark, custom | Multimodal + Workspace integration | |
| Grok 4.5 | xAI | Custom / open-weight stacks | Competitive reasoning, open ecosystem |
The meta-tool pattern (3 gateway tools, ~200 tokens) is particularly effective with models that have smaller default context budgets โ MCPlex's token savings become more impactful as model costs rise.
Context savings scale with model pricing. With GPT-5.6 Sol at frontier pricing, eliminating 40k tokens of tool definitions per request translates to measurable cost reduction at scale.
๐ง Semantic Tool Routing
The killer feature. Instead of dumping all tool definitions into your LLM's context, MCPlex uses a meta-tool pattern that works with every standard MCP client โ no custom extensions needed:
| Scenario | Without MCPlex | With MCPlex | Savings |
|---|---|---|---|
| 5 servers, 50 tools | ~10,000 tokens | ~200 tokens | 98% |
| 10 servers, 100 tools | ~20,000 tokens | ~200 tokens | 99% |
| 20 servers, 200 tools | ~40,000 tokens | ~200 tokens | 99.5% |
How It Works
When your agent calls tools/list, MCPlex returns 3 lightweight meta-tools (~200 tokens) instead of all real tools:
Agent MCPlex Gateway
โ โ
โโโtools/listโโโโโโโโโโโโโโโโโโโบโ Returns: mcplex_find_tools, mcplex_call_tool,
โ โ mcplex_list_categories (~200 tokens)
โ โ
โโโmcplex_find_toolsโโโโโโโโโโโโโบโ "store a memory"
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ [{name: "create_memory", desc: "...", inputSchema: {...}},
โ โ {name: "save_note", desc: "...", inputSchema: {...}}]
โ โ
โโโmcplex_call_toolโโโโโโโโโโโโโโบโ {name: "create_memory", arguments: {...}}
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ tool result (routed through security + cache + audit)
mcplex_find_tools(query)โ Search for tools by natural language intent. Returns matching tools with full schemas.mcplex_call_tool(name, arguments)โ Execute a discovered tool. Routes through the full security/audit/cache pipeline.mcplex_list_categories()โ Browse available tool categories (server groups) with tool counts.
This works with Claude Code, Claude Desktop, Cursor, Windsurf, and any other MCP client โ no custom extensions or client-side plugins required.
Routing Mode
MCPlex supports three routing modes via router.mode:
| Mode | Behavior | Client Compatibility |
|---|---|---|
metatool (default) |
Returns 3 gateway meta-tools; agent discovers real tools via mcplex_find_tools |
โ All standard MCP clients |
passthrough |
Returns all real tools directly (no routing indirection) | โ All standard MCP clients |
legacy |
Uses _mcplex_query param extension for filtering |
โ Custom clients only |
Routing Strategy
Within metatool and legacy modes, MCPlex uses a routing strategy to rank tools:
semanticโ Character n-gram embeddings with cosine similarity (recommended)keywordโ TF-IDF keyword matching (zero ML dependency)passthroughโ No filtering (baseline)
[router]
mode = "metatool" # "metatool", "passthrough", or "legacy"
strategy = "semantic" # "semantic", "keyword", or "passthrough"
top_k = 5 # Return top 5 most relevant tools
similarity_threshold = 0.3 # Minimum relevance score
cache_embeddings = true # Cache for faster repeated queries
๐ Security Engine
Role-Based Access Control (RBAC)
[security]
enable_rbac = true
[roles.developer]
allowed_tools = ["github/*", "database/query_*"]
[roles.admin]
allowed_tools = ["*"]
[roles.readonly]
allowed_tools = ["*/list_*", "*/get_*"]
blocked_tools = ["*/delete_*", "*/drop_*"]
Role trust boundary: a caller's role is only ever established server-side, from a verified
api_key/api_keysmatch โ never from anything a client sends in the request body. Whenenable_rbac = trueand noapi_key/api_keysare configured, every tool call is denied by default (no role can be established), and the gateway logs a startup warning so this isn't mistaken for a bug.
Per-Server Tool Blocklists
[[servers]]
name = "database"
url = "http://localhost:8080/mcp"
blocked_tools = ["drop_table", "delete_*", "truncate_*"]
Structured Audit Logging
Every tool invocation is logged as JSON Lines:
{"timestamp":"2026-04-10T10:00:00Z","event":"tool_call","tool_name":"github/create_issue","server_name":"github","duration_ms":342,"trace_id":"a1b2c3d4"}
{"timestamp":"2026-04-10T10:00:01Z","event":"tool_blocked","tool_name":"database/drop_table","reason":"security_policy","trace_id":"e5f6g7h8"}
๐ Real-time Dashboard
Built-in observability dashboard at http://localhost:9090:

- Global Metrics โ Total requests, tool calls, errors, tokens saved
- Per-Tool Stats โ Invocation count, avg/p50/p95/p99 latency
- Server Fleet โ Connected servers, transport type, tool/resource/prompt counts
- Live Event Stream โ Real-time feed of all gateway activity with latency tags
Glassmorphism design with animated gradients. Auto-refreshes every 3 seconds. Zero configuration.
By default the dashboard's /api/* routes are unauthenticated โ fine for 127.0.0.1-only binds, but if you expose the dashboard beyond localhost, set gateway.dashboard_api_key so requests need an Authorization: Bearer <key> (or X-API-Key) header:
[gateway]
dashboard_api_key = "${MCPLEX_DASHBOARD_API_KEY}"
๐ฆ Response Caching
Avoid redundant upstream calls for read-only tools:
[cache]
enabled = true
ttl_seconds = 300 # 5 minute TTL
max_entries = 1000 # Max cached responses
Cached responses are partitioned by caller role, so a cache entry from one role/tenant is never served to another. MCPlex auto-detects read-only tools by prefix (list_*, get_*, search_*, query_*, describe_*, show_*). You can override with custom patterns:
[cache]
patterns = ["my_custom_tool", "expensive_*"]
Write operations (create_*, update_*, delete_*) are never cached by default.
๐ Multi-Tenant API Keys
Map API keys to RBAC roles for team-based access:
[api_keys."sk-dev-team-abc123"]
role = "developer"
description = "Dev team key"
[api_keys."sk-admin-xyz789"]
role = "admin"
description = "Admin key"
When a request comes in with Authorization: Bearer sk-dev-team-abc123, MCPlex automatically applies the developer role's RBAC policies.
๐ฅ Hot-Reload Configuration
Config changes apply instantly โ no restart needed:
# Edit config while MCPlex is running
vim mcplex.toml
# MCPlex automatically detects changes:
# ๐ Config file changed, reloading...
# โ
Configuration reloaded successfully
๐ Full MCP Capability Support
MCPlex aggregates and forwards all three MCP capability types from upstream servers:
| Capability | List | Execute/Read | Routing |
|---|---|---|---|
| Tools | tools/list โ aggregated |
tools/call โ routed to owner |
โ Semantic / Keyword |
| Resources | resources/list โ aggregated |
resources/read โ routed by URI |
Direct routing |
| Prompts | prompts/list โ aggregated |
prompts/get โ routed by name |
Direct routing |
Configuration Reference
[gateway]
| Key | Type | Default | Description |
|---|---|---|---|
listen |
string | 127.0.0.1:3100 |
MCP client connection address |
dashboard |
string | โ | Dashboard address (disabled if not set) |
hot_reload |
bool | true |
Auto-reload config on file change |
name |
string | mcplex |
Gateway instance name |
api_key |
string | โ | API key for client auth (supports ${ENV}) |
dashboard_api_key |
string | โ | API key for the dashboard /api/* routes (supports ${ENV}). If unset, the dashboard is unauthenticated โ bind it to 127.0.0.1 in that case. |
rate_limit_rps |
int | 0 |
Max requests/sec per client (0 = unlimited) |
[router]
| Key | Type | Default | Description |
|---|---|---|---|
mode |
string | metatool |
metatool, passthrough, or legacy |
strategy |
string | keyword |
semantic, keyword, or passthrough |
top_k |
int | 5 |
Maximum tools returned per query |
similarity_threshold |
float | 0.3 |
Minimum relevance score (0.0-1.0) |
cache_embeddings |
bool | true |
Cache tool embeddings |
[security]
| Key | Type | Default | Description |
|---|---|---|---|
enable_rbac |
bool | false |
Enable role-based access control |
enable_audit_log |
bool | false |
Enable structured audit logging |
audit_log_path |
string | ./logs/audit.jsonl |
Audit log file path |
max_log_size_mb |
int | 100 |
Max log file size before rotation (keeps 5 backups) |
circuit_breaker_max_crashes |
int | 5 |
Max crashes within the window before respawns are blocked |
circuit_breaker_window_secs |
int | 60 |
Sliding window (seconds) for crash counting |
request_timeout_secs |
int | 30 |
Steady-state per-request timeout for HTTP and stdio upstream calls, once connected (0 = no timeout) |
default_handshake_timeout_secs |
int | 30 |
Default timeout for the initial stdio MCP handshake (initialize), before a server is considered failed and enters the respawn loop. Overridable per server โ see servers[].handshake_timeout_secs below. (0 = no timeout) |
[[servers]]
| Key | Type | Required | Description |
|---|---|---|---|
name |
string | โ | Unique server name |
command |
string | โก | Executable path for stdio transport |
args |
list | โ | Command arguments |
url |
string | โก | URL for HTTP transport |
env |
map | โ | Environment variables (supports ${VAR}) |
allowed_roles |
list | โ | Roles allowed to access this server |
blocked_tools |
list | โ | Tool blocklist patterns (glob) |
allowed_tools |
list | โ | Tool allowlist patterns (glob) |
enabled |
bool | true |
Enable/disable this server |
handshake_timeout_secs |
int | โ | Per-server override for the initial stdio handshake timeout. Falls back to security.default_handshake_timeout_secs when unset. Useful for servers with a slow cold start, e.g. npx resolving/downloading a package on first run (0 = no timeout) |
โก = One of command or url is required
Note: For stdio servers,
commandshould be the executable path (e.g.npx,/usr/bin/python3). Additional arguments go in theargsarray.
Handshake timeout vs. request timeout: the initial stdio
initializehandshake and steady-statetools/callrequests use two independent timeouts. A slow-starting server (e.g.npxfetching a package on first run) can be given a longerhandshake_timeout_secswithout loosening the timeout applied to every later tool call:[[servers]] name = "slow-npx-server" command = "npx" args = ["-y", "@some/mcp-server"] handshake_timeout_secs = 90 # default 30s, 0 = no timeout
[roles.<name>]
| Key | Type | Description |
|---|---|---|
allowed_tools |
list | Tool patterns this role can access (glob) |
blocked_tools |
list | Tool patterns this role cannot access (glob) |
Glob Patterns: * matches any characters, ? matches a single character.Examples: github/*, */query_*, database/get_?ser
Environment Variables
MCPlex supports ${ENV_VAR} syntax in configuration:
[[servers]]
name = "github"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "${GITHUB_TOKEN}" }
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MCPlex Gateway โ
โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โ โ Semantic โ โ Security โ โ Observability โ โ
โ โ Router โ โ Engine โ โ Collector โ โ
โ โ โ โ โ โ โ โ
โ โ โข Embeddings โ โ โข RBAC โ โ โข Token Savings โ โ
โ โ โข TopK Match โ โ โข Allowlist โ โ โข Latency (p99) โ โ
โ โ โข Caching โ โ โข Audit Log โ โ โข Dashboard โ โ
โ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโฌโโโโ โ โ
โ โผ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ MCP Protocol Multiplexer โโโโโโโโโโ โ
โ โ + Response Cache โ โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโ
โผ โผ โผ
MCP Server MCP Server MCP Server
(stdio โ (stdio โ (HTTP โ
persistent) persistent) stateless)
CLI Reference
mcplex [OPTIONS]
Options:
-c, --config <FILE> Config file path [default: mcplex.toml]
-v, --verbose Enable verbose logging
--listen <ADDR> Override gateway listen address
--dashboard <ADDR> Override dashboard listen address
--check Validate config and exit
-h, --help Print help
-V, --version Print version
๐ก๏ธ Production Hardening
API Key Authentication
Secure your gateway so only authorized agents can connect:
[gateway]
api_key = "${MCPLEX_API_KEY}" # Set via environment variable
Clients authenticate via header:
Authorization: Bearer your-secret-key
# or
X-API-Key: your-secret-key
Health checks (/health) are always unauthenticated for load balancer probes.
Rate Limiting
Prevent runaway agents from overwhelming your servers:
[gateway]
rate_limit_rps = 50 # Max 50 requests/sec per client (burst: 100)
Uses a per-client token bucket with 2x burst allowance. Returns 429 Too Many Requests when exceeded.
Log Rotation
Audit logs rotate automatically โ they won't fill your disk:
[security]
enable_audit_log = true
audit_log_path = "./logs/audit.jsonl"
max_log_size_mb = 100 # Rotates at 100MB, keeps 5 backups
Files rotate as: audit.jsonl โ audit.jsonl.1 โ ... โ audit.jsonl.5 (oldest deleted).
Network Security
Bind to localhost only (default) for local agents:
[gateway]
listen = "127.0.0.1:3100" # Localhost only
dashboard = "127.0.0.1:9090" # Dashboard also localhost
For network access, use a reverse proxy (nginx/caddy) with TLS.
Prometheus Monitoring
MCPlex exposes Prometheus-compatible metrics on the dashboard port for external monitoring:
/api/metricsโ JSON metrics endpoint/api/prometheusโ Prometheus text exposition format (v0.4.0)
mcplex_requests_total 1234
mcplex_tool_calls_total 567
mcplex_errors_total 3
mcplex_tokens_saved_total 45000
mcplex_tool_duration_ms{tool="create_issue",quantile="0.95"} 142
๐ AgentLens Integration
MCPlex pairs with AgentLens for full-stack agent observability. MCPlex handles execution, AgentLens handles visualization โ together they cover 100% of the agent lifecycle. As of v0.4.0, the AgentLens bridge is fully wired and active when configured via the [agentlens] config section.
Enable the Bridge (opt-in)
Add to your mcplex.toml:
[agentlens]
enabled = true
url = "http://127.0.0.1:3000/api/ingest"
session_name = "MCPlex Gateway"
Every tool call, security event, and routing decision is forwarded to AgentLens's timeline replay UI. The bridge is non-blocking (fire-and-forget) โ it never slows down the gateway, even if AgentLens is offline.
Note: MCPlex works 100% independently without AgentLens. The bridge is entirely opt-in.
๐ฅ The Forge Integration
MCPlex pairs naturally with The Forge โ a multi-agent code evolution platform that pits frontier models head-to-head on coding challenges using evolutionary algorithms.
The Forge exposes its own MCP server, which means you can add it to MCPlex just like any other server:
[[servers]]
name = "forge"
url = "http://localhost:8400/mcp" # The Forge MCP endpoint
transport = "streamable-http"
allowed_roles = ["developer", "admin"]
With this setup, your agents can invoke Forge tools (multi-model arena runs, evolution sessions, research synthesis) through the same gateway โ with full RBAC, audit logging, and token-saving routing applied automatically.
| Forge Tool | What It Does |
|---|---|
forge_run_arena |
Pit GPT-5.6 / Claude Fable 5 / Gemini / Grok head-to-head on a coding challenge |
forge_evolve |
Apply mutation operators to code and run fitness-gated selection |
forge_research |
Deep-research a topic across multiple models, synthesise consensus |
forge_benchmark |
Score and rank model outputs on custom evaluation criteria |
MCPlex works 100% independently without The Forge. See github.com/ModernOps888/the-forge for setup.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Run
cargo fmt && cargo clippy -- -D warnings && cargo test - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License โ see LICENSE for details.
๐ง Recent Changes (v0.7.1 โ Security Hardening & Configurable Handshake Timeout)
- RBAC Self-Assertion Bypass Fixed (HIGH) โ
dispatch_real_toolpreviously fell back to a client-supplied_mcplex_rolerequest param whenever no server-verified role was established (e.g. noapi_key/api_keysconfigured). Any caller could self-assertrole: "admin"and bypass RBAC entirely. The role now comes exclusively from the auth middleware's server-verifiedtrusted_role. - Dashboard Authentication โ
/api/*dashboard routes were previously unauthenticated. Added optionalgateway.dashboard_api_key; when set, dashboard routes require a matchingAuthorization: Bearer/X-API-Keyheader. Unset by default (backward compatible), with a startup warning to bind localhost-only in that case. - Role-Partitioned Cache โ The tool response cache is now partitioned by caller role, closing a cross-tenant leakage gap in multi-key deployments.
- Empty-Secret Rejection โ
validate_confignow rejectsgateway.api_key/dashboard_api_key/api_keysthat resolve to an empty string (e.g. from an unset${ENV_VAR}), instead of silently turning "auth required" into a no-op. - Configurable Stdio Handshake Timeout (fixes #20) โ the initial
initializehandshake and steady-state tool calls now use independent timeouts:security.default_handshake_timeout_secs(global, default 30s) andservers[].handshake_timeout_secs(per-server override) for the handshake, vs.security.request_timeout_secsfor steady-state calls. Fixes spurious failures on slow-cold-start servers (e.g.npxdownloading a package on first run).0 = no timeoutis now actually implemented for both. - Audit Redaction List Expanded โ added
pwd,auth,cookie,session,bearerto the sensitive-key redaction list. - 38 tests passing (up from 32) โ new regression tests for the RBAC fix, cache partitioning, and handshake-timeout config.
- Compatible Models Dashboard Panel โ Built-in dashboard now shows a
๐ค Compatible Modelspanel with colour-coded provider badges for all 9 active frontier models: GPT-5.6 Sol/Terra/Luna, Claude Fable 5/Mythos 5/Sonnet 5, Gemini 3.5 Flash/3.1 Pro, Grok 4.5. - Ecosystem Footer โ Dashboard footer now links to AgentLens, The Forge, and GitHub with the current MCP protocol version displayed.
- Expanded Server Examples โ
mcplex.tomlandexamples/updated withmemory,fetch,brave-search,postgres,sequential-thinking, andforge(The Forge MCP) server blocks. - The Forge Integration โ New README section with example config and tool table for connecting The Forge multi-model arena as an MCP server.
- Compatible AI Models Table โ Full July 2026 model matrix in the README with MCP client and use-case guidance.
- Fixed Python example protocol version
2025-03-26โ2025-11-25. - Fixed missing CHANGELOG comparison links for v0.4.0โv0.6.0.
- Audit Log Secret Redaction โ Values under sensitive keys (
password,token,api_key,secret,credential, etc.) are recursively replaced with[REDACTED]before hitting disk; credentials never persist inaudit.jsonl. - Spoof-Proof Rate Limiting โ Client identity now comes from the real socket address (
ConnectInfo), falling back to the firstX-Forwarded-Forhop only behind proxies; header spoofing no longer evades limits. - Auth/Rate-Limit Denial Telemetry โ
auth_deniedandrate_limitedsecurity events now recorded in metrics and visible in the dashboard event stream. - Resource/Prompt Audit Coverage โ
resources/readandprompts/getare now written to the audit trail (resource_read/prompt_getevents); resource URIs capped at 2048 chars. - Panic Fix โ
tools/listin meta-tool mode no longer panics when fewer real tools than meta-tools are connected (saturating arithmetic). - 2 new audit redaction tests (31 total); live smoke test verified end-to-end.
- Constant-Time API Key Verification โ Key comparison is no longer vulnerable to timing side-channels.
- Trusted Role Binding โ Multi-tenant API keys now bind their RBAC role at the middleware layer (
X-MCPlex-Roleinjected server-side). Clients can no longer self-assert a role via_mcplex_rolewhen authenticated. - Tool Call Input Validation โ Tool names are restricted to a safe charset (max 128 chars); arguments are capped at 64KB with a max JSON nesting depth of 16. Malformed calls are rejected before touching upstream servers.
- Request Body Limit โ Gateway rejects request bodies over 1MB.
- Security Telemetry โ New
security_events,blocked_tool_calls, andrejected_tool_callscounters, surfaced as dashboard cards plus a live security-posture pill (RBAC/Audit status) in the header. - VS Code / GitHub Copilot Integration โ Documented
.vscode/mcp.jsonsetup with a ready-to-copy template (examples/vscode-mcp.json) for token-saving meta-tool routing inside IDE sessions. - Dynamic Version Banner โ CLI banner and dashboard header now display the crate version automatically.
- Shared HTTP Client โ Replaced per-call
reqwest::Client::new()with a pooled static client. Enables HTTP/2 connection reuse, TLS session resumption, and 30s request timeouts. - Circuit Breaker Config โ
circuit_breaker_max_crashes,circuit_breaker_window_secs, andrequest_timeout_secsare now configurable in[security](previously hardcoded). - Env-Var Expansion Fix โ
${ENV_VAR}expansion now uses a single-pass cursor instead of awhile-loop, preventing infinite loops if a resolved value itself contains${. - Rate Limiter Memory Fix โ Stale client IP entries are now pruned every 100 checks (entries idle >5 min), preventing unbounded HashMap growth.
Built with ๐ฆ Rust for the AI agent community
If MCPlex saves your context window, give it a โญ