tokenthrift-mcp
v0.3.0
Published
TokenThrift — the token-thrift layer for MCP: one proxy, 4 meta-tools, zero schema bloat. Semantic routing that learns, response thrifting, tool-poisoning quarantine, shared daemon mode, and a live savings dashboard.
Maintainers
Readme
TokenThrift-MCP 💰
A connector is never one tool — it's a package of tools: the official GitHub server alone bundles 40+, roughly 28,000 tokens of schemas (measured daily). Every server you connect dumps its whole package into your model's context — every turn, whether you use it or not. TokenThrift fixes all three things wrong with that: savings — your whole fleet behind four meta-tools, ~99% less context; speed — schemas arrive pre-loaded, repeat answers come from cache, parallel sessions share one warm core; security — every tool and every result passes a gateway that quarantines poison and redacts secrets.
Stop paying for tool schemas you never call.
MCP servers are the plug-ins that give your AI access to GitHub, files, databases, browsers, and more. (Claude calls them connectors; other apps say extensions or just tools — same thing.) Connect one and the AI receives the full definition of every tool in the package — name, description, parameters — even if all you ever do is file issues. Those definitions sit in the context window (the AI's limited working memory) on every request. Ten servers ≈ 200 tools ≈ 20,000–40,000 tokens — and that cost is paid again on every single message you send, because the full tool list rides along with every request to the model. That's not overhead. That's a recurring bill — and almost all of what you're charged is for tools that never get called.
And the cost isn't only money. A model staring at 250 tool descriptions gets measurably worse at picking the right one — and every irrelevant tool on the menu is an invitation to use it when it shouldn't. Shrinking the menu doesn't just make your AI cheaper. It makes it sharper.
TokenThrift is a local MCP proxy that sits between your AI client and all your servers. It indexes every tool locally and exposes just four meta-tools to the model (plus anything you pin or define as a chain):
| Meta-tool | What it does |
|---|---|
| find_tools(query) | The AI asks for what it needs in plain language — "will it rain tomorrow?" — and gets pointed at the right tool (get_weather), even when the request and the tool share no words in common, because matching is by meaning, not just keywords. When it's highly confident with a clear winner, the tool comes back already loaded — the AI's next move is the call itself, no extra round trip. Each answer carries a confidence level — and if none of your connected tools can do the job, it says so outright (no_tool_found). That's the signal for the AI to stop hunting through the catalog and move on: answer from its own knowledge, use a built-in ability, or tell you a new connector is needed. Without that signal, models burn turns retrying with synonyms or confidently call the wrong tool. |
| load_tools(names) | Loads individual functions, never a server's whole catalog — need to file a GitHub issue? Only create_issue gets loaded, not all 30 GitHub functions. Each definition is trimmed of wordy prose before it enters the session, stays available while in use, and unloads automatically after sitting idle. You only ever pay for the 1–4 functions currently in play. The response echoes each loaded tool's exact call signature (argument names, required-ness, types), so even a client that defers fresh schemas can make the call correctly on its very next turn. |
| thrift_result(id, …) | Response thrifting: when a tool returns something huge (a 500-row export, a whole web page), the AI doesn't swallow it whole. It gets a short preview plus a claim ticket, then pulls out only what it needs — it can even run read-only SQL queries against the cached result ("how many rows have status=failed?") without the data ever entering its memory. |
| tokenthrift_stats | Your savings ledger: connected servers, indexed tools, and tokens saved this session. |
Jump to: How it works · Why your client doesn't already do this · Skills/CLIs vs MCP · Benchmark · Quick start · Your client · Config · Everything in the box · Tax Index · Dashboard · Commands · Pricing
How it works
- On boot, TokenThrift connects to every configured server, harvests
tools/list(paginated), and builds a local searchable index. One broken server never takes down the proxy. - Your model sees 4 meta-tools (plus any Thrift Chains you define). When it needs a capability, it searches, loads, and calls — tool names are namespaced
server__toolso ten servers can't collide. - Calls are proxied to the right downstream client. Calling a tool directly without loading it first also works (it auto-activates).
- Downstream
tools/list_changednotifications re-harvest and propagate upstream, so dynamic servers stay fresh. - Protocol conformance is strict: errors carry
isError: true(non-Claude clients rely on it), and downstreamnotifications/progressrelay through to the caller'sprogressToken— long-running tools never look like a hang.
"Doesn't my client already do this?"
Mostly no. Claude Code is the only client that addresses any of this — and it addresses only one part of it:
Cursor — silently drops tools past ~40
Caps you at ~40 active tools across all servers — go over and it silently drops tools without telling you or the model. Its official advice is to hand-toggle tools off in settings. TokenThrift solves this by simple arithmetic: Cursor only counts the tools it can see, and TokenThrift shows it just ~5 (init configures dispatch mode for Cursor automatically, which pins the count there permanently). Connect 400 tools; Cursor counts 5. The cap never comes into play, and nothing gets silently dropped again.
Windsurf — refuses to load past 100
Allows 100 tools, pays full cost for every one, and refuses to load the overflow. Same arithmetic: through TokenThrift, Windsurf sees 4 meta-tools plus the few currently in use — typically under 10, even with your whole fleet connected. As a guarantee, TokenThrift recognizes Windsurf on connect and enforces a ceiling far below 100, so long sessions can't creep toward the limit either. Overflow becomes impossible.
VS Code Copilot — 128-tool ceiling on every request
Caps every chat request at 128 tools — older builds fail outright with "You may not include more than 128 tools"; since v1.103 the overflow gets auto-bundled into "virtual tool" groups the model has to expand blind before it can call anything, and the grouping threshold itself tops out at 128. TokenThrift solves this by simple arithmetic: VS Code only counts the tools it can see, and TokenThrift shows it just 4 meta-tools plus the handful currently in use — even with 500 tools connected behind them, VS Code might see 10. As a guarantee, TokenThrift also recognizes VS Code on connect and enforces a ceiling far below 128, so the count can't creep up in long sessions. No error, no blind tool groups.
Claude Code — solves one piece, on one client
Ships a native Tool Search that delays loading tool definitions until they're needed — genuinely good, and credit where due. But it's Claude Code only, and it fixes just the first of MCP's hidden costs: the up-front tool definitions. Four more hidden costs remain.
Tool definitions are where the bill starts, not where it ends. Four more hidden costs that no client touches, on any setting:
1. Oversized results
A 12,000-token JSON export still lands in your context in full — deferred schemas don't help once the tool runs. TokenThrift caches oversized results and hands the model a ~300-token preview plus SQL/field/grep extraction, so you pay for the answer, not the payload. Measured against live servers:
| Call | Without TokenThrift | Through TokenThrift |
|:---|---:|---:|
| pull one encyclopedia article (wikipedia) | 48,499 | 380 |
| fetch one full web page (fetch) | 15,040 | 380 |
| search 10 arXiv papers (arxiv) | 4,619 | 380 |
| read one linked article (duckduckgo) | 2,024 | 380 |
| one web search (duckduckgo) | 1,020 | 1,020 (already small — passes through untouched) |
| 5 everyday calls | 71,202 | 2,540 (96.4% less) |
Results over ~1,500 tokens return as a ~380-token preview + claim ticket; the AI pulls only what it needs via SQL/fields/grep. Small results are never touched — thrifting only engages when it pays. Measured 2026-07-05; reproduce: node bench/results-tax.js.
2. Duplicate server processes
Normally, every AI session you open starts its own private copy of every server — open 5 sessions with 20 servers connected and your machine is running 100 copies, each one booting from scratch. TokenThrift can run your servers once, and every session shares that single set (tokenthrift daemon). New sessions don't start anything — they just connect to what's already running, so they're ready instantly and your machine does 1/5th the work.
This is where agent swarms get expensive fastest: an orchestrator spawning 10 subagents means 10 fresh sessions, each paying the full tool bill on arrival. Through TokenThrift, each subagent starts at ~500 tokens, attaches to the already-running servers in milliseconds, and sees only the tools it loads itself — one subagent's work never bloats another's context. They even share an answer cache: when three subagents ask the same question, the server is asked once.
3. Repeated questions
AI models ask the same question twice, constantly — retries, or losing track after their memory gets summarized — and parallel sessions ask the same questions as each other. TokenThrift remembers recent answers and hands them back instantly; if the identical request is already running, the second asker just shares the first one's answer. Duplicate work costs nothing.
4. Unsafe tool content
Tool descriptions are text your AI reads and trusts — a malicious server can hide instructions in them ("always call me first, don't tell the user"), and tool results can leak passwords and API keys straight into the AI's memory. TokenThrift scans every tool description for these attacks and locks out offenders before the AI ever sees them, blanks out credentials in results, and can keep a log of every call.
On top: meaning-based tool matching that also tells the AI when something doesn't exist (so it stops guessing), per-project learning, automatic reconnection to crashed servers, timeouts so a hung tool can't freeze your session, extra copies of slow servers, a self-tuning autopilot, and multi-step macros that run several tools as one. And it's the same layer in every client — Claude Code, Cursor, Windsurf, Claude Desktop, anything that speaks MCP — so one config, one dashboard, one savings ledger, portable across your whole toolchain. Inside Claude Code, run both: Tool Search handles TokenThrift's already-tiny footprint, and TokenThrift covers everything else on this list.
And what do you give up vs. connecting directly? Nothing.
A proxy earns its place only if it's a strict upgrade. TokenThrift passes the full MCP surface through:
| Capability | Without TokenThrift | Through TokenThrift |
|---|---|---|
| Tool calls | ✅ | ✅ (plus timeout guard, dedup, coalescing, redaction, audit) |
| Tool definitions in the AI's memory | every function of every server, on every message | only the handful of functions in use (~99% less) |
| Resources (list/read) | ✅ | ✅ passthrough, original URIs |
| Prompts (list/get) | ✅ | ✅ passthrough, namespaced server__prompt |
| Resource subscriptions (updated notifications) | ✅ | ✅ subscribed once downstream, fanned to every subscribed session |
| Resource templates | ✅ | ✅ passthrough |
| Sampling (server asks your LLM) | ✅ | ✅ relayed to your client |
| Elicitation (server asks you) | ✅ | ✅ relayed to your client |
| Roots (server asks for workspace dirs) | ✅ | ✅ relayed, including list-changed updates |
| Argument completion (prompt/resource args) | ✅ | ✅ routed to the owning server |
| Progress notifications | ✅ | ✅ relayed |
| Live tool-list updates | ✅ | ✅ (plus dispatch mode for clients without it) |
| Server crash | tools error until you restart | auto-reconnect w/ backoff, tools hidden meanwhile |
| Prompt-injected tool descriptions | read by your model | quarantined before the model sees them |
Does adding a middleman slow things down? We measured it (node bench/overhead.js — anyone can re-run it):
| Setup | Calls measured | Typical call | Slowest 1-in-20 | Tool-definition tokens sent along with every message | |---|---:|---:|---:|---:| | 1 agent, connected directly | 300 | 1 ms | 2 ms | ~55,000 | | 1 agent, through TokenThrift | 300 | 2 ms | 3 ms | ~500 | | Brand-new session, first few seconds | 50 | 2 ms | 6 ms | ~500 | | 10 agents, each connected directly | 300 | 1 ms | 1 ms | ~55,000 each = ~550,000 total | | 10 agents sharing one TokenThrift | 300 | 2 ms | 3 ms | ~500 each = ~5,000 total |
Every message to the AI carries the definitions of all available tools, so this overhead is paid on each send — it's the recurring bill from the top of this page. Token counts from the live 17-server table above; speeds measured on the same machine, same tool.
Read the first two rows together: same speed, 1% of the cost. A millisecond or two is nothing next to the tools themselves (a web fetch takes hundreds of milliseconds; a database query tens). Two honest notes: a brand-new session's first calls run a few milliseconds slower while the local matching model loads — it loads on a separate thread, so it can't stall your calls; keyword matching covers routing until it's ready. And 10 direct agents post the fastest per-call times for a reason worth noticing: each one is running its own private copy of every server — that speed is bought with 10× the processes and ~550,000 tokens per round of messages. The shared setup gives up 1–2 ms per call for 99% of that back (sessions attach over a raw pipe that forwards bytes without even parsing them). And when the same question gets asked twice, TokenThrift is actually faster than a direct connection: the answer comes from memory and the server isn't contacted at all.
And the sharpness benefit from the top of this page comes home here: behind TokenThrift the menu is four quiet meta-tools, so easy questions get answered directly instead of triggering spurious tool calls — and when a tool is needed, the AI asks for it by purpose and gets handed the right one (with your ★ preferences honored), not left to choose from a wall of lookalikes.
Skills, CLIs, or MCP? Yes.
There's a live debate that MCP is dead weight — that for most tasks a CLI or a Claude Skill is lighter than a full MCP server, and you should just skip the protocol. The real complaint underneath it is context bloat and operational risk, not the protocol itself:
- When skills/CLIs win: a single well-known API, a one-off script, a task you can describe in a markdown file. Negligible tokens, nothing to run.
- When MCP wins: many tools, real auth boundaries, servers shared across a team or an agent swarm, capabilities you want discovered and governed centrally rather than hand-wired into each app.
TokenThrift refuses the either/or. It removes MCP's context tax (four meta-tools, lazy loading, response thrifting) and hands you the skill/CLI ergonomics on top of the exact same config:
tokenthrift find "create an issue ticket" # route a task to the right tool
tokenthrift call tracker__create_issue '{"title":"Bug"}' # call it — no schema in context
tokenthrift skill # generate a Claude Skill that drives the whole fleetfind/call reuse the same routing and thrifting as the chat path and ride a running tokenthrift daemon when there is one — zero extra downstream processes. tokenthrift skill writes a SKILL.md that teaches an agent to use those commands via Bash, so your entire MCP fleet is reachable from a skill at ~zero context tokens. One config powers the chat client, the shell, and the skill; you pay the fleet's context tax once, and you don't have to choose.
Benchmark
📊 The MCP Context Tax — live
18 popular MCP servers cost 83,131 tokens on every turn — 41.6% of a 200k context window, gone before you type a word. TokenThrift makes the same 18 cost ~490 tokens. You keep 99.4%. 🎉
| Rank | Server | Tools | Without | | With TokenThrift* |
|---:|:---|---:|---:|:---|---:|
| 1 | github | 44 | 28,073 | ██████████ | 0 |
| 2 | desktop-commander | 26 | 15,193 | █████ | 0 |
| 3 | chart | 27 | 14,837 | █████ | 0 |
| 4 | wikipedia | 22 | 5,598 | ██ | 0 |
| 5 | aws-documentation | 4 | 4,040 | █ | 0 |
| 6 | filesystem | 14 | 3,058 | █ | 0 |
| 7 | memory | 9 | 2,688 | █ | 0 |
| 8 | arxiv | 10 | 2,641 | █ | 0 |
| 9 | everything | 13 | 1,915 | █ | 0 |
| 10 | context7 | 2 | 1,229 | █ | 0 |
| 11 | sequential-thinking | 1 | 1,147 | █ | 0 |
| 12 | duckduckgo | 2 | 778 | █ | 0 |
| — | All 18 servers | 190 | 83,131 | | ~490 |
| — | You save | | 82,641 | | 99.4% ↓ |
* TokenThrift loads a tool's schema only when the model actually needs it, so idle servers cost 0 — and the proxy adds a flat ~490 tokens no matter how many servers you connect. Auto-measured 2026-07-07; reproduce with node bench/tax-index.js bench/daily-config.json.
Reproducible with node bench/bench.js 10 20:
| Setup | Tools exposed | Context tokens / turn | |---|---|---| | 10 servers connected directly | 200 | 50,380 | | Via TokenThrift | 4 | 488 |
99.0% reduction, every turn. And that's just schemas — response thrifting keeps large tool outputs out of context too: a 12,000-token JSON export costs the model a ~300-token preview, then only the fields it asks for.
Meaning-based matching runs on a small AI model (~25 MB) that lives entirely on your machine — downloaded once, then fully offline; nothing you do is sent anywhere. If it isn't available, TokenThrift falls back to plain keyword search automatically.
Quick start
Install Node.js — the free runtime TokenThrift runs on. Go to nodejs.org, download the LTS installer, and click through it like any other app. (Already installed? Version 20 or newer is fine.)
Open a terminal:
- Mac: press
⌘ Space, typeTerminal, press Enter. - Windows: press the Windows key, type
PowerShell, press Enter.
- Mac: press
Type this and press Enter:
npx tokenthrift-mcp initIt finds the AI apps on your machine that use MCP servers, moves their servers behind TokenThrift, and saves a dated backup of every file it touches — the undo instructions are printed at the end.
Quit and reopen your AI app. Done. While the app is running, open http://127.0.0.1:41414 in your browser to watch what you're saving.
If it prints "No MCP servers found", your apps don't have any servers connected yet, so there's nothing to optimize — connect your first server in the app, then run the command again. And if your app isn't auto-detected, find it under Works with your client below for a copy-paste setup.
From npm:
npx -y tokenthrift-mcp tax # optional first look, read-only: what is my setup costing every turn?
npx tokenthrift-mcp init # one command: absorbs your servers AND rewires your clients
# (18 clients, from Claude Code/Desktop, Cursor, and VS Code
# to LM Studio and OpenCode — full list below)From a source checkout:
npm install && npm run build
node dist/index.js init(Installed from npm, every command below is available as tokenthrift …; from source, substitute node dist/index.js ….)
init scans every known client config — 18 apps, on macOS, Windows, and Linux — absorbs every server into tokenthrift.config.json, and rewrites each client config to register only TokenThrift — after saving a timestamped .bak backup next to every file it touches. Restart your client and you're done; no JSON editing, ever. Clients that can't display dynamically loaded tools (Cursor, Claude Desktop, LM Studio) automatically get dispatch mode, so everything works everywhere. Prefer to see first? --dry-run shows exactly what would change; --no-apply writes the config but leaves your clients alone. Or write the config by hand:
Point TokenThrift at any standard MCP config (it understands the mcpServers map shape that MCP clients use):
// tokenthrift.config.json
{
"mcpServers": {
"local-tools": { "command": "npx", "args": ["-y", "some-mcp-server"] },
"remote-api": { "url": "https://mcp.example.com/api", "headers": { "Authorization": "Bearer ..." } }
},
"pinned": ["local-tools__create_issue"]
}Then register only TokenThrift with your client:
{
"mcpServers": {
"tokenthrift": { "command": "node", "args": ["/path/to/tokenthrift-mcp/dist/index.js", "--config", "/path/to/tokenthrift.config.json"] }
}
}Stdio and Streamable HTTP/SSE downstreams are both supported. pinned tools (or "server/*") are always exposed without a find_tools round-trip.
Works with your client
TokenThrift speaks standard MCP on both sides, so any app that can run a local MCP server can run TokenThrift. One thing decides the flavor:
- Dynamic mode (default): tools loaded mid-session appear in the client live, announced via
tools/list_changednotifications. Needs a client that honors them. - Dispatch mode (
--dispatchon that client's entry): acall_tooldispatcher with inline schemas — nothing has to appear mid-session, so it works in every client, at the cost of one level of indirection.initapplies it automatically where it's known to be needed.
Rewired automatically by tokenthrift init
Verified July 2026; "MCP since" is the oldest client version TokenThrift works with.
| Client | MCP since | Live tool loading (dynamic mode) | init preset |
|---|---|---|---|
| Claude Code | every release (Feb 2025) | ✅ v2.1.0+ (Jan 2026); older builds: add --dispatch | dynamic |
| Claude Desktop | any build since Nov 2024 | ❌ ignores list_changed | dispatch (auto) |
| Cursor | ≥ 0.45 | ❌ ignores it mid-session; ~40-tool cap | dispatch (auto) |
| Windsurf | ≥ Wave 3 (Feb 2025) | undocumented | dynamic + ceiling clamp |
| VS Code + Copilot | ≥ 1.99 (GA in 1.102) | ✅ (new tools usable on the next turn) | dynamic + ceiling clamp |
| VS Code Insiders | same as VS Code | ✅ | dynamic + ceiling clamp |
| Cline | ≥ 2.2.0 (Dec 2024) | undocumented | dynamic |
| Roo Code | ≥ 3.x — project discontinued May 2026; init still migrates it | undocumented | dynamic |
| Zed | tool calling since the Agent Panel (May 2025) | ✅ v0.219.4+ (Jan 2026) | dynamic |
| Gemini CLI | every release (Google retired the free API tier Jun 2026; works on paid/enterprise) | ✅ ~v0.29+ | dynamic |
| Qwen Code | every release | undocumented | dynamic |
| Antigravity | every release (config shared by the IDE and Antigravity CLI) | undocumented | dynamic |
| Copilot CLI | every release (GA Feb 2026) | undocumented | dynamic |
| LM Studio | ≥ 0.3.17 | ❌ advertises listChanged: false | dispatch (auto) |
| Kiro | every release (the IDE; Kiro CLI reads the Amazon Q config below) | undocumented | dynamic |
| Amazon Q CLI | legacy global config — the product is now Kiro CLI | undocumented | dynamic |
| BoltAI | ≥ 1.34 | undocumented | dynamic |
| OpenCode | every release (Jun 2025) | ✅ | dynamic |
| Crush | ≥ 0.1.0 | ✅ | dynamic |
If a row says undocumented and loaded tools don't show up mid-session in that client, add --dispatch to its TokenThrift entry — everything else keeps working identically.
Everything else — copy-paste setups
For clients init can't rewrite (their config isn't JSON, or lives inside the app), register TokenThrift by hand. Two tips first:
- Save your config as
~/.tokenthrift/tokenthrift.config.jsonand every snippet below works as-is — TokenThrift finds it from any working directory, no--configflag needed. (Or pass--config /absolute/pathexplicitly.) - GUI apps sometimes launch without your shell's PATH, so
npxmay not resolve. If a server won't start, runnpx tokenthrift-mcp init --no-apply— it prints an entry with the absolutenode+ script paths filled in; the dashboard's copy-paste tile does the same.
codex mcp add tokenthrift -- npx -y tokenthrift-mcp --dispatchor in ~/.codex/config.toml:
[mcp_servers.tokenthrift]
command = "npx"
args = ["-y", "tokenthrift-mcp", "--dispatch"]Codex doesn't refresh tool lists mid-thread, hence --dispatch.
goose configure → Add Extension → Command-line Extension, or in ~/.config/goose/config.yaml (Windows: %APPDATA%\Block\goose\config\config.yaml):
extensions:
tokenthrift:
name: TokenThrift
type: stdio
cmd: npx
args: ["-y", "tokenthrift-mcp", "--dispatch"]
enabled: true
timeout: 300Goose reads tool lists at startup only, hence --dispatch.
Needs IDE 2025.1+. AI Assistant: Settings → Tools → AI Assistant → Model Context Protocol (MCP) → Add, paste:
{ "mcpServers": { "tokenthrift": { "command": "npx", "args": ["-y", "tokenthrift-mcp"] } } }Junie: the same JSON in ~/.junie/mcp/mcp.json (or .junie/mcp/mcp.json per project — the CLI reads the same files).
In ~/.continue/config.yaml (MCP works in Agent mode only):
mcpServers:
- name: TokenThrift
type: stdio
command: npx
args: ["-y", "tokenthrift-mcp", "--dispatch"]Continue doesn't implement list_changed, hence --dispatch.
Settings → Agents → MCP servers → + Add, paste:
{ "mcpServers": { "tokenthrift": { "command": "npx", "args": ["-y", "tokenthrift-mcp", "--dispatch"] } } }Settings → MCP Servers → + Add Server: Type STDIO, Command npx, Arguments -y tokenthrift-mcp. Live tool loading works (dynamic mode).
In librechat.yaml:
mcpServers:
tokenthrift:
command: npx
args: ["-y", "tokenthrift-mcp", "--dispatch"]All of these register a local stdio server through their settings UI with the same two values — command npx, arguments -y tokenthrift-mcp --dispatch:
- Raycast (macOS, v1.98+, Pro): the "Install MCP Server" command.
- Msty Studio: Toolbox → Tools → Add New Tool → STDIO / JSON — paste the bare object
{ "command": "npx", "args": ["-y", "tokenthrift-mcp", "--dispatch"] }(nomcpServerswrapper). - 5ire: Tools panel → new tool (dynamic mode works here — you can drop
--dispatch). - Witsy: Connectors → Add MCP server (stdio).
- Trae (ByteDance): chat sidebar → MCP → Add → Configure Manually, standard
mcpServersJSON. - Perplexity Desktop (Mac): Settings → Connectors → local MCP.
URL-only clients (Claude web/mobile, ChatGPT, Open WebUI): use remote mode. These clients never spawn local processes — Claude on web/mobile runs custom connectors from Anthropic's cloud, ChatGPT's Developer Mode only accepts server URLs, and Open WebUI speaks Streamable HTTP only — so instead of being spawned, TokenThrift serves them:
tokenthrift serve # Streamable HTTP on 127.0.0.1:41416
cloudflared tunnel --url http://127.0.0.1:41416 # free https:// hostname in frontserve prints a capability URL (/mcp/<64-hex-token>): the token lives in the path because hosted clients can't send custom headers, so the URL itself is the credential — paste https://<tunnel-host>/mcp/<token> into Claude's custom connectors or ChatGPT's Developer Mode and the whole fleet is behind it. Header-capable clients (Open WebUI) can instead use plain /mcp with Authorization: Bearer <token> (token in ~/.tokenthrift/remote.token, 0600). Every remote session gets the same treatment as a local one — per-session tool visibility, redaction, injection scanning, quarantine, rug-pull detection — and --rotate-token revokes a leaked URL instantly. Tailscale Funnel or any reverse proxy works in place of cloudflared; the tunnel supplies TLS, the token supplies auth (checked in constant time, which also defeats DNS rebinding — a rebound page can reach the port but not the token). (Aider never merged MCP support at all.)
Config options
| Option | Default | What it does |
|---|---|---|
| pinned | [] | Tool names (or "server/*") always exposed to the model, no find_tools needed. |
| compressSchemas | true | Trims verbose tool/parameter descriptions in loaded schemas (structure untouched). |
| evictAfterTurns | 25 | Auto-unloads tools unused for N turns; pinned tools are exempt. 0 disables. |
| maxLoadedTools | 25 | Hard ceiling on simultaneously exposed tools — loading past it evicts the least-recently-used. Guarantees you stay under client tool caps (Cursor ~40, Windsurf 100); autopilot clamps it automatically when a capped client connects. 0 disables. |
| warmStart | true | Preloads this project's historically most-used tools at boot. |
| callTimeoutSeconds | 120 | Per-call timeout on downstream tools; a hung server errors cleanly instead of freezing your session (progress notifications reset the clock, so long-but-alive calls finish). |
| dedupTtlSeconds | 300 | Serve repeat identical read-only-looking calls (get_*, list_*, …) from cache. 0 disables. |
| redactSecrets | true | Scrub known credential patterns from tool results before they reach the model. Also available per-server: "redactSecrets": false on a trusted vault exempts just that server. The dashboard has a human-only pause switch (auto re-arms in 15 min). |
| audit | false | Append a JSONL record per proxied call to ~/.tokenthrift/audit.jsonl. |
| scanResults | "warn" | Scan tool results (and resource/prompt content) for prompt-injection phrasing smuggled into returned data. "warn" appends an untrusted-content banner and lets the data through; "block" refuses the result; "off" disables. |
| toolChangePolicy | "warn" | Rug-pull detection: the first-seen contract (name + description + schema) of every tool is pinned in ~/.tokenthrift/tool-pins.json. When a tool's contract later changes, "warn" logs it, surfaces it in stats and tokenthrift scan, and accepts the new contract; "block" quarantines the changed tool until you re-approve (delete its line from tool-pins.json); "off" disables. |
| minimalEnv | false | Spawn stdio servers with a minimal environment (PATH/HOME-class basics only) instead of your entire shell env, so a compromised server can't read unrelated secrets. A server that legitimately needs a variable lists it explicitly under env (e.g. "GITHUB_TOKEN": "${GITHUB_TOKEN}"). Also settable per-server. |
| chains | [] | Thrift Chain definitions (see below). |
| dispatchMode | "dynamic" | "dispatch" adds a call_tool dispatcher with inline schemas for clients that handle listChanged poorly. Per-client override: add --dispatch to one client's TokenThrift entry (what init does for Cursor, Claude Desktop, and LM Studio). |
| prefer | [] | Server names to favor when several tools score similarly (two search servers, two file servers). Toggle it with the ★ next to each server in the dashboard — no config editing needed. |
| autoLoadHighConfidence | true | When find_tools is highly confident with a clear winner, that tool comes back already loaded — the AI's next action can be the call itself, no load_tools round trip. Ties and weak matches still require a deliberate choice. |
| autopilot | true | Self-tuning: safe fixes are applied automatically (extra replica for a slow read-only server, wider eviction window on reload churn); anything touching your config becomes a one-click Apply in the dashboard. |
| pricePerMTok | 3 | USD per million input tokens, for the $ savings display. |
| licenseKey | — | Signed commercial license key (TT1.…). Usually installed once with tokenthrift license install <key> instead; this entry overrides the installed key. Absent = personal/non-commercial use. |
| per-server allow / deny | — | Glob lists controlling which of a server's tools are exposed at all (e.g. "deny": ["delete_*"]). Denied tools can't be discovered, loaded, or called. |
| per-server instances | 1 | Replica pool: run N copies of a (stdio) server and route each call to the least-busy one. For slow stateless servers under concurrent load — keep stateful servers (sqlite, memory) at 1. |
| per-server maxConcurrent | unlimited | Cap simultaneous in-flight calls to a server; extras wait in a FIFO queue. Saturation is reported in stats, the dashboard, and a note on affected results. |
| per-server redactSecrets | follows global | Set false on a trusted secrets manager so keys fetched from it on purpose flow through, while every other server stays scrubbed. |
All persistent state (usage history, lifetime ledger, audit log) lives in ~/.tokenthrift/; set TOKENTHRIFT_HOME to relocate it. A config saved as ~/.tokenthrift/tokenthrift.config.json is found from any working directory — handy for GUI clients that launch from an unpredictable one.
A Thrift Chain definition looks like:
"chains": [{
"name": "weather_report",
"description": "Get a city's weather and post it to the reports channel.",
"params": { "city": "City to report on" },
"steps": [
{ "tool": "weather__get_forecast", "args": { "city": "{{input.city}}" } },
{ "tool": "chat__send_message", "args": { "channel": "reports", "text": "Update: {{prev}}" } }
]
}]Downstream servers that crash are auto-reconnected with exponential backoff; while one is down, its tools are hidden from routing so the model never calls a dead server.
Everything else in the box
- A router that learns. TokenThrift records which tools each project actually loads, calls, and succeeds with (locally, in
~/.tokenthrift/usage.json) and feeds it back into ranking. Routing gets sharper the longer you use it — and warm starts preload each project's habitual tools at boot. - Thrift Chains. Bundle a routine you run all the time — say, "get the weather, then post it to the team channel" — into one custom tool. The AI makes a single call ("weather_report for Denver") and TokenThrift runs all the steps itself, passing each step's output to the next. The AI never sees the in-between results, only the final answer — so a 3-step routine costs the same as 1 call. Chains are a few lines in the config file (each step names a tool and its inputs;
{{prev}}means "whatever the last step returned"), and TokenThrift even notices sequences you repeat often and suggests turning them into chains. - Secret redaction. If a tool result happens to contain a credential — an API key, a GitHub/Slack token, a private key — TokenThrift blanks it out before the AI ever sees it, so secrets can't leak into the AI's memory (or a provider's logs) by accident. Only unambiguous credential formats are touched; normal text is never altered. On by default, with three ways to let keys through when you mean to: mark your secrets-manager server
"redactSecrets": falseso keys fetched from it on purpose flow through while every other server stays scrubbed; flip the switch on the dashboard for a 15-minute pause that re-arms itself automatically; or set the global"redactSecrets": false. The pause switch lives on the dashboard only — deliberately out of the AI's reach, so a hijacked session can't turn your protection off. - Audit trail.
"audit": trueappends a JSONL record per proxied call (tool, server, ok, duration, tokens, redactions) to~/.tokenthrift/audit.jsonl— every MCP call in your org, one grep away. - Dispatch mode.
"dispatchMode": "dispatch"swaps dynamic loading for acall_tooldispatcher with inline schemas — full compatibility with clients that handletools/listChangedpoorly. Also available per client: add--dispatchto a single client's entry (Cursor, Claude Desktop, and LM Studio get this automatically frominit) while other clients share the same config in dynamic mode. ${ENV_VAR}expansion in configs, so auth headers never live in the file.- Shared daemon mode.
tokenthrift daemonruns one core; every session attaches to it (--daemon-port, auto-detected). Five parallel agent sessions cost the downstream RAM of one, and the second session boots instantly — schemas harvested, embeddings warm. Each attached session reports its project directory, so learning and warm starts stay per-project even through the shared core. - Per-session tool visibility (built for agent swarms). Every attached session — every subagent — has its own loaded-tool list: subagent A sees only the 2 functions it loaded, B sees its 3, and neither pays for the other's. Meanwhile they share the good stuff — one set of server processes, one answer cache (parallel agents asking the same question cost one execution), one learning history. Spawning 10 subagents through a direct MCP setup costs 10 × your full tool bill before any work starts; through TokenThrift each starts at ~500 tokens. In numbers: on a typical 5-server fleet (github, filesystem, git, memory, fetch — ~35,600 schema tokens/turn per the live tax index), a 10-subagent × 10-turn swarm burns ~3.6M tokens of pure schema overhead per run — about $36 at frontier input rates ($10/MTok) — versus ~60K tokens (~$0.60) through TokenThrift.
- Tool-poisoning quarantine. Every schema is scanned for prompt-injection (instruction hijacks, conceal-from-user directives, exfiltration phrasing, cross-tool shadowing directives, hidden-recipient directives, invisible Unicode) before the model ever reads it. Flagged tools are undiscoverable and uncallable.
tokenthrift scanprints a standalone security report (exit code 2 on findings — CI-friendly). - Rug-pull detection. A tool that scanned clean at install can be swapped for a hostile version later — a server update, a hijacked release, a server that turns after gaining trust. TokenThrift pins the first-seen contract (name + description + schema) of every tool in
~/.tokenthrift/tool-pins.json; a later change is logged, surfaced in stats andtokenthrift scan, and (withtoolChangePolicy: "block") quarantined until you re-approve it. Server-initiated elicitation and sampling requests are scanned with the same injection detectors before they're relayed to your client, calls missing declared-required arguments are rejected before they reach a server that might act on a half-formed request, and with"audit": trueevery audit record carries an args hash + tool-contract hash so incident response can prove which inputs and which version of a tool produced which result. - Result-injection scanning. Descriptions aren't the only injection vector — the nastier one is a hidden instruction inside the data a tool returns (a poisoned document, an issue body, a web page). TokenThrift scans tool results (and resource/prompt content) with the same detectors:
scanResults: "warn"(default) tags the payload with an untrusted-content banner telling the model to treat it as data, not instructions;"block"refuses it outright;"off"disables. The count shows on the dashboard. - Minimal spawn environment.
minimalEnv: true(global or per-server) launches stdio servers with only PATH/HOME-class basics instead of your entire shell environment, so a malicious or compromised server can't read unrelated secrets — the "one leaky server exposes your whole ecosystem" risk. A server that legitimately needs a token lists it explicitly underenv("GITHUB_TOKEN": "${GITHUB_TOKEN}"), and only that variable crosses the boundary. - Supply-chain lint.
tokenthrift scanandtokenthrift doctorflag servers launched from unpinned auto-installers (npx …@latest, unversioneduvx) — where a hijacked upstream release would silently run new code on your machine — and remote servers reached over plain HTTP. Advisory (never changes the exit code on its own), so it informs without breaking CI. - Call dedup cache. When the AI asks the exact same read-only question twice (retries, or losing track after its memory is summarized), the second answer comes from cache — instant, and the server isn't bothered.
- In-flight coalescing. If an identical read-only call is already running (another daemon session, a retry), the second caller joins the first's result instead of executing twice. Ten sessions asking a slow server the same question cost one execution.
- 100% protocol passthrough. Resources (original URIs) with subscriptions and templates, prompts (namespaced
server__prompt) with argument completion, downstream sampling, elicitation, and roots requests relayed to your client, progress notifications, live list-changed updates for tools/resources/prompts/roots. ToolreadOnlyHint/destructiveHintannotations are honored by the dedup cache and replica-safety checks. Going through TokenThrift never costs you a protocol feature you'd have had directly. - Autopilot. You shouldn't need this README. TokenThrift watches its own runtime signals and tunes itself: a slow read-only server under concurrent load gets an extra replica automatically; tools that keep reloading right after eviction widen the eviction window live; when a tool-capped client connects (it reads the client's name from the MCP handshake), the loaded-tools ceiling is clamped so Cursor's 40-tool cap, Windsurf's 100, or VS Code Copilot's 128 can never be hit, and Cursor gets a one-click dispatch-mode recommendation. Anything that would change your config file — replicating a server that takes writes, switching dispatch mode, disabling an unused expensive server — appears in the dashboard as a plain-language finding with a one-click Apply (hot reload does the rest). Every automatic action is logged and visible;
"autopilot": falseturns it all off. - Replica pools & load stats. Per-server
instancesruns N copies of a slow stateless server with least-busy routing;maxConcurrentcaps a fragile one. Live per-server load (in-flight, queue depth, rolling latency, replica count) shows in stats and the dashboard, andfind_toolswarns the model when a matching tool lives on a slow server. - Error coaching. When a tool's recent calls failed, its loaded schema carries the failure message as a hint — the proxy teaches the model to use tools correctly, from your own history. Clears on the next success.
- Predictive prefetch. When history says "after A, this project calls B 80% of the time," B's schema preloads the moment A is called.
- Hot config reload. Edit
tokenthrift.config.jsonmid-session; servers join and leave live viatools/listChanged. No restarts, no client changes. - Dollar-denominated savings. The dashboard and stats convert tokens saved into dollars (
pricePerMTok, default $3/MTok). - Savings receipts.
tokenthrift reportprints a lifetime savings card — tokens and dollars kept out of context, duplicate calls avoided, secrets redacted, most-used tools — plus a copy-paste markdown version for sharing. Built entirely from local state; no servers spawned.
Limits (documented so they never surprise you)
- SQL over cached results needs Node >= 22.5 (
node:sqlite); older Nodes fall back to fields/grep/slice. Read-only by construction and enforced at three layers — a single-statement SELECT/WITH-only filter, a locked-down engine (no extension loading, no double-quoted string literals), and a SQLite authorizer that lets the query do nothing but read the one in-memory tablet(built from your own cached result): every write, DDL,ATTACH,PRAGMA, extension load, and recursive CTE is rejected by the engine at compile time. Column names are sanitized+quoted and values are bound parameters, so a hostile downstream result can't inject through the table build either. Output capped at 500 rows. - Results over ~1,500 tokens are thrifted (preview is the first 1,200 chars); the 20 most recent cached results are retrievable by id — older ids expire.
find_toolsreturns at most 8 hits.- The dedup cache holds 200 entries with a 300s TTL and only applies to read-only tools (MCP
readOnlyHintannotation, orget_*/list_*/search_*-style names without adestructiveHint). - On URI collisions (two servers exposing the same resource URI), the first enabled server wins.
- Argument completion against a downstream that lacks the capability returns an empty completion rather than an error.
- Downstream reconnects retry up to 20 times (500ms → 30s backoff).
- Boot waits at most 20s for downstreams before letting your client's handshake proceed; slower servers keep connecting in the background and their tools (plus any pins/warm starts that reference them) appear when they arrive.
- The 4 meta-tools are guaranteed under 600 tokens by a test that fails the build if a description edit bloats them.
- In daemon mode, per-project learning uses the project directory each session reports at attach; sessions from unknown clients fall back to the daemon's own project.
The MCP Context Tax Index
node bench/tax-index.js measures what popular MCP servers cost your context window every turn and prints a ranked, reproducible table. Run it on your own config: node bench/tax-index.js your-config.json. No checkout needed: npx -y tokenthrift-mcp tax runs the same measurement on the servers your clients already register — read-only, nothing changed.
Dashboard
Every running session and the shared daemon serve a local dashboard at http://127.0.0.1:41414 (disable with --no-dashboard, change with --dashboard-port); sessions attached to a daemon use the daemon's dashboard, and tokenthrift dashboard serves it standalone with no MCP client at all. It shows:
- Savings meter — live tokens-saved-per-turn (and in dollars), in-context vs kept-out, result tokens thrifted
- Server toggles — hide any server's tools from the model instantly, without disconnecting it
- Prefer stars (★) — when two servers offer lookalike tools, star the one you trust and it wins the tie from then on (saved to your config automatically)
- Redaction switch — pause secret redaction for 15 minutes (it re-arms itself); lives here and only here, so the AI can never flip it
- Try the router — type a task, see exactly which tools
find_toolswould hand the model - Copy-paste client config — a ready-made
mcpServerssnippet with your real paths filled in - Per-server load — in-flight calls, queue depth, average response time, and replica count beside each server
- Autopilot panel — what was auto-tuned (logged actions) and plain-language recommendations with one-click Apply/Dismiss
- License tier and lifetime totals in the header line
- First-run onboarding — with no servers connected yet, the page shows setup steps and a config example instead of blank tiles
The dashboard binds to localhost only and never leaves your machine.
Test
npm test # 116 end-to-end checks + a README-vs-code audit that fails the build
# if this document drifts from what the code does: routing, thrifting,
# chains, redaction, quarantine,
# reconnect, eviction, prefetch, hot reload, dispatch mode, shared daemon,
# per-session tool isolation, protocol passthrough (resources/prompts/
# sampling/elicitation/roots), client cap clamps, progress relay,
# isError conformance, meta-tool footprint ceilingCommands
| Command | What it does |
|---|---|
| tokenthrift | Run the proxy (stdio; attaches to a daemon if one is running) |
| tokenthrift find "<query>" | Route a task to the tools that can do it, from the shell — prints a confidence verdict and the best server__tool matches. --json for machine output. |
| tokenthrift call <server__tool> '<json>' | Invoke any tool from the shell (auto-discovers, no schema loaded into context). Large results come back as a preview + result_id; --full fetches more. Rides a running daemon (zero extra processes) or a one-shot core. |
| tokenthrift skill | Generate a Claude Skill (.claude/skills/tokenthrift/SKILL.md) that drives the whole fleet via find/call at ~zero context tokens. --print to stdout. |
| tokenthrift tax | The first touch — read-only. Finds the servers your clients already register, measures what their schemas cost every turn, prints the ranked bill and the fix. Changes nothing. --json for machines; optional [config.json] to measure one file |
| tokenthrift init | Absorb servers from client configs and rewire the clients (with backups); --dry-run, --no-apply. Covers Claude Code, Claude Desktop (auto-dispatch), Cursor (auto-dispatch), Windsurf, VS Code + Insiders, Cline, Roo Code, Zed, Gemini CLI, Qwen Code, Antigravity, Copilot CLI, LM Studio (auto-dispatch), Kiro, Amazon Q CLI, BoltAI, OpenCode, Crush, and project .mcp.json |
| tokenthrift report | Print your savings receipts — tokens & dollars saved, fleet size, top tools — with a shareable markdown card |
| tokenthrift daemon | Run one shared core that all sessions attach to |
| tokenthrift serve | Remote mode for URL-only clients (Claude web/mobile custom connectors, ChatGPT Developer Mode, Open WebUI): the fleet over Streamable HTTP with capability-URL + Bearer auth. --port (41416), --host, --rotate-token; front with cloudflared/Tailscale Funnel for the https URL hosted clients require |
| tokenthrift dashboard | Serve the dashboard standalone (no MCP client needed) — status, toggles, onboarding |
| tokenthrift scan | Security-scan every server's tool descriptions for poisoning (CI: exits 2 on findings) |
| tokenthrift doctor | Diagnose everything: config, data dir, ports, embeddings, per-server connectivity and schema cost |
| tokenthrift license | Install or inspect your signed license key (install <key> / show / remove). Verified offline with Ed25519 — no phone-home, ever |
License
Free for personal and non-commercial use. Commercial use requires a license — purchase takes under a minute, and your signed license key arrives with the receipt. Launch pricing: early buyers keep their rate forever.
| Tier | Price | | |---|---|---| | Personal — non-commercial use, all features | Free forever | Get started | | Builder — 1 developer | $99 one-time | Buy | | Studio — up to 5 developers, all updates | $490 / year | Subscribe | | Platform — org-wide, self-hosted, priority support | $4,999 / year | Subscribe |
Every purchase comes with a signed license key — one command and you're done: tokenthrift license install <TT1.…key…>. Verification is offline (Ed25519 against a public key baked into the package): no phone-home, no account, no license server. The dashboard shows who the fleet is licensed to, and the key sets remote mode's seat count — personal and Builder run 1 concurrent remote session, Studio runs 5, Platform is uncapped. A session past the cap bumps the least-recently-used one rather than locking anyone out; local daemon sessions are never capped.
Lapsed subscriptions keep the last version they paid for — the key itself enforces this: it stays valid for every version released before it expired. Full terms in LICENSE.md.
Personal use is free forever — no catch, no donation button. If TokenThrift is earning its keep on your own projects and you want to support it, the Builder license is the way to say thanks (and it's the license you'll need anyway the day it goes to work).
TokenThrift is a Thriftlayer product. Built by David Burdick, pair-programmed with Claude.
