Lossless-first compression layer for AI-agent tool outputs — byte-faithful on fields you mark critical, configurable lossy reduction only where you opt in.
Project description
terse
The lossless-first MCP compression proxy: it makes tool output smaller without ever changing what your agent reads — byte-faithful by default, lossy only where you explicitly opt in.
terse reduces tokens two ways, and they are not equally easy to copy. That split is the whole positioning.
1. The lossless codec — the reach. terse removes only structural overhead:
pretty-print whitespace, keys repeated once per record, repeated values, repeated
nested schema. The transformed bytes are the model's input — a denser but still
legible representation, not an offload. There is no decode step, no ML model in the
loop, and every transform has an exact inverse (a round-trip gate asserts
decompress(compress(x)) == x over the whole corpus). This is the guarantee most
tools in this space decline to make: headroom's JSON path is lossless on uniform arrays
but falls back to dropping rows on larger/irregular record sets, recoverable only via
a retrieve round-trip against a cache that expires (verified, v0.32.0; default 30-min
TTL); Anthropic/OpenAI context-editing drops old tool results server-side. terse
never silently mutates what the model sees — and
"lossless" is the category, not the token count. The tabularization primitive itself
is public (formats like TOON publish it standalone, MIT-
licensed, ~40% on flat arrays), so the codec is terse's demo, not its moat: a
motivated competitor could clone it.
2. The stateful cross-call diff — the moat. When the same tool is called again — poll a list, re-read a file — terse emits a lossless delta against the prior result instead of the whole payload (73% smaller on repeated calls in the benchmark below). This is the one axis a stateless encoder architecturally cannot reach: TOON, headroom's stateless per-call compressor, and server-side history-pruning all pay the full column every call because none of them remember the last result. terse can only do it because it lives in the session as a transparent proxy. Cloning the codec is a weekend; cloning the diff means becoming a session-spanning proxy — a different product. Default-on since its validation program completed (see Status).
Around those two sits the bundle that turns a byte filter into a control plane you
don't want to rip out: MCP-native proxy packaging (transparent to any downstream
server, no client-side reformatting), a live savings ledger (terse stats), a
fluency-gated lossy escape hatch, and self-installing ops tooling (install-mcp,
mcp-status) — each diff/lossy tier validated by a behavioral eval before it was ever
turned on by default.
It is selective by design. Measurement on real tool output showed the win is strongly per-tool (0–30%): large on record/symbol-shaped verbose output, near-zero on already-minified or already-projected tools. So terse applies per-tool policy rather than compressing everything blindly.
How It Works
terse transforms a tool's JSON output through a tiered, fully-lossless pipeline, then (optionally) serves it through a per-tool policy that decides which tiers run.
- Tier 0 — minify: strip insignificant whitespace.
- Tier 0 — tabularize: a list of uniform records becomes one header + value rows (keys written once, not once per record), recursively hoisting nested uniform-dict columns into a shared header.
- Tier 0.5 — dictionary code: repeated string values and repeated whole subtrees
are folded into an inline legend (
~0,~1, …) proven disjoint from every literal in the payload. Committed only when it actually saves tokens, so it never regresses. - Tier 0.7 — cross-call diff (stateful, ON by default): when the same tool is called
repeatedly, the proxy emits a lossless delta against the prior result instead of
the full payload (the 91%-overlap headroom). Self-describing, verified to reconstruct
exactly, and emitted only when smaller — falls back to the full form otherwise.
Default-on since its validation program completed (fluency, nested-record coverage,
and the drift soak — see Status); opt out with
proxy --no-diff/install-mcp --no-diffor a policy-file"diff": false. Record-shaped JSON gets a row/key diff; non-JSON results (file reads, source excerpts, log tails) get a separate content-defined-chunking (CDC) diff — a rolling hash cuts chunk boundaries by content, not position, so an edit anywhere only perturbs the chunk(s) it overlaps and the rest is sent as references to the prior result. Each shape keeps its own diff base per tool. - Tier 1 — lossy (opt-in, per field):
truncatecaps and annotates a field marked{"lossy":"truncate","max":N}, gated by an acceptable-loss check (only marked, non-criticalfields may differ, each only as a valid truncation).drop-to-retrievereplaces a marked field with a handle, stores the original per session, and serves it back via a syntheticterse.retrievetool the proxy injects — gated so a drop is accepted only if the handle resolves to the exact original.summarize(needs a model) is still parsed but deferred — warned and left lossless. Off everywhere by default.
Every transform has an exact inverse, and a round-trip gate asserts
decompress(compress(x)) == x over the whole corpus. The transformed bytes are
the model's input — a denser but still-readable representation, not an offload.
The proxy also keeps a live savings ledger (on by default; --no-stats to opt
out): one payload-free JSONL record per result — sizes, tokens, and the decision
taken, never content — so terse stats can answer "how much did terse actually save
me this week?" from real sessions, not just the synthetic corpus.
Install
Needs Python 3.11+ and uv.
uv tool install "git+https://github.com/inth3shadows/terse.git@v0.2.0" # global `terse`
# → becomes `pipx install terse-mcp` once it lands on PyPI (see Status).
Quick Start (under a minute)
terse sits between your MCP client and a server and shrinks the server's tool results in flight. No config needed — the proxy is lossless-everywhere by default:
# 1. Wrap ANY stdio MCP server. Your agent talks to it exactly as before;
# terse compresses the results it returns, losslessly.
terse proxy -- uvx some-mcp-server --flags
# 2. See what it saved (the payload-free ledger is on by default):
terse stats
Want to eyeball the codec first, no server involved?
echo '[{"id":1,"state":"open","repo":"acme/widgets"},{"id":2,"state":"open","repo":"acme/widgets"},{"id":3,"state":"open","repo":"acme/widgets"},{"id":4,"state":"open","repo":"acme/widgets"},{"id":5,"state":"open","repo":"acme/widgets"},{"id":6,"state":"open","repo":"acme/widgets"}]' | terse gate -
# → round-trip lossless: PASS ; ~36% fewer cl100k tokens
(Savings grow with record count and repetition; on a single tiny object terse correctly declines and passes it through unchanged — it never inflates what it can't shrink.)
Does terse help my server?
The win is per-tool and terse only keeps what pays, so it never hurts — but it helps a lot
more on some shapes than others. Point it at a server and run terse stats to see for real;
as a rule of thumb:
| terse helps most | terse barely moves |
|---|---|
| record/array JSON (lists of objects) | already-minified or already-projected output |
| repeated values or nested repeated subtrees | free-text-dominated results (logs, prose, diffs) |
| verbose REST-ish payloads (GitHub, Jira, DB rows) | tiny single objects |
| tools you call repeatedly (cross-call diff) | binary / non-JSON blobs (passed through untouched) |
Wire it into your MCP client (permanent)
install-mcp rewrites your MCP config to launch a server through terse — reversible, and
transparent to the client. It needs a per-tool policy; the smallest useful one is:
echo '{"version":1,"defaults":{"tiers":["minify","tabularize","dictionary"]}}' > terse-policy.json
# Claude Code, user scope (~/.claude.json) — wrap a server you've already registered by name:
terse install-mcp --policy terse-policy.json <server-name>
# Project scope (a committed .mcp.json instead):
terse install-mcp --policy terse-policy.json --scope project --file .mcp.json <server-name>
terse mcp-status # confirm what's wrapped
terse uninstall-mcp <server-name> # cleanly restore the original entry
Other MCP clients (Cursor, etc.) read the same config shape — wherever a server is launched
as cmd --flags, launch it as terse proxy -- cmd --flags to get the same effect.
See USAGE.md for tuning a policy (terse tune) and reading terse stats.
From source (contributors): uv sync then uv run terse ...; uv run pytest is the
lossless gate.
Project Structure
src/terse/
transforms.py lossless tiers (minify, tabularize, dict coding) + round-trip gate
policy.py selective per-tool policy: load, match, apply
proxy.py MCP stdio middleware: compress a downstream server's tool results
stats.py live savings ledger (payload-free) + the `terse stats` aggregation
capture.py corpus capture (shape-tagged envelopes) + shape classifier
measure.py per-payload + cross-tokenizer token measurement
probes.py value-redundancy + cross-call-overlap ceiling probes
fluency/ does a model read the compressed form as accurately as raw JSON?
(questions / scoring / answerers / harnesses / pack behind one facade)
tokenize.py cl100k / o200k token counting
report.py markdown reports (savings, per-tool, probes, tokenizer, fluency)
html_report.py charted HTML companion (inline SVG, no JS/CDN) for measure/verify
cli.py entrypoint: gate / capture / measure / probe / validate / compress / proxy / stats / fluency
scripts/
gen_stress_corpus.py synthetic stress corpus for the fluency eval
bench/ terse-vs-TOON token benchmark on a real GitHub-API corpus
(fetch_corpus.sh, benchmark.py, diff_demo.py, toon_encode.mjs)
tests/ round-trip, measurement, probe, policy, and fluency tests
policy.example.json selective policy encoding the measured per-tool insight
corpus/ captured tool outputs (gitignored; may contain real data)
Verify it yourself
terse sits in your agent's critical path, so it earns trust by inspection. See VERIFY.md for the full walkthrough — or generate a self-contained report (lossless gate + per-tool token savings) in one command:
terse verify --out reports/verify-report.md # bundled sample, zero setup
terse verify --corpus corpus --out report.md # your own captured traffic
terse verify --html --out reports/verify-report.md # + a charted HTML report alongside it
terse verify --corpus corpus --json # machine-readable gate + savings (CI-checkable)
Benchmarks: terse vs alternatives
A head-to-head token-reduction benchmark on a corpus of real, public GitHub API
payloads (scripts/bench/) — the nested, record-shaped output that dominates real MCP
tool traffic. Everything below is lossless and verified per payload (a row is dropped
from the total if either encoder fails its round-trip), counted in cl100k_base (the same
tokenizer terse uses). Reproduce end-to-end:
cd scripts/bench && npm install # pins the official @toon-format/toon encoder
./fetch_corpus.sh # OR use the committed corpus snapshot as-is
uv run scripts/bench/benchmark.py # terse vs TOON vs baselines
uv run scripts/bench/diff_demo.py # terse's cross-call diff (its own axis)
The only directly-comparable public tool is TOON — a
lossless encoding that shares terse's tabularization primitive. The corpus is compact JSON
(no pretty-print whitespace), so minify saves ~0% and every number below is pure
structural gain, the hardest honest case:
| payload (real GitHub API) | records | raw tok | terse | TOON |
|---|---|---|---|---|
| gh_pulls | 30 | 151,165 | 76.1% | −8.4% |
| gh_workflow_runs | 20 | 76,032 | 80.3% | −7.5% |
| gh_issues | 30 | 48,032 | 32.7% | −8.0% |
| gh_commits | 30 | 69,652 | 26.5% | −4.5% |
| gh_dir_listing | 24 | 6,736 | 31.4% | −7.7% |
| gh_rate_limit | 1 obj | 357 | 13.4% | −36.7% |
| gh_repo_single | 1 obj | 1,652 | 0.0% | −4.4% |
| gh_commits_flat | 30 | 10,886 | 2.4% | 1.7% |
| gh_labels | 9 | 632 | 15.2% | 19.0% |
| weighted total | 365,144 | 58.3% | −7.1% |
(% = fewer cl100k tokens than raw; higher is better; bold = winner.)
Honest reading of this:
- On real nested API records, terse wins decisively and TOON regresses (−7.1% overall
— worse than raw). TOON is built for flat, uniform arrays; GitHub records are
deeply nested and non-uniform (a PR embeds repeated
user/head/base/reposubtrees), which terse's dictionary tier folds and TOON's tabular layout cannot — it adds key-path overhead instead. terse's headline 76% ongh_pullsis exactly this: 60 repeated copies of the same repo object collapsed to one legend entry. - TOON is not beaten everywhere — and the boundary is value repetition, not column
width. On
gh_labels(9 records × 7 columns — TOON's designed sweet spot) TOON leads, +19.0% vs terse's +15.2%. terse's decisive corpus win comes from nested repeated subtrees and long repeated string values — its dictionary and subtree-aliasing tiers fold them, TOON's flat tabular layout cannot — which is exactly what real GitHub records carry. On stripped-flat synthetic tables with none of that redundancy, the two converge: a seeded column-width sweep (uv run scripts/bench/width_sweep.py) shows them within a few points at every width, trading the lead by parity, with no clean column-count crossover (an earlier claim of a ≤3/≥4 boundary did not reproduce — see BENCHMARKS.md). So the honest frame is: terse wins where records repeat or nest (real tool output); TOON stays competitive on flat, low-redundancy uniform tables — "different niche," not "terse strictly dominant." - Neither tool helps much when free text dominates (
gh_commits_flat: long commit messages, ~2% either way) or on tiny single objects — matching terse's own "selective, 0–30%, per-tool" claim rather than contradicting it. Cross-call diff — the axis no stateless encoding has. When the same tool is called again (poll a list, re-read a file), terse emits a lossless delta against the prior result instead of the whole payload. TOON, minify, and terse's own single-shot codec all pay the full column every call. Modeling one repeated call per payload (two records changed, one appended — the poll-again pattern), the second call costs (uv run scripts/bench/diff_demo.py):
| repeated call | records | full re-send (terse) | diff | diff smaller |
|---|---|---|---|---|
| gh_commits_flat | 30 | 10,681 | 812 | 92.4% |
| gh_commits | 30 | 51,623 | 6,273 | 87.8% |
| gh_issues | 30 | 32,608 | 4,448 | 86.4% |
| gh_dir_listing | 24 | 4,779 | 977 | 79.6% |
| gh_pulls | 30 | 37,776 | 15,292 | 59.5% |
| gh_workflow_runs | 20 | 15,370 | 12,336 | 19.7% |
| weighted total | 152,837 | 40,138 | 73.7% |
The diff cost scales with what changed, not with payload size — so its win compounds
exactly where token cost otherwise does: a long agent loop re-fetching mostly-unchanged
results. (gh_workflow_runs is lower here only because its records are large and this
model changed a big nested field; a status/timestamp churn would diff far smaller.) This
is on top of the single-shot reduction above, and stacks with it.
Tools not benchmarked head-to-head, and why (no invented numbers):
| Tool | Why not a like-for-like row |
|---|---|
headroom (headroom-ai, headroomlabs-ai) |
The closest product competitor and far more adopted (star figures cited vary, ~29–49k — unverified). But its JSON compressor is a deterministic Rust transform, not an ML model (verified, v0.32.0): lossless on uniform arrays, yet falling back to dropping rows on larger/irregular record sets, recoverable only via a retrieve round-trip against a time-boxed, backend-dependent cache (default 30-min TTL; SQLite in the proxy path, in-memory if constructed directly — gone after expiry, eviction, or process exit). A separate, optional text/log compressor is ML (a keyless model download). Not comparable on a lossless token axis: terse's guarantee is unconditional — no cache, no TTL, no ML, no egress. (The headroom package on PyPI is a different, unrelated CLI.) |
| LLMLingua-2 (Microsoft, 6.4k★) | Lossy prompt compression via a trained token-classifier; operates on input prompts, not structured tool output. Verified on a JSON payload it strips the syntax ({, }, :, ") as low-information and emits invalid, unparseable JSON (and silently truncates past its 512-token window). Different axis entirely. |
| Anthropic context editing / OpenAI equivalents | Native, server-side, lossy history-pruning (drop oldest tool results past a threshold), no local artifact to run keylessly. This — not any third-party tool — is the real strategic overlap with terse for first-party API users. |
| Atlassian mcp-compressor (97★) | Primarily compresses tool schemas/descriptions at connect time (lossless deferred-disclosure) — complementary and stackable with terse (terse proxy -- mcp-compressor -- <server>). Caveat: an opt-in --toonify flag does reformat call results into TOON, so "schemas only" isn't strictly true — but that path is off by default and is a single static pass with no diffing, per-tool policy, or cross-call state, out-competed by terse's codec on that axis. |
Adoption honesty: terse is new (pre-PyPI, few/no stars); TOON (24.9k★) and headroom (widely adopted, star figures cited vary ~29–49k) are far more established. terse's defensible wedge is narrow and specific — unconditionally lossless (no expiring retrieve-cache), no ML dependency, MCP-transparent, plus cross-call diffing — not breadth of adoption.
Related Documentation
- Benchmarks — dated, reproducible terse-vs-TOON + competitor numbers
- Verify it yourself — prove losslessness, savings, and no-egress locally
- Technical Reference — architecture, pipeline, policy schema, limitations
- Usage Guide — running the CLI day-to-day and reading its output
- Changelog — notable changes per release
Status
A working, measured, selective lossless library, CLI, and MCP
stdio proxy. The proxy's open question — does a model read the compressed form as
well as raw JSON? — now has a measured answer: on a stress corpus, Claude Haiku 4.5
and Gemini 2.5 Flash match raw-JSON accuracy on the compressed form (100% paired) at a
37% token saving (terse fluency; see TECHNICAL.md). Whole-subtree aliasing (folding
repeated objects, not just strings) is built. Cross-call diffing is a lossless tier
that is now on by default — its full validation program passed: pair fluency
(fluency --diff, 4-model panel 100%), the nested-record surface (structure: diff
100% vs full-terse 94%), and long-chain drift soaked from both sides — mechanically
(tests/test_diff_soak.py — exact reconstruction hundreds of chained hops deep) and
behaviorally (fluency --diff-soak — no depth-correlated accuracy loss up to the
keyframe bound). Opt out per proxy (--no-diff) or per policy ("diff": false).
The Tier 1 lossy modes truncate and
drop-to-retrieve are built (opt-in, off by default); summarize remains designed but
not yet built — see TECHNICAL.md "Known Limitations".
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file terse_mcp-0.3.1.tar.gz.
File metadata
- Download URL: terse_mcp-0.3.1.tar.gz
- Upload date:
- Size: 528.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3903cf68d1c95b07d7ea127084b1affeb5a8821cd1595220b7839e76b1e0e96
|
|
| MD5 |
d07ff5d59cc7b02fe5c37bded300a76f
|
|
| BLAKE2b-256 |
6309eae472ac2c09db3c981a1243248e6aa3032ffd552ae8c319296121605f4c
|
Provenance
The following attestation bundles were made for terse_mcp-0.3.1.tar.gz:
Publisher:
release.yml on inth3shadows/terse
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
terse_mcp-0.3.1.tar.gz -
Subject digest:
a3903cf68d1c95b07d7ea127084b1affeb5a8821cd1595220b7839e76b1e0e96 - Sigstore transparency entry: 2195341461
- Sigstore integration time:
-
Permalink:
inth3shadows/terse@1b45b4b2e5a5e12ac04514dbf52ce9ef7088858d -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/inth3shadows
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1b45b4b2e5a5e12ac04514dbf52ce9ef7088858d -
Trigger Event:
push
-
Statement type:
File details
Details for the file terse_mcp-0.3.1-py3-none-any.whl.
File metadata
- Download URL: terse_mcp-0.3.1-py3-none-any.whl
- Upload date:
- Size: 193.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c6d97be0fa65a1df7b303323dc7deff5355538d651a8f12a63b3d7d7c29f192
|
|
| MD5 |
90bd3d489e07db69301362c33e8488bb
|
|
| BLAKE2b-256 |
347211d600b7bad38236de2d30128b5b62d8b25b34d4497bb8e7eb736e4b3b83
|
Provenance
The following attestation bundles were made for terse_mcp-0.3.1-py3-none-any.whl:
Publisher:
release.yml on inth3shadows/terse
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
terse_mcp-0.3.1-py3-none-any.whl -
Subject digest:
7c6d97be0fa65a1df7b303323dc7deff5355538d651a8f12a63b3d7d7c29f192 - Sigstore transparency entry: 2195341464
- Sigstore integration time:
-
Permalink:
inth3shadows/terse@1b45b4b2e5a5e12ac04514dbf52ce9ef7088858d -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/inth3shadows
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1b45b4b2e5a5e12ac04514dbf52ce9ef7088858d -
Trigger Event:
push
-
Statement type: