You open a fresh Claude Code session. You haven't asked it to do anything yet. You run /context and see the warning: Large MCP tools context (~82,000 tokens > 25,000).
This is the MCP context tax. Every MCP server you connect pushes its full tool schemas into the model's context at session start. Ten servers with 40–50 tools each means 40,000–80,000 tokens gone before your first prompt. On a 200k context window, that's 20–40% of your budget spent on definitions the agent may never use.
The problem is real and widely documented. Scott Spence measured 81,986 tokens across seven MCP servers in a single Claude Code session. The MCP specification issue #2808 notes that tool definitions consume 5–15× more tokens than minimal schemas. Claude Code issues #13717 and #7172 track the consolidation request. A Reddit user on r/LangChain reported 50k tokens per run just from preloaded MCP tools.
Why Tool Schemas Are So Expensive
An MCP tool definition isn't just a function signature. Each tool carries:
- Name and description
- Full JSON Schema for input parameters (including nested objects, arrays, enums, constraints)
- Annotations for UI hints (readOnly, destructive, idempotent)
- Server-level instructions and metadata
A typical tool schema runs 300–800 tokens. Multiply by 50 tools and you're at 15,000–40,000 tokens. The MCP spec issue shows that a minimal type-only schema for the same tool is 5–15× smaller. The verbosity is by design—rich schemas help the model understand what tools do—but the cost is paid upfront for every tool, used or not.
The Multiplexer Pattern: Lazy Schema Discovery
The fix is architectural: don't load schemas until the model actually needs them. This is what mcp-multiplexer (by JohGirard) implements. Instead of connecting each MCP server directly, you point your client at one multiplexer server. The multiplexer fronts all upstream servers but exposes only 7 meta-tools:
| Meta-tool | Purpose |
|---|---|
list_servers | Overview: name, status, tool count, instructions |
list_tools(server) | Tool names, one-line descriptions, annotations — no schemas |
search_tools(query, server?, limit=5) | Matching tools with full input schemas |
describe_tool(server, tool) | One exact tool's full input schema |
call_tool(server, tool, arguments) | Proxied call; results returned verbatim |
refresh_tools(server?) | Reconnect and rebuild the tool index |
authorize_server(server, pasted_url?) | Start/complete OAuth login for a server |
The model discovers tools lazily: list and search first, fetch a full schema only when it's about to call. The tool index is cached at ~/.cache/mcp-multiplexer/index.json for instant startup, and upstream servers connect lazily.
--stats command reports: without mux ~16,333 tokens (46 tools), with mux ~553 tokens (meta-tools), saved ~15,780 tokens (96%). The marketing claims "up to 95%" — the measured number is slightly better.
How the Model Actually Uses It
You might wonder: does the model know to use these meta-tools? Yes. The multiplexer's instructions tell the model the protocol:
To use tools, first call
list_serversorsearch_toolsto discover what's available. Then calldescribe_toolto get the full schema for a specific tool. Finally callcall_toolto execute it.
In practice the flow looks like:
- Model calls
search_tools("read file", limit=3)→ gets 3 matching tools with full schemas - Model picks one, calls
call_tool("filesystem", "read_file", {path: "..."}) - Multiplexer proxies the call to the upstream filesystem server, returns result verbatim
This is 2–3 tool calls instead of 1, but the token cost of those 2–3 calls is tiny compared to loading 50 schemas upfront. The tradeoff is a discovery hop for a massive context saving.
Hybrid Mode: Expose Your Favorites
For servers you call every session, many times per session (e.g., Serena for code navigation, a database server you query constantly), the discovery hop is pure overhead. The multiplexer supports "expose": true on a per-server basis:
{
"mcpServers": {
"serena": {
"command": "uvx",
"args": ["serena", "start-mcp-server"],
"expose": true
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"]
}
}
}
With expose: true, that server's tools mount directly as first-class server__tool tools (e.g., serena__find_symbol), schema and all, next to the meta-tools. The model calls them like any directly-connected tool — no search, no describe, no proxy hop. The server stays reachable through the meta-tools too, and allow/deny still apply.
Rule of thumb: multiplex the fleet, expose the favorites.
Governance and OAuth Built In
The multiplexer isn't just a token saver. It adds two features that matter for team and production use:
Allow/deny governance. Per-server allow (exact names or prefix* globs) and deny lists let you restrict what the model can see and call. deny always wins. This is a control plane for agent tool access without modifying upstream servers.
OAuth 2.1 brokering. Remote servers that speak the MCP authorization spec work with "oauth": true on a URL server. First use fails with an authorization URL; open it, approve; a temporary localhost listener completes the exchange. Tokens live in ~/.cache/mcp-multiplexer/tokens.json (mode 0600), refresh automatically, and survive restarts. The model can also drive this itself via the authorize_server meta-tool.
Claude Code Integration
The project is also a Claude Code plugin. Install it and it migrates your existing servers into a multiplexer config:
/plugin marketplace add JohGirard/mcp-multiplexer
/plugin install mcp-multiplexer@mcp-multiplexer
Then ask Claude to "set up mcp-multiplexer" (or run /mcp-multiplexer:setup). The plugin installs the binary, rewrites your config (secrets become ${VAR} references, OAuth servers get flagged), rewires your client config, and verifies.
With the plugin installed, /mcp-multiplexer:gain shows the token savings report in chat.
What It Doesn't Do (Non-Goals)
The README is explicit about non-goals, which is refreshing:
- MCP resources and prompts (tools only)
- Upstream sampling, elicitation, and roots
- No truncation of tool results — returned verbatim
- OAuth for URL servers only (stdio servers use env for secrets)
- No
tools/list_changednotification forwarding — userefresh_tools
This scope discipline is why the binary is small, fast, and reliable.
Related Work
mcp-multiplexer isn't the only project attacking this problem. The pattern goes by several names:
- MCP gateways: Gate22, MetaMCP, Jetski, agiletec-inc/airis-mcp-gateway (60+ tools through 7 meta-tools)
- Lazy schema loading: Focused, OpenMCP, Ismael Ramos, ArXiv 2604.21816 ("Tool Attention Is All You Need")
- Filesystem-based discovery: Bifrost exposes tools as files; discovery becomes lazy
- Call batching: callmux batches multiple tool calls into one request (~19× context reduction)
Claude Code itself has started addressing this: recent versions defer MCP tool schemas by default and load on demand through tool search (Geotoolbox, wmedia.es). But the multiplexer approach works across all MCP clients (Cursor, Zed, custom clients) and adds governance/OAuth on top.
Should You Use It?
If you run 3+ MCP servers in Claude Code (or Cursor, Zed, etc.) and see context warnings, yes. The install is one binary, the config is your existing mcpServers with one wrapper, and the plugin handles migration. The token savings are measurable and immediate.
If you run 1–2 servers with few tools, the overhead of the discovery hop may not be worth it. But as your tool fleet grows, the multiplexer pattern becomes the default architecture — it's the difference between an agent that can think and an agent that's already out of context before it starts.
If this was useful, you can support my open-source work on Ko-fi or check out my services.