llmtrim
About
MCP server that compresses your LLM API requests so you pay less, with no change to the answers. Trims wasted tokens from prompts, history, tool output, and code before they're sent: -31% input / -74% output, measured live.
Details
- Author
- fkiene
- Categories
- Other, AI
Jump to
Setup
Install llmtrim in your MCP client (Claude Desktop, Cursor, Windsurf, and others).
Repository: https://github.com/fkiene/llmtrim
Follow the installation instructions in the repository README, then restart your MCP client.
Local proxy that compresses LLM API traffic so you pay less. Same answers, smaller bill.
−31% input · −74% output · −66% round-trip cost· 112 live A/B cases · ~5 ms/call · no model to load
UsingClaude Code? One install also gets you/subto serve it through CLIProxyAPI.
Proxy · CLI · MCP · library (Python · Ruby · Swift · Kotlin · JS/WASM)
What it does•Install•Day to day•In action•Works with•Claude Code•Numbers•Config•CLI & library
You run Claude Code, Codex, Cursor, or your own app. Every turn, the tool sends a large request: system prompt, tools, history, raw command output. You pay for every token of that, including the parts that do not help the model.
A 200-line build log with two errors. Tool schemas resent on every call. JSON with hundreds of near-identical rows. That bulk is still billed.
llmtrim sits on your machine as a local proxy, trims the waste, and forwards a smaller request. The reply is unchanged. You keep the same tools and answers; you spend less.
before: your tool ───── full request ─────▶ OpenAI / Anthropic / … ◀──────── reply ────────── after: your tool ──▶ llmtrim ──smaller──▶ OpenAI / Anthropic / … (on your machine) ◀──────── reply ────────── (same answer)
Compression cannot raise your bill or break a request; worst case is zero savings. Everything runs locally, nothing is sent to us.In action →
For Claude Code the same install also wires/subto route sessions through another subscription.Details →
npm install -g @llmtrim/cli@latest && llmtrim setup # open a new terminal, then keep working llmtrim status
That's it.setupstarts a local proxy, wires your shell, and enables recoverable tool-output shaping. When Claude Code is present, it also turns on/sub. You do not run a separate install for that.
Any tool that honorsHTTPS_PROXYworks (Claude Code, Codex, Cursor, Aider, your SDK). GitHub Copilot does not (certificate pinning).Full list →
# Linux / macOS curl -fsSL https://raw.githubusercontent.com/fkiene/llmtrim/main/install.sh | sh # Windows (PowerShell) irm https://raw.githubusercontent.com/fkiene/llmtrim/main/install.ps1 | iex # Package managers brew install fkiene/tap/llmtrim cargo binstall llmtrim scoop install llmtrim docker run -d -p 43117:43117 -v llmtrim-state:/data ghcr.io/fkiene/llmtrim
Menu-bar / system-tray popover with the same savings numbers. Bundled in Homebrew, Scoop, and npm;setupcan enable open-at-login. Open withllmtrim tray. On Linux desktops, interactiveensurecan fetch the tray binary from thelatest release(needslibwebkit2gtk-4.1andlibayatana-appindicator3).
No API keys stored (your tool's auth is forwarded). Prompts never touch disk; only anonymous token counts. Recoverable tool results stay in bounded daemon RAM for five hours by default and disappear on restart. Full threat model:SECURITY.md.
llmtrim ca openssl x509 -in ~/.llmtrim/ca.pem -noout -text | grep -A3 "Name Constraints"
llmtrim status # savings + health (aliases: monitor, gain) llmtrim update # new release, restart daemon, refresh integrations llmtrim ensure # match the recommended install state on this machine
Aftersetup,update, orensure, owned Claude Code/substays in sync with the binary.
Time series:llmtrim status --daily·--weekly·--monthly·--json·--csv.
An agent ran a build. The tool returned 58 lines; two were errors. All 58 would have been billed.
4,662 chars → 978 (−79%). Errors stay verbatim. Repeated INFO lines fold into a template plus the values (lossless when the range is regular).
# before (noise + signal) [2026-06-13T10:02:00Z] INFO compiling module core::worker::task_0 (incremental) … 28 more near-identical INFO lines … [2026-06-13T10:02:31Z] ERROR src/worker/pool.rs:214: mismatched types: expected usize, found i64 … 25 more INFO lines … [2026-06-13T10:03:01Z] ERROR src/net/conn.rs:88: cannot borrow buf as mutable more than once [2026-06-13T10:03:02Z] INFO build failed, 2 errors # after (errors verbatim; INFO folded losslessly) [{}] INFO compiling module core::worker::task_{} (incremental) [×30: (10:02:00Z..10:02:29Z step 1s; 0..29)] [2026-06-13T10:02:31Z] ERROR src/worker/pool.rs:214: mismatched types: expected usize, found i64 [{}] INFO compiling module core::net::conn_{} (incremental) [×25: 10:02:32Z..10:02:56Z; 0..24] [2026-06-13T10:03:01Z] ERROR src/net/conn.rs:88: cannot borrow buf as mutable more than once [2026-06-13T10:03:02Z] INFO build failed, 2 errors
echo '{"model":"gpt-4o","messages":[...]}' | llmtrim compress --provider openai
Log folding is one stage. Others kick in on different waste:
[!IMPORTANT] Compression cannot raise your bill or break a request. Each stage is re-measured with the provider's real tokenizer and undone if it does not save tokens. If the provider rejects the compressed body, the original is resent. Worst case is zero savings.
Existing prompt-cache prefixes (cache_control) are left alone. On shell-capable agent turns, a newly arriving tool result may be shaped once before its first cache write; the exact raw result remains recoverable with the emittedllmtrim recall r_…command.
Stages run in savings order. Nothing under acache_controlmarker is rewritten.
Defaultautoenables each stage only where it pays.safeis lossless-only.Config →
When~/.claudeexists,setup,update, andensurewire/sub(and routed subagents). No separate install command for that.
Send Claude Code throughCLIProxyAPIinstead of Anthropic, or as a fallback hop when the current path fails. Login is CLIProxyAPI's TUI; decide for yourself whether that fits the provider ToS.
Interactive:llmtrim status→ tab4 Sub— Off / Always / Fallback, theneto edit the input→output map (type to searchCLIProxyAPI's catalog;ssave,aadd,ddelete).[]rotate the fallback first hop.llmtrim updatealso updates CLIProxyAPI when you use it. PointLLMTRIM_CLIPROXY_URLat an existing instance to skip the managed sidecar.
Route only a delegated Claude Code subagent while leaving the parent window unchanged:
llmtrim agents install # also installed/refreshed by setup, update, and ensure
Then ask naturally:Implement it using a Grok subagent,use Terra, orreview this with GPT Terra. Provider-only agents preserve the child request's Claude tier through the configured mapping; an explicit model agent pins that provider model. Request-local agents override the window/suband global policy only for their own requests.llmtrim agents uninstallremoves only llmtrim-owned agent files and records an opt-out soensureleaves them removed.
This window only (installed with ensure; includes subagents; survives/clear):
/sub on [optional:cli-or-model] # gemini, codex, claude, grok, kimi, vertex, qwen, copilot, or a model id /sub off /sub status
Sidecar:~/.llmtrim/cliproxy/(binary + config). Auth:~/.cli-proxy-apiwhen present, else~/.llmtrim/cliproxy/auth. Env:LLMTRIM_SUB,LLMTRIM_SUB_MODE,LLMTRIM_CLIPROXY_URL,LLMTRIM_CLIPROXY_KEY.
Anthropic/loginvs claude.ai connectors:with globalsubinalwaysmode, by default llmtrim writes a dummyANTHROPIC_AUTH_TOKENinto~/.claude/settings.json(same idea asclaude-code-proxy'sANTHROPIC_AUTH_TOKEN=unused) so Claude Code does not need a live Anthropic OAuth session. The MITM strips that dummy token and sends/v1/messagesto CLIProxyAPI; non-messages Anthropic probes are answered locally so they never return401 Invalid bearer token.
Claude Code treats any API-key auth as overriding claude.ai login, soclaude.ai connectors are disabledwhile the dummy token is set. That is expected. To keep connectors (and accept Anthropic/loginwhen the session expires):
llmtrim sub anthropic-login keep # connectors OK; Anthropic login required llmtrim sub anthropic-login skip # default: no Anthropic /login; connectors off
Restart Claude Code aftersub on/sub off/sub mode/sub anthropic-loginfor the settings change to take effect. Fallback whose first hop is "what's in use" still needs a real Anthropic login.
Same engine, no proxy required. No extra model calls; compress runs in-process.
echo '{"model":"gpt-4o","messages":[...]}' | llmtrim compress --provider openai > out.json echo '{"model":"gpt-4o","messages":[...]}' | llmtrim send --provider openai
use llmtrim_core::{compress, ir::ProviderKind}; let out = compress(request_json, Some(ProviderKind::OpenAi))?;
import llmtrim out = llmtrim.compress(request_json, llmtrim.Provider.OPEN_AI, "aggressive")
import { compress } from "@llmtrim/js"; const out = compress(requestJson, "openai", "aggressive");
Bindings and WASM notes:crates/llmtrim-uniffi·crates/llmtrim-wasm.
llmtrim mcp install # Claude Code llmtrim mcp install --print # paste into any client
{ "mcpServers": { "llmtrim": { "command": "llmtrim", "args": ["mcp"] } } }
Tools:llmtrim_compress,llmtrim_compress_text,llmtrim_stats(same ledger asstatus).
Any tool that honorsHTTPS_PROXYand an env-provided CA:
No proxy: any MCP client can call llmtrim as tools (llmtrim mcp install), or use theCLI / library.
Providers come from thellm_providersregistry (OpenAI, Anthropic, Google, DeepSeek, Mistral, xAI, Moonshot, Zhipu, Qwen, OpenRouter, …) and update with it. Non-LLM connections pass through untouched.
Default is fine for most traffic.autoinspects each request and picks compressors by shape (tools →agent, code →code, long Q&A →rag, elseaggressive).
Override withLLMTRIM_PRESET=<name>orpreset = "<name>"in$XDG_CONFIG_HOME/llmtrim/config.toml:
autocomposes these per request shape, so most users never set them directly. Pick one when you know your traffic and want to skip shape detection:
Every stage is individually tunable via config flags;presetwins over individual flags. The full table is long; see the field list inconfig.rsor runllmtrim compress --help. The most useful knobs:
Env:LLMTRIM_PRESET(preset),LLMTRIM_CONFIG(config-file path).
These knobs are orthogonal to compression. Each resolves env-first, then from the config file, so set whichever fits. The env var wins when both are present.
extra_hostsentries must be exact hostnames (llm.acme.com, never a bareacme.com): each one widens the name-constrained MITM CA, which regenerates automatically on the next launch to cover them.
Claude Code options (subscription reroute) are underClaude Code.
export LLMTRIM_UPSTREAM_PROXY=http://host:port # or with auth: http://user:pass@host:port (redacted in logs)
Outbound calls useCONNECT+ verifying TLS; the upstream only sees the encrypted stream. Looping to llmtrim's own listen address is rejected. Put the variable in thedaemon'slaunch environment (launchd / systemd), not only your interactive shell. Profile secrets sit in plaintext.
Companion tools on another port (e.g.headroom) are fine.
Every case is sent twice, once original and once compressed, then both answers are scored and billed at real rates. Cost and quality are measured together, not estimated, across 112 cases:
The token cuts are model-independent (−31% input, −74% output). The dollar saving tracks the model's output-to-input price ratio: −66% here, projecting to −57% at GPT-4o rates and −59% at Claude Sonnet rates. The proxy compresses only the new-content surface and never rewrites the cache-controlled prefix, so your prompt-cache discount survives.
The same A/B on the standard academic suites, at a conservative shape-matched preset (qwen3-next-80b, paired 95% CI). Quality is the score on the original request vs the compressed one. GSM8K comes from the frontier above (n=12); the other three are the named benchmarks readers compare against (n=20 each):
Three rows compress with no quality loss; GSM8K is the one dip:
- BFCLdrops the tool schemas the query doesn't need (a menu of 2 to 37 candidates per call).
- SQuAD v2still answers its unanswerable questions correctly.
- TruthfulQAholds factual accuracy exactly: its ~75-token prompts are almost all answer text, so the safe preset finds nothing to cut.
- GSM8Ktrades −8pp of accuracy for −71% cost, so measure per workload before enabling its reasoning preset. ¹Its input goes negative because that preset injects a Chain-of-Draft instruction whose payoff is output-side (see the frontier table).
Evidence and a one-line reproduce (named-benchmark snapshot):
make -C crates/llmtrim-cli/bench data (cd crates/llmtrim-cli && cargo run -q --features live -- bench quality \ --corpus bench/data/squad2.jsonl --preset rag \ --model qwen/qwen3-next-80b-a3b-instruct --route "" --n 20)
Methodology, per-corpus frontier, and confidence intervals:crates/llmtrim-cli/bench/README.md. Reproduce it:
make -C crates/llmtrim-cli/bench data # pull real corpora (gsm8k, humaneval, dolly, hotpotqa, …) (cd crates/llmtrim-cli && cargo run -q --features live -- bench suite) # live A/B across all corpora (needs OPENROUTER_API_KEY) (cd crates/llmtrim-cli/bench/scripts && PYTHONPATH=. python3 -m benchkit.tools.chart) # regenerate the chart + table
Each tool compresses one slice of the request. llmtrim compresses input and output, leaves the cached prefix untouched to keep the prompt cache stable, and scores on whether the answer survives the cut, not on tokens removed. Both axes below use theo200k_baseencoder and reproduce from this repo.
Input reduction (deterministic) next to answer quality from a live A/B. Quality is the drop vs llmtrim at each tool's compared setting (✅ held, a statistical tie; ❌ significantly lower), so a big reduction with a ❌ means the tool bought tokens by losing answers:
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.




