High-utility, zero-bloat Model Context Protocol (MCP) tool router and schema compactor in Go.
Compress verbose JSON schemas, enforce deterministic token budgets, dynamically route multi-server catalogs, and cut context token overhead by up to 96.5% without breaking tool execution.
+-----------------------------------------------------------------------------+
| $ toolprune proxy --preset balanced --max-schema-tokens 1500 |
+-----------------------------------------------------------------------------+
| [MCP Gateway] Intercepted 6 upstream MCP servers (42 tools loaded) |
| [Context Tax] Raw JSON Schema payload: 46,210 tokens |
| [AST Compactor] 5-pass token budget + active window: 1,420 tokens |
| [Stream Filter] FNV-1a cache: 0.004ms hit | Pre-bind check: 0.011ms |
| --------------------------------------------------------------------------- |
| Token Reduction: -96.9% | Cold Compaction Pass: < 0.40ms |
| Saved / Session: 44,790 tokens/turn |
+-----------------------------------------------------------------------------+
# macOS / Linux (SHA-256 verified)
curl -fsSL https://raw.githubusercontent.com/aloneangel45/toolprune/main/install.sh | bash
# Windows PowerShell (SHA-256 verified)
irm https://raw.githubusercontent.com/aloneangel45/toolprune/main/install.ps1 | iex- The Problem: The MCP Context Tax
- How ToolPrune Solves It
- Empirical Benchmark Results
- Architecture
- Installation
- Quick Start
- Client Integration Guides
- CLI Reference
- Configuration Reference (
toolprune.yaml) - Contributing & License
The Model Context Protocol (MCP) standardizes how frontier LLMs connect to databases, filesystems, cloud APIs, and developer tools. However, multi-server MCP deployments incur a severe structural cost: The MCP Context Tax.
+------------------------------------------------------------------------+
| THE MCP CONTEXT TAX |
+------------------------------------------------------------------------+
| 4-6 Upstream Servers (GitHub, PostgreSQL, Cloudflare, Filesystem) |
| | |
| v |
| 20,000 to 45,000 Schema Tokens |
| Injected on EVERY SINGLE Message Turn |
| | |
| v |
| [COST] Compounding API inference consumption across 30+ turn loops |
| [LATENCY] Degraded Time-To-First-Token (TTFT) on uncached prefixes |
| [DRIFT] Attention dilution and parameter hallucination |
| [BUDGET] Context window starvation for source files and diffs |
+------------------------------------------------------------------------+
- Redundant Schema Prose: Typical MCP tool definitions embed multi-paragraph Markdown tutorials, decorative titles,
$schemaURIs, unreferenced$defs, and sprawling regular expressions. - Turn Multiplication: Full
tools/listschemas are injected into the prompt on every turn. In a 30-turn coding session, a 25,000-token tool catalog forces 750,000 tokens of repeated schema overhead. - Attention Dilution: Oversized tool catalogs degrade instruction adherence and tool selection accuracy.
ToolPrune is a single-binary, low-latency MCP proxy and multi-upstream router written in Go. It sits transparently between MCP clients (Claude Desktop, Claude Code, Cursor, Windsurf, Zed) and upstream servers:
- Tier 1 (
lossless/--preset fast): Strips$schemadeclarations,$comment,examples, decorative titles, and redundant formatting without altering parameter types or documentation semantics. - Tier 2 (
compact/--preset balanced): Condenses verbose Markdown prose, strips embedded HTML/code blocks, simplifies overly long regex patterns, and truncates descriptions on word boundaries. - Tier 3 (
aggressive/--preset strict): Strips redundant docstrings on self-evident parameters (id,path,query,repo,branch) while preservingtype,properties,required,enum,const,$ref,$defs/definitions,anyOf/oneOf/allOf,not, bounds, and MCPannotations. - Tier 4 (
dynamic/--preset dynamic): Exposes compact tool cards managed by an LRU active sliding window plus four built-in discovery/invocation meta-tools (toolprune_search_tools,toolprune_get_tool_schema,toolprune_activate_tools,toolprune_invoke). Dormant tools auto-hydrate on demand. - RFC #1 Adaptive Token-Budget Compactor (
--max-schema-tokens=N): Enforces a hard upper bound ontools/listtokens in<0.5msusing a 5-pass structural AST compactor weighted by livetools/callrecency and domain affinity, paired with<0.1mspre-binding argument validation that returns self-correctingCallToolResult{IsError: true}diagnostics before invalid calls reach upstream servers.
Measured using exact OpenAI cl100k_base BPE tokenization (tiktoken-go) and Go testing.B microbenchmarks (AMD Ryzen 9 8945HX, Go 1.23, CGO_ENABLED=0):
| Configuration | cl100k_base Tokens |
Savings (%) | Tokens / Tool | Schema Validity |
|---|---|---|---|---|
| Raw Unpruned Baseline | 3,596 | Baseline | 599.3 | PASS |
Tier 3 (--preset strict) |
2,167 | -39.7% | 361.2 | PASS |
RFC #1 (--max-schema-tokens 350) |
282 | -92.2% | 47.0 | PASS |
Tier 4 (--preset dynamic) |
127 | -96.5% | 21.2 | PASS |
| Tier | Pruning Strategy | Tokens | Bytes | Savings (%) | Tokens / Tool | Draft-07 Status |
|---|---|---|---|---|---|---|
| 0 | Tier 0: Raw Unpruned | 1,827 | 6,983 | Baseline | 456.8 | PASS |
| 1 | Tier 1: Lossless Minify | 1,761 | 6,706 | -3.6% | 440.2 | PASS |
| 2 | Tier 2: Description Condensation | 884 | 3,882 | -51.6% | 221.0 | PASS |
| 3 | Tier 3: Parameter Prune | 682 | 2,923 | -62.7% | 170.5 | PASS |
| 4 | Tier 4: Dynamic Catalog | 158 | 663 | -91.4% | 39.5 | PASS |
| Operation | Latency (ns/op) |
Wall Time | Memory (B/op) |
Allocs |
|---|---|---|---|---|
StreamFilter Fingerprinted Cache Hit (tools/list) |
~3,690 ns | 0.004 ms | 256 B | 2 |
StreamFilter Pre-Binding Schema Validation (tools/call) |
~11,300 ns | 0.011 ms | 4,408 B | 74 |
EnforceTokenBudget 5-Pass Compactor (6 Enterprise Tools) |
~224,000 ns | 0.224 ms | 93,208 B | 730 |
StreamFilter Cold Pass + Fingerprint Registration |
~360,000 ns | 0.360 ms | 158,000 B | 790 |
# Evaluate the bundled schema test suite
toolprune bench --file test/fixtures/schemas.json --format table
# Evaluate the realistic enterprise suite with an explicit token budget and CSV output
toolprune bench --file test/fixtures/mcp_suite_realistic.json --max-schema-tokens 350 --format csv
# Benchmark against a live upstream MCP server command
toolprune bench --verbose -- npx -y @modelcontextprotocol/server-github +-----------------------------------------+
| AI Client (Claude / Cursor / Windsurf)|
+--------------------+--------------------+
|
Inbound Transport (Stdio | SSE /sse | Streamable HTTP /mcp)
|
v
+------------------------------------------------------------------------+
| TOOLPRUNE CORE PROXY |
| |
| +------------------------------------------------------------------+ |
| | Bounded Framing Reader (64 KiB init / 10 MiB cap) | |
| +----------------------------------+-------------------------------+ |
| | |
| v |
| +------------------------------------------------------------------+ |
| | Dynamic Router, Multiplexer & Adaptive StreamFilter | |
| | (Profiles, namespacing, LRU window, pre-binding call check) | |
| +----------------------------------+-------------------------------+ |
| | |
| v |
| +------------------------------------------------------------------+ |
| | Schema Pruner & 5-Pass Token Budget Engine | |
| | (Minify -> Condense -> Param Prune -> Budget -> Draft-07) | |
| +----------------------------------+-------------------------------+ |
| | |
| v |
| +------------------------------------------------------------------+ |
| | Process Supervisor (Windows Job Objects / POSIX Process Groups) | |
| | & Outbound SSE / Streamable HTTP Client Transport | |
| +----------------------------------+-------------------------------+ |
+-------------------------------------+----------------------------------+
|
Outbound Bidirectional JSON-RPC 2.0
|
v
+-----------------------+-------------------------+-------------------+
| Upstream Server 1 | Upstream Server 2 | Upstream Server 3 |
| (GitHub MCP - stdio) | (Postgres MCP - stdio) | (Remote MCP - SSE)|
+-----------------------+-------------------------+-------------------+
graph TD
Client["AI Client (Claude / Cursor / Windsurf)"] -->|Stdio / SSE / Streamable HTTP| TP["ToolPrune Proxy Gateway"]
subgraph TP_Engine ["ToolPrune Core Engine"]
Buffer["Bounded Framing (64KiB-10MiB)"] --> Router["Dynamic Router & Multiplexer"]
Router --> Filter["Adaptive StreamFilter & Pre-Bind Validator"]
Filter --> Window["LRU Active Tool Window & Meta-Tools"]
Window --> Pruner["Schema Pruner & 5-Pass Budget Engine"]
Pruner --> Sup["Supervisor & Outbound SSE/HTTP Client"]
end
TP --> Buffer
Sup -->|Supervised Subprocess| UP1["Upstream: GitHub (@mcp/server-github)"]
Sup -->|Supervised Subprocess| UP2["Upstream: SQLite (mcp-server-sqlite)"]
Sup -->|HTTP / SSE Stream| UP3["Upstream: Remote MCP Server"]
flowchart LR
T0["Tier 0: Raw Unpruned (3,596 tok)"] -->|Strip $schema, unused $defs| T1["Tier 1: Lossless"]
T1 -->|Condense prose & regex| T2["Tier 2: Compact"]
T2 -->|Strip self-evident params| T3["Tier 3: Aggressive (2,167 tok)"]
T3 -->|5-pass budget compactor| TB["RFC #1 Budget (282 tok, -92.2%)"]
TB -->|Catalog + JIT hydration| T4["Tier 4: Dynamic (127 tok, -96.5%)"]
- Full JSON Schema & Modern MCP Fidelity: Preserves
$defs,definitions,$ref,anyOf,oneOf,allOf,not,patternProperties,dependentSchemas,dependentRequired,minProperties/maxProperties, and MCPtitle,outputSchema,annotations(readOnlyHint,destructiveHint,idempotentHint,openWorldHint), andiconsso Pydantic and Zod schemas never break. - Windows Job Object & POSIX Group Supervision: Places upstream subprocesses inside a Windows Job Object (
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) or POSIX process group and drainsstdout/stderrpipes completely prior toWait(), preventing zombie leaks and pipe deadlocks. - Bounded Memory Framing: Uses chunked line framing starting at
64 KiBwith a strict10 MiBpre-allocation ceiling, defeating unbounded line OOM attacks while keeping idle per-upstream memory minimal. - Localhost HTTP Hardening: Validates
HostandOriginheaders on local SSE and Streamable HTTP (/mcp) listeners to block DNS-rebinding and cross-origin browser attacks.
Downloads the platform binary release and verifies its SHA-256 digest against checksums.txt:
macOS & Linux (Bash / Zsh):
curl -fsSL https://raw.githubusercontent.com/aloneangel45/toolprune/main/install.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/aloneangel45/toolprune/main/install.ps1 | iexdocker run -i --rm ghcr.io/aloneangel45/toolprune:latest proxy --preset balancedRequires Go 1.23+:
go install github.com/aloneangel45/toolprune/cmd/toolprune@latestgit clone https://github.com/aloneangel45/toolprune.git
cd toolprune
go build -o toolprune ./cmd/toolprune
toolprune versionPrepend toolprune proxy -- to any existing MCP server command without creating a config file:
# Wrap GitHub MCP server with balanced condensation
toolprune proxy --preset=balanced -- npx -y @modelcontextprotocol/server-github
# Wrap SQLite MCP server with dynamic catalog windowing (Tier 4)
toolprune proxy --preset=dynamic -- uvx mcp-server-sqlite --db-path ./test.dbCap total serialized tools/list schema tokens across any upstream server while getting automatic pre-binding argument self-correction:
toolprune proxy --max-schema-tokens=800 -- npx -y @modelcontextprotocol/server-githubCombine multiple stdio and remote HTTP/SSE MCP servers behind a single namespaced gateway:
# 1. Initialize toolprune.yaml
toolprune config init
# 2. Validate configuration syntax and upstream definitions
toolprune config validate
# 3. Inspect active routes under a security profile
toolprune routes --profile read_only
# 4. Launch over stdio or local SSE + Streamable HTTP (/sse and /mcp)
toolprune proxy --config toolprune.yaml
toolprune proxy --config toolprune.yaml --transport sse --port 8080Automatically wrap existing servers in claude_desktop_config.json (creates a .bak backup first):
# Preview changes without modifying disk
toolprune config patch-claude --dry-run
# Apply wrapper preset to Claude Desktop config
toolprune config patch-claude --preset strictOr configure manually:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
{
"mcpServers": {
"github": {
"command": "toolprune",
"args": [
"proxy",
"--preset=strict",
"--max-schema-tokens=1200",
"--",
"npx",
"-y",
"@modelcontextprotocol/server-github"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_yourTokenHere"
}
}
}
}Generate .cursor/mcp.json directly:
toolprune preset generate --client cursor --output .cursor/mcp.jsonOr connect Cursor to a shared local SSE / Streamable HTTP daemon:
toolprune proxy --transport=sse --port=8080 --config=toolprune.yaml{
"mcpServers": {
"toolprune_http": {
"url": "http://127.0.0.1:8080/mcp"
}
}
}toolprune preset generate --client windsurf --output ~/.codeium/windsurf/mcp_config.json
toolprune preset validate --client windsurf --input ~/.codeium/windsurf/mcp_config.jsonStarts the ToolPrune proxy in zero-config wrapper mode or multi-upstream gateway mode.
Usage:
toolprune proxy [flags] [-- <upstream_cmd> [args...]]
Flags:
-c, --config string Path to toolprune.yaml configuration file
-m, --mode string Pruning tier: none, lossless, compact, aggressive, dynamic (default "aggressive")
--preset string Preset alias: fast (lossless), balanced (compact), strict (aggressive), dynamic
--max-schema-tokens int Hard token budget for tools/list schemas (0 = disabled; env TOOLPRUNE_MAX_SCHEMA_TOKENS)
-t, --transport string Inbound transport protocol: stdio, sse (serves /sse, /messages, and /mcp) (default "stdio")
--host string Listen host address for SSE/HTTP transport (default "127.0.0.1")
-p, --port int Listen port for SSE/HTTP transport (default 8080)
--profile string Active routing profile: full, read_only, dev, minimal (default "full")
--namespace string Namespacing strategy: auto, always, on_collision, none (default "auto")
--timeout duration Request timeout duration (default 1m0s)
--stats Print session token savings summary to stderr on exit (default true)
--log-level string Log level: debug, info, warn, error (default "info")
Evaluates exact token metrics and reduction percentages across Tiers 0-4 and optional token budgets.
Usage:
toolprune bench [flags] [-- <upstream_cmd> [args...]]
Flags:
-f, --file string Path to JSON schema fixtures file
--format string Output format: table, markdown, json, csv (default "table")
--model string Tokenizer engine: cl100k_base, calibrated (default "cl100k_base")
--max-schema-tokens int Evaluate an additional RFC #1 token budget pass
-v, --verbose Display per-parameter token breakdown
Inspects upstream servers, namespaced tools, active profile rules, and filter matches.
Usage:
toolprune routes [flags]
Flags:
--filter string Filter upstream/tool names matching substring or regex
--profile string Evaluate routing table under specific profile (e.g. read_only, minimal)
--json Output routing table in machine-readable JSON
Configuration initialization, validation, and client patching utilities.
Usage:
toolprune config [command]
Available Commands:
init Initialize a new toolprune.yaml configuration file with defaults
validate Validate toolprune.yaml syntax, schema rules, and upstream definitions
patch-claude Wrap existing Claude Desktop MCP servers with ToolPrune (--dry-run, --preset, --mode, --path)
Generates and validates MCP client presets for Cursor and Windsurf while preserving existing sibling keys.
Usage:
toolprune preset [command]
Available Commands:
generate Generate or merge a client MCP config preset for ToolPrune
validate Validate a client MCP config file (supports command stdio and url SSE/HTTP servers)
Displays version, Git commit, build timestamp, and Go runtime details.
Environment variables (${GITHUB_TOKEN} or ${DB_PATH:-./default.db}) expand automatically on load.
version: "1"
server:
transport: "stdio" # "stdio" or "sse" (serves /sse, /messages, and Streamable HTTP /mcp)
host: "127.0.0.1"
port: 8080
base_path: ""
cors_origins: []
timeout: "60s"
inject_proxy_info: true
pruning:
tier: "aggressive" # none | lossless | compact | aggressive | dynamic
max_schema_tokens: 0 # RFC #1 token cap (0 = disabled; env TOOLPRUNE_MAX_SCHEMA_TOKENS)
max_description_length: 120
max_param_desc_length: 80
strip_regex: true
max_regex_length: 25
strip_self_evident: true
strict_draft07: true
strip_code_blocks: true
strip_html_tags: true
routing:
default_profile: "full" # full | read_only | dev | minimal | custom
namespace_mode: "auto" # auto | always | on_collision | none
active_window_size: 15
auto_promote: true
pinned_tools:
- "toolprune_search_tools"
- "toolprune_activate_tools"
- "toolprune_get_tool_schema"
- "toolprune_invoke"
global_exclude:
- "*_internal_*"
upstreams:
- id: "github"
prefix: "gh"
transport: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_TOKEN}"
- id: "remote_agent"
prefix: "remote"
transport: "sse"
url: "http://${REMOTE_HOST:-127.0.0.1}:${REMOTE_PORT:-9000}/mcp"
timeout: "30s"- Fork the repository
- Create a feature branch (
git checkout -b feat/your-feature) - Commit with Conventional Commits (
git commit -m 'feat(scope): summary') - Run static analysis, tests, and benchmarks (
go vet ./... && go test -v ./...) - Open a Pull Request
Distributed under the MIT License. See LICENSE for details.
