@hassanalitamam/mcp-budget
v0.1.0
Published
Transparent MCP proxy: budget-aware result shaping with zero info loss. Ships Claude Code / Cursor / Gemini plugins and an agent-guidance skills pack.
Maintainers
Readme
mcp-budget
A transparent MCP proxy that shrinks oversized tool results — without losing access to the full content.
Quickstart · How it works · Skills & integrations · Savings report · Host configs · FAQ
📦 What is this?
mcp-budget is a local proxy that wraps any MCP (Model Context Protocol) server and reshapes oversized tool results to fit a per-call token budget — so your AI agent (Claude Code, Cursor, VS Code, Cline, Gemini CLI) doesn't burn its context window on a single tool call.
Without losing data. Every full upstream result is stored locally (content-addressed by BLAKE3) before any shaping runs. The agent retrieves anything missed via a new fetch_result(result_id, query?) tool — no upstream re-run, safe for mutating tools (send email, create issue, browser actions).
With agent guidance built in. The proxy ships a Claude Code / Cursor / Gemini plugin and a skills pack that teaches the agent how to read the _meta.mcp.budget envelope, call fetch_result with the cheapest slice that answers the question, and recover from the documented error modes.
"MCP Discussion #315 was the right idea, rejected for the wrong reason — here it is as a proxy."
mcp-budget is complementary to mcp-compressor: compressor shrinks tool schemas (tools/list bloat), budget shrinks tool results (tools/call bloat). Run both for max savings.
🎯 The problem
graph LR
subgraph Without["WITHOUT mcp-budget"]
A1[Agent calls read_file] --> B1["MCP server returns<br/>14,604 tokens"]
B1 --> C1["Agent context:<br/>14,604 tokens burned"]
style C1 fill:#fee
end
subgraph With["WITH mcp-budget"]
A2[Agent calls read_file] --> P[mcp-budget proxy]
P --> B2["MCP server returns<br/>14,604 tokens"]
B2 --> P
P --> S[("Local store<br/>BLAKE3-addressed")]
P --> C2["Agent context:<br/>499 tokens (-96.6%)"]
style C2 fill:#efe
end⚡ 60-second install
# Wrap any MCP server with mcp-budget
npx @hassanalitamam/mcp-budget --default-budget 8000 -- npx -y @modelcontextprotocol/server-fetchEverything before -- is mcp-budget config; everything after is the upstream server command.
📊 Benchmark results
Verified against the real @modelcontextprotocol/server-filesystem, judged by OpenAI gpt-5.4-mini, tiktoken cl100k_base, budget = 500 tokens. Full analysis: docs/BENCH_RESULTS.md.
| Server | Tasks | Shaped | Median reduction | p90 | Max | Success delta | Errors | |----------------|------:|-------:|-----------------:|----:|----:|--------------:|-------:| | Filesystem MCP | 20 | 9 | 58.4% | 83.8% | 96.6% | +0.0 pp | 0 / 40 |
- 🏆 Largest single win:
fs-008— 14,604 → 499 tokens (96.6% saved) - ✅ Zero errors on 20/20 tasks across both direct + proxied paths
- ✅ +0.0 pp success delta — external LLM judge scored every shaped result identically to its direct counterpart
- ✅ Zero info loss — roundtrip tests prove byte-for-byte recoverability via
fetch_result
🛡️ How it works
sequenceDiagram
participant Agent
participant Proxy as mcp-budget
participant Upstream
participant Store as Local store
Agent->>Proxy: tools/call read_text_file
Proxy->>Upstream: forward
Upstream-->>Proxy: full result (14,604 tokens)
Proxy->>Store: put(full, result_id)
Note over Proxy: shape to fit budget
Proxy-->>Agent: shaped (499 tokens) + result_id
Note over Agent: needs more?
Agent->>Proxy: fetch_result(result_id)
Proxy->>Store: get(result_id)
Store-->>Proxy: original (14,604 tokens)
Proxy-->>Agent: full payload — no upstream re-runFour shaping strategies tried in order (per-tool configurable):
| Strategy | When | What it does |
|---|---|---|
| structured | Tool has an outputSchema + structuredContent | Project highest-priority fields that fit the budget. Schema-validated. |
| verbatim-trim | Text content | Keep head (60%) + tail (30%) of text, mark the elided middle. |
| head-preview | Text content over budget | Keep a coherent head + attach a mcp-budget:// ResourceLink to the rest. |
| resource-link | Guaranteed fallback | Replace content with a ResourceLink to the full payload. Agent calls fetch_result to retrieve. |
🧠 Skills & integrations
mcp-budget ships a plugin and skills pack so AI agents automatically learn to use the proxy efficiently — read _meta.mcp.budget, call fetch_result(id, query) with the cheapest slice that answers the question, and handle the documented error reasons (store_entry_missing, no_store_mode, race_eviction, invalid_query, arguments_unavailable).
Hosts without a skills mechanism still benefit because the fetch_result tool description itself has been enriched with when-to-use guidance (visible in any MCP client's tool list).
Claude Code
/plugin marketplace add hassanalitamam/mcp-budget
/plugin install mcp-budget@mcp-budget-pluginsAfter install, five skills appear under /skills:
mcp-budget— top-level overview; loads on any_meta.mcp.budgetmention.fetch-result-recovery— when and how to callfetch_resultwith the rightSliceQueryvariant.budget-hints— per-call_meta.mcp.budget/maxResultTokensoperator hints.troubleshooting— the documented error reasons + remediation.savings-report— driving thesavingssubcommand and reading the net-vs-gross output.
Cursor
Add the workspace plugin manifest at .cursor-plugin/plugin.json to your .cursor/plugins/ directory. Cursor surfaces the same skill catalog as Claude Code under its plugin context UI.
Gemini CLI
gemini extensions install --auto-update https://github.com/hassanalitamam/mcp-budgetAGENT_GUIDE.md (mirrors the skills-pack guidance in a single file) is auto-loaded via .gemini/settings.json so the Gemini model gains the same when-to-call-fetch_result instincts as the Claude / Cursor flows.
MCP Registry
server.json lists the proxy at io.github.hassanalitamam/mcp-budget so MCP-Registry-aware hosts (LangGraph, Continue's MCP catalogue, …) can discover and install the proxy without any host-specific manifest.
📈 Measuring your savings
Want to see how much the proxy is saving you? Enable opt-in local telemetry and run the savings report.
# 1. Start the proxy with telemetry on (or set MCP_BUDGET_TELEMETRY=events)
npx @hassanalitamam/mcp-budget --telemetry events --default-budget 5000 -- <upstream-server>
# 2. Use the proxy normally for a while (drive your agent / MCP client).
# 3. Show the report
npx -p @hassanalitamam/mcp-budget mcp-budget-cli savingsExample output:
Savings — last 7 days (net)
─────────────────────────────────────────────────────────────────
calls gross redeemed net
By tool
read_text_file 127 453,120 12,400 440,720
fetch 43 121,800 5,200 116,600
search_files 18 28,400 0 28,400
─────────────────────────────────────────────────────────────────
Totals 188 603,320 17,600 585,720
Net savings ratio: 97.1% of gross retained after fetch_result redemptions.
Tokenizer: tiktoken-cl100k_base. Window: 2026-05-14 → 2026-05-21 (UTC).Net vs gross — why the honest number subtracts
gross_saved is the sum of original_tokens − shaped_tokens over every shape event. But when the agent calls fetch_result(id, {type: "full"}) to pull back the content that was shaped away, those tokens are re-spent. The report subtracts them as redeemed, leaving net_saved = gross − redeemed — the only number that reflects what the proxy actually saved your context window. The --gross flag falls back to the marketing number that ignores fetch-redeems if you need it.
savings subcommand flags
| Flag | Values | Default | Meaning |
|---|---|---|---|
| --by | tool | server | strategy | day | tool | Grouping key for the breakdown rows. |
| --since | duration (e.g. 24h, 7d, 30d) | 7d | Lookback window. |
| --top | integer | 20 | Maximum rows per group. |
| --gross | flag | off | Show gross savings only (ignore fetch_redeem). |
| --json | flag | off | Emit machine-readable JSON instead of the text table. |
Privacy
All telemetry is local. Nothing leaves your machine. Honors DO_NOT_TRACK=1 (W3C universal opt-out, takes precedence over every other setting). Opt out anytime by removing the --telemetry events flag and wiping the call_events rows (or mcp-budget-cli store clear --yes for a full reset).
🔌 Host configs
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["@hassanalitamam/mcp-budget", "--default-budget", "8000", "--", "npx", "-y", "@modelcontextprotocol/server-fetch"]
}
}
}{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["@hassanalitamam/mcp-budget", "--default-budget", "8000", "--", "npx", "-y", "@modelcontextprotocol/server-fetch"]
}
}
}{
"servers": {
"fetch": {
"type": "stdio",
"command": "npx",
"args": ["@hassanalitamam/mcp-budget", "--default-budget", "8000", "--", "npx", "-y", "@modelcontextprotocol/server-fetch"]
}
}
}{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["@hassanalitamam/mcp-budget", "--default-budget", "8000", "--", "npx", "-y", "@modelcontextprotocol/server-fetch"]
}
}
}{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["@hassanalitamam/mcp-budget", "--default-budget", "8000", "--", "npx", "-y", "@modelcontextprotocol/server-fetch"]
}
}
}Canonical copy-paste files live in examples/.
⚙️ Configuration
CLI flags also accept env vars (MCP_BUDGET_DEFAULT_BUDGET=…, etc.) or a policies.toml file at the platform config directory.
| Flag | Default | Notes |
|---------------------|--------------------------------------------------------------------|----------------------------------------------------|
| --default-budget | 8000 | Tokens per response. |
| --prefer-shape | structured,verbatim-trim,head-preview,resource-link | Strategy order — first that fits the budget wins. |
| --policy-file | platform config dir | Per-tool overrides (TOML). |
| --store-path | platform data dir | SQLite + content-addressed blobs. |
| --store-ttl-days | 7 | How long to keep stored results. |
| --store-cap-mb | 1024 | LRU cap (1 GiB default). |
| --no-store | off | Privacy / dry-run; disables fetch_result. |
| --log-file | unset | Required to enable logging in stdio mode. |
| --log-level | warn | error \| warn \| info \| debug. |
| --tokenizer | auto | auto \| tiktoken \| anthropic \| approx. |
| --model-hint | unset | Drives tokenizer resolver (e.g. claude-sonnet-4).|
| --transport | stdio | stdio \| sse \| http. |
| --telemetry | off | off \| events — opt-in local savings tracking. |
Example policies.toml (per-tool overrides):
[defaults]
budget = 8000
prefer_shape = ["structured", "verbatim-trim", "head-preview", "resource-link"]
[servers."github-mcp"]
budget = 4000
[tools."playwright::browser_snapshot"]
budget = 6000
prefer_shape = ["head-preview", "resource-link"]
[tools."github-mcp::list_*"]
budget = 3000Precedence: per-request _meta.mcp.budget/* > per-tool exact > per-tool glob (longest pattern) > per-server > [defaults] > built-in.
🛠️ Subcommands
mcp-budget-cli store stats # store size, entry count, oldest entry
mcp-budget-cli store gc # force TTL+LRU eviction now
mcp-budget-cli store clear --yes # wipe all entries
mcp-budget-cli store get <result_id> # pretty-print a stored result
mcp-budget-cli doctor # check tokenizer + store + runtime
mcp-budget-cli savings # token savings report (see above)
mcp-budget --print-config -- <cmd> # dump effective config as JSON🌍 Positioning vs alternatives
| Project | Layer | Scope | Complement? | When to use | |---|---|---|---|---| | mcp-budget (this) | proxy | tool result shaping + retrieval | — | huge tool outputs | | mcp-compressor | proxy | tool schema compression | yes ✅ | huge tool lists | | mcp-cache | proxy | response caching | yes ✅ | repeated identical calls | | Morph Flash Compact | host plugin | per-turn context compaction | yes ✅ | long conversations | | Better Stack Context Mode | host feature | session-level context budgeting | yes ✅ | long conversations | | Cloudflare Code Mode | runtime | code-execution-driven tool wrapping | yes ✅ | client supports code execution | | Discussion #315 | protocol spec | per-call size negotiation (rejected) | — | will not ship at protocol layer |
❓ FAQ
Will this break my agent?
No. The benchmark shows +0.0 pp success delta when judged by an external LLM across all 20 tasks. The shaped result keeps enough information for the agent to act, and fetch_result is always one call away. The skills pack teaches agents the recovery pattern explicitly.
Does it cost anything to run?
No. Pure local-first: no cloud, no telemetry by default. The opt-in --telemetry events mode also stays 100% local — nothing leaves your machine. An OpenAI key is only needed if you run the bench harness yourself.
What if the local store fills up?
1 GiB LRU cap by default + 7-day TTL. Both configurable. The store is content-addressed by BLAKE3 — identical calls deduplicate to a single blob. A 5-minute sticky window protects in-flight fetch_result calls from eviction.
What about mutating tools (send email, create issue, browser actions)?
Safe. fetch_result reads from the local store and never re-runs the upstream tool. Retries don't double-send. On a miss, the proxy returns isError=true with the original arguments embedded so the agent can decide whether re-running is safe.
What's in _meta.mcp.budget on every response?
original_tokens, shaped_tokens, strategy, result_id, tokenizer_used, model_hint, reason, persistent_storage. Use it to debug and to call fetch_result(result_id) when you need the full payload. The skills pack walks through every field with examples.
Can I use a different tokenizer?
Yes. tiktoken cl100k_base for OpenAI families (default), Anthropic SDK count_tokens for Claude (requires ANTHROPIC_API_KEY), or approx (chars/4) as a fallback.
What does the --telemetry events flag store?
One row per shape event and one per fetch_result redemption: tool name, server name, strategy, original / shaped token counts, tokenizer, model hint, timestamp. No tool arguments, no result content, no user data. Stored in a local SQLite table you can wipe at any time. DO_NOT_TRACK=1 forces it off regardless.
📚 Documentation
- Architecture — component diagram, data-flow invariants
- Design decisions — why a proxy, why content-addressed storage, why opt-in telemetry
- User guide — 5-minute end-user overview with diagrams
- Agent guide — the runtime guide auto-loaded by Gemini and mirrored in the skills pack
- Bench results — verified Phase-6 numbers, per-task breakdown
- Spec — token-conscious skills pack — design behind the v1.0 skills + plugin layout
🗂️ Repository layout
mcp-budget/
├── src/ # proxy implementation
├── tests/ # unit + integration tests
├── skills/ # Claude Code skills pack (5 skills + references)
├── docs/ # user / architecture / design docs
├── examples/ # copy-paste host configs
├── bench/ # benchmark harness + fixtures
├── .claude-plugin/ # Claude Code plugin manifest
├── .cursor-plugin/ # Cursor plugin manifest
├── .gemini/ # Gemini context auto-injection
├── server.json # MCP Registry listing
├── AGENT_GUIDE.md # agent runtime guide
└── legacy/ # historical Python implementation + planning docsThe legacy/python/ directory preserves the original reference implementation (Python + FastMCP 3.x) that defined the algorithm, plus the audit and planning documents from the project's design phase. It is kept for historical reference; active development happens at the root.
🤝 Contributing
See CONTRIBUTING.md. Issues and PRs welcome — please run the test suite before submitting.
📜 License
📋 Changelog
CHANGELOG.md follows Keep a Changelog 1.1.
