mcp-pro
v0.1.1
Published
The smart cache for MCP tool calls. Observe -> Report -> Cache.
Downloads
200
Maintainers
Readme
mcp-pro
The smart cache for MCP tool calls. Observe → Report → Cache.
Agentic workflows burn money on redundant tool calls. The same file read five times per session. The same search query, rephrased slightly, issued across every session. mcp-pro is a transparent MCP proxy that first proves how much you're wasting, then eliminates it — safely, per tool, with policies learned from your actual traffic.
# Wrap any MCP server. Zero config. Nothing cached yet — just watching.
npx mcp-pro -- npx -y @some-org/some-mcp-server# One week later:
npx mcp-pro report
┌─ mcp-pro Report ─ last 7 days ───────────────────────────────┐
│ 4,812 tool calls observed across 214 sessions │
│ │
│ Redundant calls: 1,127 (23.4%) │
│ within a session: 891 │
│ across sessions: 236 │
│ Wasted upstream time: 41.2 min │
│ │
│ Projected if caching were enabled hit rate stale rate │
│ ttl:60s 11.0% 0.4% │
│ ttl:300s 19.2% 1.9% │
│ learned 26.1% 0.2% │
│ │
│ Per tool calls repeats stability │
│ github/read_file 1,204 812 99.1% │
│ │
│ Top repeated chain (61.0% of sessions) │
│ list_repos → get_repo → list_files → read_file │
│ │
│ Safe to cache, by the numbers │
│ github/read_file 94.4% hit / 0.0% stale │
│ readOnly+idempotent, 99.1% stable across 812 repeats │
│ │
│ mcp-pro enable github/read_file │
└──────────────────────────────────────────────────────────────┘Every hit rate is printed with the stale-hit rate beside it. A cache that answers more often by answering wrongly is worse than no cache, so the two numbers are never separated.
Then flip caching on, tool by tool, with the numbers in front of you.
Why mcp-pro?
Exact-match caching proxies for MCP exist. mcp-pro is different in three ways:
- It measures before it caches. Shadow mode replays your real traffic through a simulated cache and shows you the savings curve before a single response is served from cache. No trust required up front.
- It learns per-tool volatility. Instead of one global TTL, mcp-pro observes how often each tool's responses actually change and derives TTLs the way DNS resolvers estimate record lifetimes. A tool that never changes earns hours; a tool that changes every call is pinned to zero — automatically.
- It sees hits that hashing can't. Agents rephrase.
search("optimal vLLM settings")andsearch("best vLLM config")are the same call to you, but different bytes to an exact-match cache. mcp-pro's field-scoped semantic matching (opt-in, experimental) recovers these.
Features
🔎 Observe — the interception log
- Transparent stdio/HTTP proxy: prepend
mcp-pro --to any MCP server command, nothing else changes - Logs every
tools/callwith canonicalized argument hashes, response hashes, latency, size, and token estimates - Captures MCP tool annotations (
readOnlyHint,idempotentHint,destructiveHint) at handshake - Local-first: SQLite + JSONL on disk, no telemetry, no network calls
📊 Report — redundancy analytics
- Exact repeats, intra-session and cross-session, with wasted-spend estimates
- Repeated call chains mined from session sequences — surface the workflows your agents run over and over
- Per-tool volatility table — empirical proof of which tools are temporally static
- Counterfactual replay — projected hit rate and staleness risk at multiple TTLs, including the learned policy
- Semantic duplicate clustering — near-identical natural-language queries grouped across sessions (analytics only; zero correctness risk)
- Approximate call-graph construction via data-flow matching (response-of-A appears in args-of-B)
⚡ Cache — annotation-aware smart policy
- Safe by default: only tools marked read-only + idempotent are eligible; destructive tools are never cached and act as invalidation triggers
- Learned TTLs per (tool, argument-shape), updated continuously via stale-while-revalidate background refreshes
- Two-level estimation: exact-args volatility when observations allow, argument-shape priors when they don't
- Explicit invalidation graph in config (
create_filebustslist_files), plus suggested invalidation edges mined from your log - Per-tool opt-in: enable caching only where the report proved it's safe
- Stale-on-error fallback: upstream down? Serve the last known good response
🧬 Semantic matching (experimental, opt-in)
- Field-scoped: NL-text fields matched by embedding similarity; paths, IDs, numbers, enums must match exactly
- Local embeddings, in-process — no external API dependency. The model is not
bundled: drop
model.onnxandvocab.txtinto~/.mcp-pro/models/bge-small-en-v1.5and install the optionalonnxruntime-node. Missing either, semantic matching stays off and says so - High-threshold direct hits; the gray band (≈0.90–0.97) is currently treated as a miss — the cheap equivalence verifier that would resolve it is not built yet, and guessing is the wrong direction to fail in
- Lexical guards for negation, opposed terms and entity traps. Without them a
sentence encoder scores
flights to Delhiandflights from Delhiat 1.000 and serves one for the other;npm run bench:semanticshows exactly that - Ships after the analytics layer has measured your would-be semantic hit
rate on your own traffic —
mcp-pro reportestimates it before anything is enabled
Quickstart
# 1. Wrap your server (in claude_desktop_config.json, Claude Code, or any MCP client)
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "mcp-pro", "--", "npx", "-y", "@modelcontextprotocol/server-github"]
}
}
}
# 2. Use your agent normally for a few days.
# 3. See what you're wasting.
npx mcp-pro report
# 4. Enable caching where the data says it's safe.
npx mcp-pro enable github/get_repo github/read_fileInstalled globally (npm install -g mcp-pro), same commands without npx:
mcp-pro -- npx -y @modelcontextprotocol/server-github
mcp-pro report
mcp-pro enable github/get_repo github/read_fileConfiguration
mcp-pro.yaml (all optional — shadow mode needs nothing):
mode: cache # observe | cache
store: ~/.mcp-pro
defaults:
# The share of cache hits allowed to serve a value the upstream had already
# changed. This is the correctness target: every learned TTL is the largest
# value whose estimated staleness stays under it.
stale_budget: 0.01
tools:
github/read_file:
cache: learned # off | learned | ttl:300000 (milliseconds)
github/search_code:
cache: learned
semantic:
field: query
threshold: 0.95
invalidations:
- on: github/create_or_update_file
bust: [github/read_file, github/list_files]
limits:
max_entry_bytes: 1048576See mcp-pro.example.yaml for every option with its default.
How it works
MCP client ──stdio/http──▶ mcp-pro proxy ──▶ upstream MCP server
│
┌─────────┴──────────┐
│ call log (SQLite) │
│ policy engine │
│ cache store │
│ vector index │
└────────────────────┘mcp-pro speaks plain MCP on both sides. Tool schemas, resources, and prompts pass through untouched. Only tools/call responses are ever candidates for caching, and only under the policy rules above.
What mcp-pro will not do
- Cache a tool that isn't marked (or proven) read-only and idempotent
- Serve a semantic hit when any non-text argument field differs
- Send your tool traffic anywhere. Everything is local.
Roadmap
- [x] v0.1 — transparent proxy + call log +
report(exact repeats, chains, volatility, counterfactual replay) - [x] v0.2 — caching engine: annotations, learned TTLs, stale-while-revalidate, invalidation config
- [x] v0.3 — semantic duplicate clustering in
report - [x] v0.4 —
semantic: experimentalfield-scoped cache lookups. Needs a local ONNX model at~/.mcp-pro/models/bge-small-en-v1.5(model.onnx+vocab.txt); without it the feature reports itself as disabled rather than falling back to a weaker score under the same thresholds - [x] v0.5 — multi-server aggregation: one report across every wrapped server
- [x] — context-level dedup behind
context_dedup: returns a compact reference for a repeat result instead of re-injecting the bytes. The only path to genuine token savings, and the only setting that changes what the model sees, so it is off by default - [ ] — shared team cache backend (Redis). Deferred deliberately: a shared cache is an outbound network dependency, and invariant 5 ("local only") should be carved out on purpose rather than quietly
Comparison
| | exact-match proxies | API gateways | mcp-pro | |---|---|---|---| | Zero-config transparent proxy | ✅ | ❌ | ✅ | | Shadow mode / savings report before caching | ❌ | ❌ | ✅ | | Learned per-tool TTLs | ❌ (static TTL) | ❌ (static TTL) | ✅ | | Annotation-aware safety policy | ❌ | partial | ✅ | | Write→read invalidation graph | ❌ | ❌ | ✅ | | Semantic (paraphrase) hits | ❌ | ❌ | ✅ opt-in | | Redundant-chain analytics | ❌ | ❌ | ✅ |
License
Apache 2.0
