mcp-slim-proxy
by ishaanpilar
README.md
# mcp-slim-proxy
A client-agnostic [Model Context Protocol](https://modelcontextprotocol.io) proxy
that slims the **tool-definition context tax**.
Every MCP tool call serializes the full tool schema into the model's context
window. Wire up a dozen chatty servers and your agent spends most of its budget
reading its own menu — one reported team burned **143k of a 200k token budget
(72%)** on tool definitions before doing any work.
`mcp-slim-proxy` sits between any MCP client (Claude Desktop, Cursor, a custom
agent) and any number of upstream MCP servers. Instead of exposing all N
servers' schemas, it exposes a handful of **meta-tools**:
| meta-tool | what it does |
|-----------|--------------|
| `search_tools` | natural-language search over the aggregated catalog → names + one-line summaries (cheap) |
| `load_tool` | pull specific tools' full schemas into the live tool list so they become callable (`tools/list_changed`) |
| `unload_tool` | drop tools back out to reclaim context |
| `load_bundle` | load a named, preconfigured group of tools at once |
| `proxy_report` | live before/after token accounting |
The agent searches in plain language, loads only the handful of tools it needs,
and calls them — the proxy forwards each call to the owning upstream
transparently. Everything else stays out of context.
### The win scales with server count
The slimmed side stays ~flat (meta-tools + a small working set) while a naive
aggregating proxy grows linearly. From `scripts/benchmark.py` (tiktoken counts):
| servers | tools | baseline tokens | slimmed tokens | saved |
|--------:|------:|----------------:|---------------:|------:|
| 1 | 10 | 1,384 | 951 | 31.3% |
| 5 | 50 | 6,904 | 951 | 86.2% |
| 10 | 100 | 13,804 | 951 | 93.1% |
| 25 | 250 | 34,504 | 951 | 97.2% |
| 100 | 1000 | 138,004 | 951 | 99.3% |
## Install
```bash
uv venv
uv pip install -e ".[tokens,http]" # tokens = exact tiktoken counts; http = serve over HTTP
```
Extras: `tokens` (tiktoken), `http` (starlette+uvicorn), `embeddings`
(sentence-transformers semantic ranker), `dev` (tests).
## Configure
The config mirrors the familiar `mcpServers` block from Claude Desktop / Cursor.
Both **stdio** and **streamable-HTTP** upstreams are supported.
```json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
},
"remote": {
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer ${MY_MCP_TOKEN}" }
}
},
"settings": {
"compressDescriptions": true,
"ranker": "bm25",
"searchLimit": 8,
"alwaysLoad": ["filesystem__read_file"],
"bundles": { "files": ["filesystem__read_file", "filesystem__write_file"] },
"lazyConnect": true,
"cache": true,
"cacheTtlSeconds": 86400
}
}
```
`${ENV_VAR}` references are expanded so secrets stay in the environment. Add
`"enabled": false` to keep a server in the file but skip it. The `settings`
block is optional; every key shown is a default.
## Run
As a stdio MCP server (the transport clients spawn):
```bash
mcp-slim-proxy --config config.json
```
Over HTTP instead (point an HTTP-capable client at `http://host:port/mcp`):
```bash
mcp-slim-proxy --config config.json --transport http --port 8848
```
Example Claude Desktop entry:
```json
{
"mcpServers": {
"slim": { "command": "mcp-slim-proxy", "args": ["--config", "/abs/path/config.json"] }
}
}
```
### Measure the savings
Connect, print the before/after token report, and exit — no client needed:
```bash
mcp-slim-proxy --config examples/multi.config.json --report # human
mcp-slim-proxy --config examples/multi.config.json --report --json # for CI
```
The JSON report also lists connected/failed upstreams, always-loaded tools, and
cross-server duplicate groups. Run the scaling table with
`python scripts/benchmark.py`.
## How it works
```
client mcp-slim-proxy upstream MCP servers
┌──────┐ stdio/http ┌──────────────────────┐ stdio/http ┌───────────┐
│agent │◀───────────▶│ search_tools │◀─────────────▶│ filesystem│
└──────┘ meta-tools │ load_tool / _bundle │ (lazy conn) ├───────────┤
+ loaded │ unload_tool │ │ gmail │
tools │ proxy_report ─┐ BM25 │ ├───────────┤
└───────────────┴──────┘ │ remote… │
catalog cache (disk) └───────────┘
```
- **Aggregation** — connects to every upstream as an MCP client and namespaces
tools as `<server>__<tool>` to avoid collisions. A failed upstream is logged
and skipped, not fatal.
- **Lexical search** — a dependency-free BM25 index over each tool's name,
description, and parameter names (camelCase / snake_case aware). Deterministic
and trivially benchmarkable. An optional embeddings ranker is available via
`"ranker": "embeddings"` + the `embeddings` extra.
- **Description compression** — strips boilerplate ("Use this tool to…") and
schema-duplicating `Args:`/`Returns:` blocks before a schema enters context.
Applied only to exposed tools; the report baseline stays uncompressed so
reported savings are honest.
- **Dedupe** — near-identical tools across servers share a signature; search
collapses them and `--report --json` lists the duplicate groups.
- **Lazy connect + cross-session cache** — the aggregated catalog is cached to
disk (keyed by the server set), so repeated sessions don't re-pay tool
discovery; a server is only actually connected when one of its tools is first
called. `--refresh` re-discovers; `--clear-cache` wipes it. Connection
lifecycles are owned by a single host task so shutdown is clean under anyio.
- **Lazy schemas** — full schemas enter context only when `load_tool` /
`load_bundle` runs, which fires `tools/list_changed` so the client re-fetches.
## Develop
```bash
uv pip install -e ".[dev,tokens,http]"
uv run pytest -q --timeout=60
```
Tests cover the catalog/BM25, compression, cache round-trips, config parsing,
the token report, and full stdio **and** HTTP client loops against bundled demo
servers (`examples/demo_server.py`, `examples/db_server.py`).
## Roadmap
- Embeddings ranker is wired but ships behind an extra; add a bundled small model.
- Per-tool call metrics in the report.
- Hot-reload of upstream tool lists on `tools/list_changed` from upstreams.
## License
MIT
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues