Skip to main content
Glama
ishaanpilar
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