mcp-token-optimizer
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-token-optimizercompress that last tool response and tell me the token savings"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
⚡ mcp-token-optimizer
Stop paying for tool-response noise.
A self-hosted proxy between your AI agent and its MCP servers that shrinks every tool response — pruning, TOON encoding and local-LLM summarization — without changing what the agent can do.
Quick start · How it works · Documentation · Testing · Contributing
┌────────────────────── mcp-token-optimizer ──────────────────────┐
AI agent ──request──►│ pass-through │──► real MCP server
(Copilot, │ │ (stdio process
Claude Code, …) ◄───│ prune → JSON/TOON → normalize → summarize (local Ollama) │◄── or remote HTTP)
│ guardrails · audit log · mto_expand (get the original) │
└──────────────────────────────────────────────────────────────────┘
everything runs on YOUR machineWhy
Every token an MCP tool returns is paid for, and fills the model's context window. Real tool output is mostly noise: null fields, hypermedia URLs, avatar and node ids, the same block repeated in two fields, ANSI and progress-bar spam, boilerplate prose. mto removes that noise before the model sees it, keeps an exact copy of anything it removed, and tells the agent how to get it back.
Related MCP server: Delta-MCP
Results
Measured, reproducible, no LLM involved (npm run smoke, TESTING.md §2). Token counts are a model-agnostic estimate.
Tool response | Original | Sent to the agent | Saved |
GitHub issue list (40 issues, REST JSON) | 37,355 | 13,244 | 64.5% |
Kubernetes describe + manifest (duplicated content) | 3,637 | 1,935 | 46.8% |
CI build log (ANSI, repeated lines) | 4,926 | 78 | 98.4% |
HTML runbook page | 1,198 | 863 | 28.0% |
Real Kubernetes ConfigMap through a live MCP server, Copilot A/B run | 24,616 | 12,617 | 48.7% |
In that live A/B run, the three files the agent wrote from the optimized result were byte-identical to the original data (checked against a pre-optimization capture).
Highlights
🧾 Lossless first | JSON ↔ TOON is emitted only after a verified round-trip; duplicate blocks become pointers; the canonical copy is never touched |
🛟 Fail-open, never bigger | any error, timeout or Ollama outage forwards the original; an optimized result is used only if it is strictly cheaper |
🔎 | notes name exactly what was pruned; the agent can fetch the exact original (outline, JSON path, grep) on demand |
🌗 Shadow mode | measure real savings while forwarding the originals, so you can roll out with zero risk |
🔌 Any MCP client | stdio wrapper and local HTTP/SSE proxy; installs into VS Code Copilot, Claude, Cursor, Windsurf, Gemini CLI, Cline, Roo (JSONC-safe, reversible) |
🧠 Local summarization | your Ollama model, your parameters, a hard latency budget and a fact-coverage guard; code and file reads are never summarized |
🔐 Private by design | binds to |
🧪 Measurable | capture, bench, compare and stats built in; 42 tests and a 24-check end-to-end smoke test |
Quick start
git clone https://github.com/shubhamcodess/mcp-token-optimizer.git && cd mcp-token-optimizer
npm install && npm run build # Node 22+ recommended
node dist/cli.js doctor # checks config, Ollama and model
node dist/cli.js discover # lists the MCP servers it found (read-only)
node dist/cli.js init --only <server> --apply # wrap one server; undo: node dist/cli.js uninstall --applyNo agent handy? npm run demo shows a tool call through the optimizer with sample data. Full walkthrough: 1. Self-host in 15 minutes.
Roll out safely: set
mode: shadowfirst. The proxy then measures savings but forwards the original responses, so nothing changes for your agent until you switch toactive.
Documentation
🚀 Get started | |
🧠 Understand | |
🎛️ Tune | 6. Profiles · 7. Output formats · 8. Summarization · 9. |
📖 Reference | 12. Configuration reference · 13. CLI reference · 14. Files & env vars |
🛟 Operate | 15. Troubleshooting · 16. Security & privacy · 17. Limitations & verification status |
🤝 Project | 18. Development · TESTING.md · CONTRIBUTING.md · CHANGELOG.md · SECURITY.md · License |
1. Self-host in 15 minutes
Install it, run the proxy, wire your agent, and undo it. Everything stays on your machine.
Everything runs locally. Nothing is sent to any cloud service: the only network calls are to your own MCP servers and to Ollama on 127.0.0.1.
1.1 Requirements
Need | Version | Why |
Node.js | 22.15+ recommended (20+ works) | 22.15+ lets |
Ollama | any recent | only for summarization. Pruning + TOON need no LLM |
An MCP-capable agent | – | VS Code Copilot agent mode was used to develop this |
node --version # v22.15.0 or newer
ollama --version # optional but recommended1.2 Install
Pick one. All give you the same mto CLI.
A. Clone and build (contributors, or if you always want the latest)
git clone https://github.com/shubhamcodess/mcp-token-optimizer.git
cd mcp-token-optimizer
npm install
npm run build
node dist/cli.js --version # prints 0.1.0B. Install a prebuilt release tarball (no clone, no build step)
Download mcp-token-optimizer-<version>.tgz from the repository's Releases page (each tagged release attaches one, built by CI), then:
npm install -g ./mcp-token-optimizer-0.1.0.tgz
mto --versionYou can also build the same artifact yourself: npm pack in a clone produces mcp-token-optimizer-<version>.tgz.
npm install -g github:shubhamcodess/mcp-token-optimizeris not supported: the package needs a TypeScript build, and npm does not install dev dependencies when building git dependencies. Use A or B. (Publishing to the npm registry is on the roadmap.)
With B the mto command is on your PATH. With A, either run node dist/cli.js … or link it:
npm link # then: mto --version (undo with: npm unlink -g mcp-token-optimizer)The rest of this document writes node dist/cli.js …. Use mto … if it is on your PATH.
1.3 Pull a local model (only for summarization)
ollama pull qwen3:4b # default; ~2.6 GB. See section 8 for how to choose
ollama list1.4 Create your config
node dist/cli.js config init # writes ~/.mcp-token-optimizer/config.yaml with all defaults
node dist/cli.js doctor # verifies config + Ollama + modelExpected doctor output:
✓ config: /Users/you/.mcp-token-optimizer/config.yaml
mode=active profile=balanced redaction=detect retrieval=true
✓ ollama http://127.0.0.1:11434 model qwen3:4b readydoctor prints ✗ ollama unreachable … if Ollama is down (pruning and TOON still work) and ✗ … model "x" is not pulled if the model is missing.
Start safe. For the first day set
mode: shadowin that file. The proxy then measures savings but forwards the original responses, so your agent behaves exactly as before. Switch toactiveonce the numbers look right (see section 4).
1.5 Run the HTTP proxy (only needed for remote/URL MCP servers)
Servers configured with "url" (type http) are reached through a small local proxy. Stdio servers do not need it (they are wrapped directly, see 1.6).
Foreground (good for a first try):
node dist/cli.js serve # http://127.0.0.1:8787Keep it running in the background:
macOS (launchd)
sed -e "s#__NODE__#$(which node)#" -e "s#__MTO_DIR__#$(pwd)#" deploy/com.mto.serve.plist > ~/Library/LaunchAgents/com.mto.serve.plist
launchctl load ~/Library/LaunchAgents/com.mto.serve.plist
curl -s localhost:8787/health # → ok
# stop: launchctl unload ~/Library/LaunchAgents/com.mto.serve.plist logs: /tmp/mto-serve.logLinux (systemd user unit): template provided, not tested by the author
mkdir -p ~/.config/systemd/user
sed -e "s#__NODE__#$(which node)#" -e "s#__MTO_DIR__#$(pwd)#" deploy/mto-serve.service > ~/.config/systemd/user/mto-serve.service
systemctl --user daemon-reload && systemctl --user enable --now mto-serveRestart mto serve whenever you change config.yaml. The proxy reads config at start. (Stdio wrappers pick up the config each time the agent restarts that server.)
1.6 Wire your agent
Step 1: see what would be changed (read-only).
node dist/cli.js discoverGitHub Copilot / VS Code (user: Code)
~/Library/Application Support/Code/User/mcp.json [servers]
· plain stdio shadcn
· plain http github
…discover looks in the known locations for Claude Code, Claude Desktop, VS Code/Copilot, Cursor, Windsurf, Gemini CLI, Cline and Roo. To include files elsewhere:
node dist/cli.js discover --file ~/work/mcp.json # key path auto-detected
node dist/cli.js discover --file ~/x/settings.json#mcp.servers # explicit key path
node dist/cli.js discover --scan ~/Development # find mcp.json-style files (depth ≤ 4)
node dist/cli.js discover --no-builtin --file ~/work/mcp.json # ONLY that file (ignore the built-in agent locations)
MTO_MCP_FILES="$HOME/a.json:$HOME/b.json" node dist/cli.js discoveror permanently in config.yaml (discovery.files, discovery.scanDirs, discovery.skip, discovery.builtin).
Step 2: dry-run the change, then apply, starting with one or two servers.
init --applyedits every MCP config it discovers unless you narrow it with--only <server,…>. To operate on exactly one file (recommended when experimenting) add--no-builtin --file <path>.MTO_HOMEisolates only mto's own data, not your agent configs.
node dist/cli.js init --only shadcn,github # dry run: prints what would change
node dist/cli.js init --only shadcn,github --apply # writes; backup saved as <file>.mto.bakWhat --apply does to a server entry:
Entry type | Before | After |
stdio |
|
|
http |
|
|
Comments and formatting in JSONC files (VS Code's mcp.json) are preserved. Files that cannot be parsed are reported as unparsable and never modified. Legacy "type":"sse" servers are skipped.
Step 3: restart the servers in your agent (VS Code: the Restart lens above the server in mcp.json; Claude Code: restart the session).
Step 4: verify. In your agent, run a task that calls one of those tools, then:
node dist/cli.js statsserver.tool calls tokens_in tokens_out saved lossy avg
k8s.kube_list_resources 2 9978 4464 55.3% 0 10ms
…Undo everything, any time:
node dist/cli.js uninstall --apply1.7 Data locations and cleanup
Everything lives in ~/.mcp-token-optimizer/ (override with MTO_HOME): config.yaml, routes.json, stats.jsonl (sizes only, never content), store/ (originals kept for mto_expand, auto-expired), captures/ (only if you turn capture on). Delete the folder to remove all state.
2. Try it without any agent (5 minutes)
A fake MCP server and synthetic sample data let you see every behaviour before touching your setup.
The repo ships synthetic sample data and a fake MCP server, so you can see every behaviour before touching your real setup. None of it is real data.
npm run examples # (re)generates examples/*.json|md|log|html|ts
npm run demo # calls a tool through the optimizer with a tiny MCP clienttools: list_issues, describe_configmap, fetch_guide, build_log, read_file, get_rows, …, mto_expand
instructions: Some tool results are compacted by mcp-token-optimizer: TOON is lossless JSON …Compare direct vs. through the optimizer:
# direct
node scripts/mcp-call.mjs --tool list_issues --quiet -- node examples/fake-mcp-server.mjs
# through the optimizer (stdio wrapper)
node scripts/mcp-call.mjs --tool list_issues --quiet -- node dist/cli.js wrap --name demo -- node examples/fake-mcp-server.mjs── agent received: 90124 chars, ~37355 tokens ← direct
── agent received: 35386 chars, ~13244 tokens ← through mto (−64.5%)Or optimize a saved response file:
node dist/cli.js try examples/github-issues.json
# tokens 37355 → 13244 (64.5% saved) · stages: prune, toon · lossy: falseRun everything at once (24 self-checking assertions):
npm run smoke # prints PASS/FAIL per check; exits non-zero on any failure3. How it works
The optimization pipeline, what is lossless, and the guardrails that protect the agent.
The optimizer is a man-in-the-middle for the MCP JSON-RPC protocol. It only ever rewrites tool results (tools/call responses) and, optionally, the tool list; everything else is forwarded untouched. Any internal error, timeout, or Ollama outage fails open: the original response is forwarded.
Pipeline for a JSON tool result:
Skip if smaller than
skipBelowTokens(default 40 tokens).Detect secrets (
redaction.mode).Prune noise:
nulls, empty values, hypermedia/URI-template URLs, avatar/node-id style bookkeeping keys (profile-dependent, section 6).Cross-field dedupe: if a long string repeats lines that already appear in another, deeper field, the repeat becomes a pointer such as
[… 831 lines identical to $.manifest.data["app.yaml"] omitted …]. The canonical copy is never touched.Encode: pick the cheaper of minified JSON and TOON (a compact tabular text format; official
@toon-format/toonlibrary). TOON is used only if decoding it reproduces the input exactly. Large results can be forced to pretty JSON instead (section 7).Note: if named fields were dropped (or text was summarized), append a short note naming what was dropped and an id for
mto_expand.Never-bigger guard: the optimized version is used only if it is strictly cheaper than the original.
Pipeline for a text result: strip ANSI, collapse repeated lines ((×300)), dedupe repeated paragraphs, HTML→text, base64 blobs removed, then (if long, and not code) summarize with your local Ollama model under a latency budget and fact-coverage guard.
Never modified: tool results with structuredContent or a declared outputSchema (only minified), error results (isError), images/resources, and code or diffs (never summarized).
What is lossless and what is not
Stage | Information lost? |
minify, TOON, json-pretty, whitespace/ANSI cleanup | none (TOON round-trip is verified per response) |
drop | none of substance |
cross-field dedupe | none: the canonical copy stays, the repeat is a pointer |
drop named noise keys ( | yes, but named in the note and retrievable via |
summarization, array truncation, blob stripping | yes → original stored, retrievable via |
4. Operating modes: off · shadow · active
Decide what the agent actually receives: the original, the original plus measurements, or the optimized result.
mode decides what the agent actually receives. It can be set globally and overridden per tool (tools:).
Mode | Agent receives | Audit/stats | Use it to |
| original, untouched | none | disable the optimizer without uninstalling |
| original, untouched | records what would have been saved | roll out with zero risk; collect a baseline |
| optimized | records real savings | production |
4.1 Global mode
# ~/.mcp-token-optimizer/config.yaml
mode: shadowRestart mto serve and the agent's MCP servers, then:
# Try it with the demo server (each mode gives a different token count):
node scripts/mcp-call.mjs --tool list_issues --quiet -- node dist/cli.js wrap --name demo -- node examples/fake-mcp-server.mjs
| Output of the command above |
|
|
|
|
|
|
4.2 Per-tool overrides
First match wins; globs match server.tool where server is the --name given to mto wrap (or the route name for HTTP).
mode: shadow # default for everything
tools:
"demo.list_issues":
mode: active # this tool is optimized
"filesystem.*":
mode: off # this server is never touchedVerified with the demo server: list_issues → ~13.2k tokens (active), describe_configmap → ~3.6k (original, shadow).
4.3 Recommended rollout
mode: shadow+node dist/cli.js capture onfor a day of normal work.node dist/cli.js statsandnode dist/cli.js benchto see projected savings (section 11).Flip a few tools to
activewith per-tool overrides, then everything.Watch
mto statsformto_expandcalls: if the agent needs originals often, tune the profile (section 6).
5. Integration modes: stdio wrapper vs HTTP proxy
How
mtogets between the agent and each kind of MCP server, plus per-agent notes.
stdio wrapper | HTTP proxy | |
For MCP servers that are… | local processes ( | remote URLs ( |
How | agent launches | agent talks to |
Extra process to keep running | no (started by the agent) | yes: |
Auth | environment passes through | client |
Transports handled | newline-delimited JSON on stdio | Streamable HTTP: JSON and SSE responses, |
Not supported | JSON-RPC batch (passed through unmodified) | legacy |
5.1 stdio: manual wrapping (any agent, any config)
"shadcn": {
"command": "node",
"args": ["/abs/path/to/mcp-token-optimizer/dist/cli.js", "wrap", "--name", "shadcn", "--", "npx", "shadcn@latest", "mcp"]
}Optional: --config /abs/path/to/other-config.yaml before the -- to use a different config for this one server.
5.2 HTTP: manual routing
Add a route:
~/.mcp-token-optimizer/routes.json{ "github": { "url": "https://api.githubcopilot.com/mcp/" } }Run
node dist/cli.js serve.In the agent config, point the server at
http://127.0.0.1:8787/githuband keep itsheaders.
Test without an agent (the demo server requires header X-API-Key: demo-key):
node examples/fake-mcp-server.mjs --http 9100 &
mkdir -p /tmp/mto-demo && echo '{"demo":{"url":"http://127.0.0.1:9100/mcp"}}' > /tmp/mto-demo/routes.json
MTO_HOME=/tmp/mto-demo node dist/cli.js serve --port 8790 &
node scripts/mcp-call.mjs --tool list_issues --quiet --url http://127.0.0.1:8790/demo --header X-API-Key=demo-key
# → agent received: … ~13244 tokens
kill %1 %2Safety properties (all covered by tests): loopback-only bind; requests with a non-loopback Host header get 403 (DNS-rebinding guard); unknown route 404; upstream failure 502 with the real cause (ECONNREFUSED, SELF_SIGNED_CERT_IN_CHAIN, …); only routes in routes.json are reachable.
5.3 Agent notes
Agent | Config file(s) found by | Notes |
GitHub Copilot / VS Code |
| JSONC (comments) supported. VS Code saves big tool results to a file and the agent then reads or |
Claude Code |
| Config handling unit-tested; not run against a live Claude Code session by the author. |
Claude Desktop |
| same caveat |
Cursor, Windsurf, Gemini CLI, Cline, Roo | see | same caveat |
anything else |
|
Corporate networks: if a remote MCP server uses a private certificate authority that your OS trusts but Node doesn't, mto serve automatically restarts itself with --use-system-ca (Node ≥ 22.15). Disable with MTO_NO_SYSTEM_CA=1, or use NODE_EXTRA_CA_CERTS=/path/ca.pem.
6. Optimization profiles
conservative,balancedoraggressive: how much JSON noise is pruned, with measured results.
profile controls how aggressively JSON noise is pruned. Measured on examples/github-issues.json (37,355 tokens, deterministic, no LLM):
Profile | Result | Saved | What it does |
| 28,144 | 24.7% | drops |
| 13,244 | 64.5% | + bookkeeping keys ( |
| 9,584 | 74.3% | + every |
Fine-tune without changing profile:
prune:
dropKeys: ["*_etag", "debug_*"] # extra globs to drop
keepKeys: ["avatar_url"] # never drop these, even if the profile would
maxArrayItems: 100 # 0 = unlimitedRun it yourself:
printf 'profile: conservative\nsummarize:\n enabled: false\n' > /tmp/c.yaml
node dist/cli.js try examples/github-issues.json --config /tmp/c.yaml
# tokens 37355 → 28144 (24.7% saved) · stages: prune, toon7. Output formats: JSON vs TOON
Minified JSON, pretty JSON or TOON, and why large results should stay JSON for clients that save them to files.
| Emits | Use when |
| cheaper of minified JSON and TOON; TOON only if it round-trips exactly | the agent reads results inline |
| minified JSON | machine-parsed output on one line is fine |
| 2-space indented JSON (valid, | large results the client saves to a file that the agent parses or reads by line range |
| always TOON when encodable | you want maximum compaction |
Results that carry structuredContent or a tool outputSchema are always kept as (minified) JSON and never pruned, whatever the format.
The Copilot lesson (measured). VS Code offloads big tool results to a file. In a clean A/B run, Copilot got TOON, tried yaml.safe_load (failed), and needed about 7 extra terminal steps to write a regex extractor, where the un-optimized run needed one json.load. On the 24.6k-token result, pretty JSON saved 48.1% vs TOON's 48.7%, so switch big results to pretty JSON:
format:
prettyJsonAboveTokens: 8000 # results larger than this → pretty JSON; smaller ones may still use TOON
jsonTools: ["postgres.*"] # globs "server.tool" that must always stay minified JSON
tools:
"k8s.kube_get_*":
format: json-pretty # per-tool overrideReproduce the format comparison:
for f in json json-pretty; do printf "audit:\n enabled: false\nsummarize:\n enabled: false\nformat:\n default: $f\n" > /tmp/$f.yaml; node dist/cli.js try examples/github-issues.json --config /tmp/$f.yaml 2>&1 >/dev/null | grep -o 'tokens.*'; done
# json: 37355 → 16117 (56.9% saved) · stages: prune, minify
# json-pretty: 37355 → 16734 (55.2% saved) · stages: prune, json-pretty
# (auto/TOON: 37355 → 13244 (64.5% saved))(Note: pretty JSON is only used if it is still smaller than the original; feeding it already-minified JSON keeps the original.)
format.announceToon: true adds a sentence to the MCP initialize instructions telling the agent what TOON tables (key[N]{a,b}:) and [mto: …] notes mean.
8. Summarization with a local Ollama model
Long prose only: which models work, how the latency budget behaves, and honest expectations.
Only long prose is ever sent to the LLM (summarize.minTokens, default 1200 tokens after cleanup). Never summarized: code and diffs (detected by content), results from tools matching summarize.neverTools (file reads, edits, writes, diffs, patches), error results. Fenced code blocks inside prose are protected. Headings never go through the model.
8.1 Choose a model and parameters
llm:
baseUrl: http://127.0.0.1:11434
model: qwen3:4b
options: { temperature: 0.1, top_p: 0.9, num_ctx: 8192, num_predict: 1024 } # passed verbatim to Ollama
keepAlive: 10m
think: falseMeasured on the author's Apple-silicon Mac with a synthetic 5k-token doc and a 20 s budget (one document; treat as a starting point and benchmark on your own data, see TESTING.md §6):
Model | Generation speed | Result |
| ~35 tok/s | ~49% saved, all key facts kept |
| ~40 tok/s | ~49% saved, all key facts kept; good faster alternative |
| ~44 tok/s | ~50% saved; one run lost 10% of key facts |
| ~68 tok/s | fastest but truncated output and lost ~19% of facts. Avoid |
| ~30 tok/s | slowest, timeouts. Avoid |
8.2 Latency, budgets, and honest expectations
Summarization cost is output token generation, so it depends on your hardware. The optimizer protects the agent's latency:
summarize.maxLatencyMs(20 s): hard budget per tool result. Text is split into chunks, biggest first. A chunk is never started if it is predicted not to finish inside the budget; unfinished chunks stay verbatim.llm.timeoutMs(30 s): per-request ceiling. A circuit breaker stops calling Ollama for 60 s after 3 failures.Truncated generations are discarded; a summary that loses key facts (paths, ids, versions, URLs, numbers, error codes) is repaired or rejected (
summarize.minCoverage, default 0.97).The model is pre-loaded when the proxy/wrapper starts (
keepAlive).
Reality check: on examples/deploy-guide.md the free deterministic stages give 34.8% and adding qwen3:4b (40 s budget) gave 39.2% at ~33 s. Pruning + TOON do the heavy lifting; the LLM adds a modest extra for long prose. If latency matters more than the last few percent, set summarize.enabled: false.
# Live demo (needs Ollama + model). minTokens lowered so the 2.3k-token sample qualifies:
printf 'audit:\n enabled: false\nsummarize:\n minTokens: 400\n maxLatencyMs: 40000\n' > /tmp/llm.yaml
node dist/cli.js try examples/deploy-guide.md --config /tmp/llm.yaml >/dev/null
# tokens 2283 → 1388 (39.2% saved) · stages: normalize, summarize(qwen3:4b) · lossy: true · ~33s
MTO_DEBUG=1 node dist/cli.js try examples/deploy-guide.md --config /tmp/llm.yaml >/dev/null # per-chunk accept/reject reasons8.3 Disable, or fail-open
summarize: { enabled: false } # never call the LLM (zero added latency)
tools:
"docs.fetch_*": { summarize: false } # per toolIf Ollama is down, requests still succeed with the deterministic result (verified: stages: normalize, 2283 → 1489 tokens).
9. Getting the original back: mto_expand
Notes tell the agent what was removed; one tool call fetches the exact original.
When the optimizer removes something that the agent might want, it stores the exact original (~/.mcp-token-optimizer/store/, expires after retrieval.ttlMinutes, default 240) and ends the result with a note that says what was removed:
[mto: pruned low-value fields: events_url×80, node_id×80, labels_url×40, avatar_url×40, gravatar_id×40, followers_url×40. Only if you need them: mto_expand {"id":"7b105335b224","path":...}]For plain-JSON objects the note is a top-level _mto key instead, so the output stays valid JSON; a JSON array at the root gets no note (no place for one without changing its shape).
When notes appear: only when named data was removed (pruned noise keys, truncated arrays, stripped blobs) or text was summarized. Not for empty/null removal, TOON encoding, or duplicate-line pointers (nothing was lost, and a note would only invite pointless fetches; measured: with a note on lossless changes Copilot burned more tokens expanding than were saved).
The proxy adds a tool mto_expand to tools/list:
Argument | Meaning |
| required, from the note |
(nothing else) | returns an outline (structure + token sizes), not the whole original |
| JSON path into the original, e.g. |
| regex; returns matching lines with 2 lines of context |
| page through the text by characters |
| return everything (can be very large) |
Try it (demo server):
OUT=$(node scripts/mcp-call.mjs --tool list_issues -- node dist/cli.js wrap --name demo -- node examples/fake-mcp-server.mjs)
ID=$(echo "$OUT" | grep -o 'mto_expand {"id":"[0-9a-f]*"' | grep -o '[0-9a-f]\{12\}')
node scripts/mcp-call.mjs --tool mto_expand --args "{\"id\":\"$ID\"}" -- node dist/cli.js wrap --name demo -- node examples/fake-mcp-server.mjs | head -5
node scripts/mcp-call.mjs --tool mto_expand --args "{\"id\":\"$ID\",\"path\":\"\$[3].user.avatar_url\"}" -- node dist/cli.js wrap --name demo -- node examples/fake-mcp-server.mjs | head -1
# → https://avatars.githubusercontent.com/u/103?v=4Every mto_expand call is audited: mto stats ends with e.g. mto_expand: agent asked for originals 4x (1 misses) across 1 optimized calls. If the agent expands a lot, your profile is too aggressive for that tool. To turn the feature off: retrieval.enabled: false.
10. Secret detection and redaction
Find (or strip) API keys, tokens and private keys in tool output before the model sees them.
Built-in patterns: AWS access keys, GitHub tokens (classic and fine-grained), Slack tokens, Anthropic/OpenAI/Google API keys, JWTs, PEM private keys, Bearer tokens. Add your own with redaction.extraPatterns.
| Behaviour |
| no scanning |
| text untouched; findings counted in the audit log; |
| secrets replaced with |
redaction:
mode: redact
extraPatterns:
- { name: internal-token, regex: "itk_[A-Za-z0-9]{24,}" }printf 'redaction:\n mode: redact\nsummarize:\n enabled: false\n' > /tmp/r.yaml
node dist/cli.js try examples/secrets.txt --config /tmp/r.yaml 2>/dev/null | head -4
# aws_access_key_id = [REDACTED:aws-access-key]
# github_token = [REDACTED:github-token] …In redact mode redaction is never undone, even by the never-bigger fallback. (Pattern-based: it will not catch every possible secret.)
11. Capture, bench, compare: tuning on your real data
Record real tool output locally, replay it, and compare what the agent received with the original.
node dist/cli.js capture on # live toggle (a flag file), no restarts. Records raw, secret-redacted results locally
# … use your agent normally …
node dist/cli.js capture status # sample count
node dist/cli.js bench --dump ~/mto-report # replay: savings, latency; before/after examples to ~/mto-report.md
node dist/cli.js bench --models qwen3:4b,granite4:3b # compare LLMs on YOUR data (speed, savings, fact retention)
node dist/cli.js compare # original vs what the agent received (needs active mode)
node dist/cli.js capture offcompare classifies each call:
Verdict | Meaning |
| nothing to do |
| same information (only nulls/empties removed, or duplicates pointed) |
| named fields dropped; listed ( |
| text condensed; critical-fact retention shown |
| a value differs. This would be a bug |
Captures contain real tool output from your systems (only common secret patterns are redacted). Treat ~/.mcp-token-optimizer/captures/ as sensitive; capture off and deleting the folder removes it. Full benchmarking procedure: TESTING.md.
12. Configuration reference
Every property: its default, what it does, and an example.
File: --config FILE → $MTO_CONFIG → ./mto.config.yaml → ~/.mcp-token-optimizer/config.yaml. Every key is optional; unknown top-level keys (and unknown keys inside tools rules) are rejected with a clear error (mto doctor shows it), so typos there don't fail silently. ~ and $VARS in audit.path/discovery paths are expanded. A complete commented sample is in mto.config.example.yaml.
Top level
Key | Default | Meaning |
|
|
|
|
|
|
|
| results smaller than this are forwarded untouched |
|
| per-tool overrides (below) |
llm: the local model
Key | Default | Meaning |
|
| only Ollama is supported |
|
| Ollama endpoint |
|
| any pulled model ( |
|
| passed verbatim as Ollama |
|
| how long Ollama keeps the model in memory |
|
| per-request ceiling (also limited by |
|
| disable "thinking" mode of reasoning models (faster, deterministic) |
summarize
Key | Default | Meaning |
|
|
|
|
| only text above this (after cleanup) is considered |
|
| hard budget per tool result; unfinished chunks stay verbatim |
|
| parallel chunk requests (helps only if Ollama runs with |
|
| requested output size relative to input |
|
| fraction of critical facts that must survive, otherwise re-attached or the summary is rejected |
| file reads/edits/writes/diffs/patches globs |
|
format
Key | Default | Meaning |
|
|
|
|
|
|
|
| results larger than this become pretty JSON instead of TOON. Recommended for VS Code Copilot: |
|
| add the TOON/notes explanation to MCP |
prune
Key | Default | Meaning |
|
| remove |
|
| remove |
|
| extra key-name globs dropped everywhere |
|
| key-name globs never dropped ( |
|
| truncate longer arrays (lossy: note + expand) |
|
| replace base64 blobs larger than this |
| on unless | replace repeated line-blocks with pointers to the deeper canonical field |
toolDefinitions
Key | Default | Meaning |
|
| also slim |
|
| schema annotation keywords removed. Parameter names (e.g. a parameter called |
redaction
Key | Default | Meaning |
|
|
|
|
|
|
retrieval
Key | Default | Meaning |
|
| store originals, add notes, expose |
|
| how long originals are kept |
|
| max stored originals |
discovery
Key | Default | Meaning |
|
| extra config files: |
|
| directories scanned for |
|
| include the built-in agent locations. |
|
| built-in agents to ignore (substring of the name shown by |
cache, audit
Key | Default | Meaning |
|
| in-memory result cache keyed by content hash |
|
| append a size-only row per tool call to the audit log (never content) |
|
| log location |
|
| $ per million input tokens, used by |
tools: per-tool overrides
First matching glob wins. Globs match server.tool (case-insensitive, * and ?).
Key | Meaning |
|
|
|
|
|
|
| extra prune globs for this tool |
| array truncation for this tool |
tools:
"github.list_*": { maxArrayItems: 100 }
"postgres.query": { format: json, summarize: false }
"filesystem.*": { mode: off }
"k8s.kube_get_*": { format: json-pretty }13. CLI reference
All commands and flags.
mto discover [--scan dir] [--file path[#keyPath]] [--skip agent] [--no-builtin] List every MCP config + server found (read-only)
mto init [--apply] [--only a,b] [--file f] [--scan d] [--skip agent] [--no-builtin] [--config f] Wrap stdio servers, route http servers
mto uninstall [--apply] [same discovery flags] Restore original commands/URLs
mto wrap [--name n] [--config f] -- <cmd> [args…] Run one stdio MCP server through the optimizer
mto serve [--port 8787] [--config f] Local HTTP proxy for remote servers
mto try <file|-> [--tool t] [--server s] [--config f] Optimize a saved response; prints result + one-line summary on stderr
mto capture on|off|status Record raw (secret-redacted) tool results, live
mto bench [dir] [--llm] [--models a,b] [--dump prefix] [--cap N] Replay captures; savings, latency, fact retention
mto compare [--tool glob] [--last N] Original vs what the agent received
mto stats [--since 7d|24h] [--json] Token savings report (+ $ estimate, expand usage, secrets seen)
mto doctor [--config f] Check config, Ollama, model
mto config init Write a default config fileinit/uninstall are dry runs without --apply and write <file>.mto.bak once per file.
Helper scripts (for testing and demos): npm run examples · npm run demo · npm run smoke · scripts/mcp-call.mjs (tiny MCP client) · scripts/examples-to-captures.mjs (examples → a captures dir for mto bench) · scripts/fidelity.mjs (byte-compare a file against the captured original) · examples/fake-mcp-server.mjs (demo server, stdio or --http PORT).
14. Files and environment variables
Where state lives and which variables change behaviour.
Path (under | Purpose |
| configuration |
|
|
| audit log: one size-only row per tool call |
| originals for |
| recorded samples and the capture flag |
| backups made by |
Variable | Effect |
| use another data directory instead of |
| config file path |
| extra MCP config files for |
| print per-chunk summarization decisions to stderr |
| don't auto-relaunch |
15. Troubleshooting
Symptoms, causes and fixes, from real problems hit while building this.
Symptom | Cause / fix |
| your config is in a non-standard place: |
| without |
| it has a syntax error; fix it (comments and trailing commas are fine). The file is never modified |
Agent shows | read the reason after it. |
HTTP server unreachable after |
|
| another |
Config change has no effect | restart |
Agent scripts fail to parse a saved tool result | it was TOON: set |
Agent keeps calling | tool is over-pruned: use |
Savings look negative in |
|
Summarization never happens |
|
Summaries too slow | smaller model, lower |
Need to see exactly what the agent got |
|
Everything wrong, want to bail out |
|
16. Security and privacy
What is stored, what is forwarded, and what is never done.
Local only. The proxy binds to
127.0.0.1; onlyHost: 127.0.0.1|localhost|[::1]is accepted; only routes inroutes.jsonare forwarded (no open proxy).Credentials pass through, never stored. HTTP headers (API keys,
Authorization) are forwarded to the configured upstream and are not logged, audited, or written to disk. Do not paste keys into screenshots or chat; the demo config uses fake values.The audit log holds sizes only.
stats.jsonlnever contains tool content.Stored originals (
store/) and captures hold real tool output, with0600files in a0700directory; captures are secret-redacted (pattern-based). Retention:retrieval.ttlMinutes; captures until you delete them.TLS is never disabled. A private CA is trusted via the OS store (
--use-system-ca) orNODE_EXTRA_CA_CERTS.init --applyedits agent config files in place after writing a.mto.bak;uninstall --applyreverses it.The summarization prompt treats tool output as data. A tool result that contains instructions is summarized, not obeyed. (Summarizers can still be nudged by adversarial text; keep
summarize.neverToolsfor sensitive tools.)
17. Known limitations and verification status
What was verified live, what was only unit-tested, and what is not covered.
Verified (automated tests + live runs on macOS, Node 22.16, Ollama, VS Code Copilot agent mode against a real remote HTTP MCP server): stdio wrapper, HTTP proxy (JSON + SSE, corporate CA), JSONC config editing, TOON round-trip (1,500-document fuzz), all modes/profiles/formats in this README, mto_expand, fail-open with Ollama down, live summarization with five local models, and a clean A/B run whose output files were byte-identical to the ground-truth data.
Not verified by the author: Claude Code, Claude Desktop, Cursor, Windsurf, Cline, Roo, Gemini CLI against live sessions (config handling is unit-tested); Linux and Windows; the systemd unit; JSON-RPC batch handling beyond pass-through.
Limitations
Token counts are a model-agnostic estimate, good for relative savings, not billing-exact.
Long strings inside JSON fields (e.g. an issue
body) are not summarized.Legacy
"type":"sse"MCP servers are not proxied.mto servemust be running for proxied HTTP servers (no auto-start beyond the launchd/systemd templates).Summarization latency is bounded but real (seconds); benefit is modest on prose that is already dense.
If an agent post-processes saved tool results with scripts, use
json-prettyfor large results (section 7).The output-format savings and model comparisons were measured on one machine and a few datasets; benchmark yours (TESTING.md).
18. Development
Layout of the code base and how to run the checks. See CONTRIBUTING.md for the full guide.
src/
cli.ts command line installer.ts discovery + config-file editing (jsonc-parser)
config.ts schema + loader stdio.ts stdio wrapper
pipeline.ts the optimization pipeline http.ts HTTP/SSE proxy
prune.ts prune + cross-field dedupe session.ts JSON-RPC interceptor, mto_expand, notes
toon.ts official TOON wrapper summarize.ts chunked LLM summarization + guards
text.ts cleanup, HTML, code sniff llm.ts Ollama client, circuit breaker
redact.ts secret patterns store.ts originals for mto_expand
audit.ts stats log/report capture.ts / bench.ts / compare.ts tuning loop
examples/ synthetic sample data + fake MCP server scripts/ mcp-call.mjs, smoke.mjs, make-examples.mjs
deploy/ launchd + systemd templates tests/ 42 tests (node:test)npm test # 42 tests: TOON fuzz, pipeline guardrails, installer/JSONC, HTTP proxy, stdio e2e, expand
npm run typecheck
npm run smoke # 24 end-to-end assertions with the fake serverContributing
Contributions of all sizes are welcome: bug reports, docs, benchmark results, new agent integrations, pipeline improvements. Start with CONTRIBUTING.md (setup, principles, how to add a pruning rule, pipeline stage or agent target, PR checklist) and please read the Code of Conduct. Security issues: SECURITY.md.
License
Apache-2.0. Uses the official @toon-format/toon library for TOON, and Ollama for optional local summarization.
This server cannot be deployed
Maintenance
Related MCP Connectors
Multiple MCP tools, persistent graph memory, token-saving data pointers, and more.
- WauldoOAuthcom.wauldo
Stateless agentic tools over MCP: concept extraction, long-context, knowledge graph, planning.
Reduces AI Agent token usage by 40% via three-stage SOP workflow.
SaaS intelligence for AI agents. 5 unified tools cover 1,000+ services with 91-96% token savings.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn adaptive tiny-model layer that sits between an LLM and its MCP tools, compressing verbose tool outputs to reduce token usage by up to two orders of magnitude.1Apache 2.0
- AlicenseNot gradedqualityCmaintenanceToken-efficient MCP reimplementation with progressive tool discovery, result handling, and compact wire encoding, reducing token usage by up to 89% on tool definitions.1MIT
- AlicenseAqualityBmaintenanceMCP proxy that compresses tool schemas on the fly. Up to 98% token reduction, 100% signal preserved verified after every compression. Zero LLM calls, fully deterministic.54MIT
- AlicenseNot gradedqualityDmaintenanceDeterministic context compression for MCP agents, reducing token usage via 11 tools for prompts, history, shell output, file deltas, and code navigation without ML or GPU.8MIT