terse-mcp 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- terse_mcp-0.3.1/.github/workflows/release.yml +71 -0
- terse_mcp-0.3.1/.github/workflows/tests.yml +42 -0
- terse_mcp-0.3.1/.gitignore +38 -0
- terse_mcp-0.3.1/.gitleaksignore +2 -0
- terse_mcp-0.3.1/.runechoguardignore +106 -0
- terse_mcp-0.3.1/BENCHMARKS.md +151 -0
- terse_mcp-0.3.1/CHANGELOG.md +58 -0
- terse_mcp-0.3.1/LICENSE +21 -0
- terse_mcp-0.3.1/PKG-INFO +345 -0
- terse_mcp-0.3.1/README.md +320 -0
- terse_mcp-0.3.1/TECHNICAL.md +389 -0
- terse_mcp-0.3.1/USAGE.md +648 -0
- terse_mcp-0.3.1/VERIFY.md +92 -0
- terse_mcp-0.3.1/corpus/.gitkeep +0 -0
- terse_mcp-0.3.1/docs/architecture.svg +661 -0
- terse_mcp-0.3.1/policy.example.json +38 -0
- terse_mcp-0.3.1/pyproject.toml +97 -0
- terse_mcp-0.3.1/scripts/bench/benchmark.py +139 -0
- terse_mcp-0.3.1/scripts/bench/corpus/gh_commits.json +1 -0
- terse_mcp-0.3.1/scripts/bench/corpus/gh_commits_flat.json +1 -0
- terse_mcp-0.3.1/scripts/bench/corpus/gh_dir_listing.json +1 -0
- terse_mcp-0.3.1/scripts/bench/corpus/gh_issues.json +1 -0
- terse_mcp-0.3.1/scripts/bench/corpus/gh_labels.json +1 -0
- terse_mcp-0.3.1/scripts/bench/corpus/gh_pulls.json +1 -0
- terse_mcp-0.3.1/scripts/bench/corpus/gh_rate_limit.json +1 -0
- terse_mcp-0.3.1/scripts/bench/corpus/gh_repo_single.json +1 -0
- terse_mcp-0.3.1/scripts/bench/corpus/gh_workflow_runs.json +1 -0
- terse_mcp-0.3.1/scripts/bench/diff_demo.py +149 -0
- terse_mcp-0.3.1/scripts/bench/fetch_corpus.sh +17 -0
- terse_mcp-0.3.1/scripts/bench/package-lock.json +19 -0
- terse_mcp-0.3.1/scripts/bench/package.json +8 -0
- terse_mcp-0.3.1/scripts/bench/toon_encode.mjs +16 -0
- terse_mcp-0.3.1/scripts/bench/width_sweep.py +76 -0
- terse_mcp-0.3.1/scripts/gen_stress_corpus.py +116 -0
- terse_mcp-0.3.1/src/terse/__init__.py +27 -0
- terse_mcp-0.3.1/src/terse/__main__.py +8 -0
- terse_mcp-0.3.1/src/terse/_secure_io.py +76 -0
- terse_mcp-0.3.1/src/terse/_version.py +24 -0
- terse_mcp-0.3.1/src/terse/capture.py +236 -0
- terse_mcp-0.3.1/src/terse/cli.py +1289 -0
- terse_mcp-0.3.1/src/terse/dropeval.py +388 -0
- terse_mcp-0.3.1/src/terse/fluency/__init__.py +102 -0
- terse_mcp-0.3.1/src/terse/fluency/answerers.py +57 -0
- terse_mcp-0.3.1/src/terse/fluency/harnesses.py +360 -0
- terse_mcp-0.3.1/src/terse/fluency/pack.py +112 -0
- terse_mcp-0.3.1/src/terse/fluency/questions.py +371 -0
- terse_mcp-0.3.1/src/terse/fluency/scoring.py +97 -0
- terse_mcp-0.3.1/src/terse/history.py +81 -0
- terse_mcp-0.3.1/src/terse/html_report.py +483 -0
- terse_mcp-0.3.1/src/terse/install_mcp.py +518 -0
- terse_mcp-0.3.1/src/terse/lossy.py +291 -0
- terse_mcp-0.3.1/src/terse/measure.py +125 -0
- terse_mcp-0.3.1/src/terse/multiproxy.py +1127 -0
- terse_mcp-0.3.1/src/terse/policy.py +385 -0
- terse_mcp-0.3.1/src/terse/policy_gen.py +296 -0
- terse_mcp-0.3.1/src/terse/probes.py +249 -0
- terse_mcp-0.3.1/src/terse/proxy.py +1037 -0
- terse_mcp-0.3.1/src/terse/report.py +1018 -0
- terse_mcp-0.3.1/src/terse/stats.py +305 -0
- terse_mcp-0.3.1/src/terse/terminal_report.py +253 -0
- terse_mcp-0.3.1/src/terse/text_diff.py +165 -0
- terse_mcp-0.3.1/src/terse/tokenize.py +59 -0
- terse_mcp-0.3.1/src/terse/transforms.py +625 -0
- terse_mcp-0.3.1/src/terse/transport.py +412 -0
- terse_mcp-0.3.1/tests/conftest.py +14 -0
- terse_mcp-0.3.1/tests/fake_mcp_server.py +71 -0
- terse_mcp-0.3.1/tests/test_capture.py +72 -0
- terse_mcp-0.3.1/tests/test_cli.py +665 -0
- terse_mcp-0.3.1/tests/test_codec_hotpath.py +141 -0
- terse_mcp-0.3.1/tests/test_diff.py +101 -0
- terse_mcp-0.3.1/tests/test_diff_soak.py +267 -0
- terse_mcp-0.3.1/tests/test_drop.py +165 -0
- terse_mcp-0.3.1/tests/test_dropeval.py +264 -0
- terse_mcp-0.3.1/tests/test_fluency.py +646 -0
- terse_mcp-0.3.1/tests/test_fuzz.py +142 -0
- terse_mcp-0.3.1/tests/test_history.py +82 -0
- terse_mcp-0.3.1/tests/test_html_report.py +113 -0
- terse_mcp-0.3.1/tests/test_install_mcp.py +789 -0
- terse_mcp-0.3.1/tests/test_lossy.py +101 -0
- terse_mcp-0.3.1/tests/test_measure.py +137 -0
- terse_mcp-0.3.1/tests/test_multiproxy.py +1100 -0
- terse_mcp-0.3.1/tests/test_policy.py +331 -0
- terse_mcp-0.3.1/tests/test_policy_gen.py +200 -0
- terse_mcp-0.3.1/tests/test_probes.py +203 -0
- terse_mcp-0.3.1/tests/test_proxy.py +1202 -0
- terse_mcp-0.3.1/tests/test_report.py +70 -0
- terse_mcp-0.3.1/tests/test_roundtrip.py +164 -0
- terse_mcp-0.3.1/tests/test_secure_io.py +186 -0
- terse_mcp-0.3.1/tests/test_stats.py +264 -0
- terse_mcp-0.3.1/tests/test_terminal_report.py +198 -0
- terse_mcp-0.3.1/tests/test_text_diff.py +106 -0
- terse_mcp-0.3.1/tests/test_transport.py +414 -0
- terse_mcp-0.3.1/tests/test_verify.py +90 -0
- terse_mcp-0.3.1/uv.lock +673 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
# Fires when a version tag is pushed (e.g. `v0.1.0`). Builds the sdist+wheel, cuts a
|
|
3
|
+
# GitHub Release, and publishes to PyPI via Trusted Publishing (OIDC — no stored token).
|
|
4
|
+
# The tag IS the version (hatch-vcs) — a build here resolves to the tag, byte-for-byte.
|
|
5
|
+
#
|
|
6
|
+
# The distribution name is `terse-mcp` (the bare `terse` is taken on PyPI); it normalizes
|
|
7
|
+
# to `terse_mcp` in wheel filenames (e.g. v0.3.0 -> terse_mcp-0.3.0-*.whl).
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags:
|
|
11
|
+
- "v*"
|
|
12
|
+
|
|
13
|
+
permissions: {} # least privilege — each job opts into exactly what it needs
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
release:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
contents: write # create the GitHub Release + upload artifacts; nothing more
|
|
20
|
+
steps:
|
|
21
|
+
# fetch-depth: 0 is REQUIRED — hatch-vcs derives the version from `git describe`,
|
|
22
|
+
# which needs full history + the tag. A shallow checkout yields a wrong/dev version.
|
|
23
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
- uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
|
|
27
|
+
with:
|
|
28
|
+
enable-cache: true
|
|
29
|
+
- name: Build sdist + wheel
|
|
30
|
+
run: uv build
|
|
31
|
+
- name: Sanity-check the built version matches the tag
|
|
32
|
+
# Fail loudly if hatch-vcs and the tag disagree — the artifact name must contain
|
|
33
|
+
# the tag without its leading `v`.
|
|
34
|
+
run: |
|
|
35
|
+
expected="${GITHUB_REF_NAME#v}"
|
|
36
|
+
if ! ls dist/terse_mcp-"${expected}"-*.whl >/dev/null 2>&1; then
|
|
37
|
+
echo "::error::built wheel does not match tag ${GITHUB_REF_NAME} (expected version ${expected})"; ls -1 dist/; exit 1
|
|
38
|
+
fi
|
|
39
|
+
echo "built version ${expected} matches tag ${GITHUB_REF_NAME}"
|
|
40
|
+
# Build ONCE — the same artifacts go to both the GitHub Release and PyPI.
|
|
41
|
+
- name: Upload dist for the publish job
|
|
42
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
43
|
+
with:
|
|
44
|
+
name: dist
|
|
45
|
+
path: dist/
|
|
46
|
+
if-no-files-found: error
|
|
47
|
+
- name: Publish GitHub Release
|
|
48
|
+
env:
|
|
49
|
+
GH_TOKEN: ${{ github.token }}
|
|
50
|
+
run: gh release create "${GITHUB_REF_NAME}" dist/* --generate-notes --title "${GITHUB_REF_NAME}"
|
|
51
|
+
|
|
52
|
+
pypi-publish:
|
|
53
|
+
needs: release
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
# `pypi` MUST match the Trusted Publisher's environment on PyPI, or the OIDC exchange is
|
|
56
|
+
# rejected. Gate it with a required reviewer in Settings -> Environments for human approval.
|
|
57
|
+
environment:
|
|
58
|
+
name: pypi
|
|
59
|
+
url: https://pypi.org/project/terse-mcp/
|
|
60
|
+
permissions:
|
|
61
|
+
id-token: write # OIDC is the ONLY credential — no PyPI token is stored anywhere
|
|
62
|
+
steps:
|
|
63
|
+
- name: Download the built dist
|
|
64
|
+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
65
|
+
with:
|
|
66
|
+
name: dist
|
|
67
|
+
path: dist/
|
|
68
|
+
# Trusted Publishing is automatic (no `with:` creds); PEP 740 attestations default on;
|
|
69
|
+
# uploads everything in dist/ (packages-dir defaults to dist/).
|
|
70
|
+
- name: Publish to PyPI (Trusted Publishing + attestations)
|
|
71
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read # least privilege — the workflow only reads the repo
|
|
8
|
+
jobs:
|
|
9
|
+
lint:
|
|
10
|
+
# Deterministic and version-independent — its own job so a lint failure reports as
|
|
11
|
+
# one named check, not 3 identical red X's under the pytest matrix.
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0 # hatch-vcs derives the version from git history + tags
|
|
17
|
+
- uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: true
|
|
20
|
+
- run: uv sync --dev # no anthropic extra: keeps CI keyless, matches design
|
|
21
|
+
- run: uv run ruff check .
|
|
22
|
+
- run: uv run mypy # checked annotations (audit fix #1); config in pyproject
|
|
23
|
+
pytest:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
strategy:
|
|
26
|
+
fail-fast: false
|
|
27
|
+
matrix:
|
|
28
|
+
# Floor (the requires-python promise) through current stable. Test both ends so
|
|
29
|
+
# a ">=3.11" claim is actually verified, not just the dev default. 3.14 is left
|
|
30
|
+
# off until tiktoken ships wheels for it (else CI compiles the Rust ext + fails).
|
|
31
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
32
|
+
env:
|
|
33
|
+
UV_PYTHON: ${{ matrix.python-version }}
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
36
|
+
with:
|
|
37
|
+
fetch-depth: 0 # hatch-vcs derives the version from git history + tags
|
|
38
|
+
- uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
|
|
39
|
+
with:
|
|
40
|
+
enable-cache: true
|
|
41
|
+
- run: uv sync --dev # no anthropic extra: keeps CI keyless, matches design
|
|
42
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.venv/
|
|
5
|
+
*.egg-info/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
|
|
8
|
+
# uv
|
|
9
|
+
# uv.lock IS committed (pinned deps) — do not ignore it.
|
|
10
|
+
|
|
11
|
+
# Captured corpus payloads may contain tool output; keep the dir, ignore contents.
|
|
12
|
+
corpus/*
|
|
13
|
+
!corpus/.gitkeep
|
|
14
|
+
|
|
15
|
+
# Synthetic stress corpus for the fluency eval — derived; regenerate with
|
|
16
|
+
# `python scripts/gen_stress_corpus.py`.
|
|
17
|
+
corpus-stress/
|
|
18
|
+
|
|
19
|
+
# Ad-hoc eval corpora for cross-call diffing (synthetic pair + real gh captures) — derived.
|
|
20
|
+
corpus-diff/
|
|
21
|
+
corpus-real/
|
|
22
|
+
|
|
23
|
+
# Spike reports
|
|
24
|
+
reports/
|
|
25
|
+
|
|
26
|
+
# runecho stray index dirs (created if `runecho-ir` is run with cwd here)
|
|
27
|
+
.ai/
|
|
28
|
+
|
|
29
|
+
# Ad-hoc experiment harnesses (e.g. the fidelity probe) — not part of the lib
|
|
30
|
+
scratch_fidelity/
|
|
31
|
+
.codegraph
|
|
32
|
+
.coverage
|
|
33
|
+
|
|
34
|
+
# hatch-vcs build-time version file (generated from the git tag; never committed)
|
|
35
|
+
src/terse/_version.py
|
|
36
|
+
|
|
37
|
+
# Benchmark: pinned TOON encoder deps (regenerate with `cd scripts/bench && npm install`)
|
|
38
|
+
scripts/bench/node_modules/
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Symbols the guard mis-flags as unresolved. The guard parses `from X import Y`
|
|
2
|
+
# only within a staged diff hunk, so a new line using a stdlib symbol whose import
|
|
3
|
+
# line isn't also in the hunk gets a false positive. A genuinely missing import of
|
|
4
|
+
# these is caught immediately by pytest/import, so suppressing them here is safe.
|
|
5
|
+
Path
|
|
6
|
+
OrderedDict
|
|
7
|
+
Lock
|
|
8
|
+
Counter
|
|
9
|
+
Thread
|
|
10
|
+
field
|
|
11
|
+
dataclass
|
|
12
|
+
# Also mis-flagged: a callback PARAMETER invoked as a bare call (`drop_sink(h, v)` in
|
|
13
|
+
# policy.apply). The guard resolves module symbols/imports, not function parameters, so a
|
|
14
|
+
# param called by name reads as unresolved. A genuinely wrong name is caught by pytest.
|
|
15
|
+
drop_sink
|
|
16
|
+
answerer
|
|
17
|
+
rule_for
|
|
18
|
+
# cli.py's _build_answerers(args, make_openai, make_anthropic, ...) — the two answerer-
|
|
19
|
+
# factory PARAMETERS, called bare (make_openai(base, key, m) / make_anthropic(model)).
|
|
20
|
+
# Same false-positive class as drop_sink/answerer/rule_for above.
|
|
21
|
+
make_openai
|
|
22
|
+
make_anthropic
|
|
23
|
+
# fluency.py's _aggregate_by_model(pairs, answerers, trials, payload_fn) — the eval
|
|
24
|
+
# PARAMETER (run_diff_payload/run_text_diff_payload), called bare as payload_fn(a, b,
|
|
25
|
+
# fn, tool, trials=trials). Same false-positive class as drop_sink/answerer above.
|
|
26
|
+
payload_fn
|
|
27
|
+
# Also mis-flagged: a local variable holding a function REFERENCE, called bare in a
|
|
28
|
+
# monkeypatch wrapper (`real = mod.fn; def wrapper(*a): return real(*a)`), same
|
|
29
|
+
# root cause as the callback-parameter case above — the guard resolves module
|
|
30
|
+
# symbols/imports, not locals. Confirmed correct: pytest passes with these wrappers.
|
|
31
|
+
real_apply
|
|
32
|
+
real
|
|
33
|
+
orig_send
|
|
34
|
+
# test_run_text_diff_payload_computes_wire_exactly_once's `original = fluency.text_diff.
|
|
35
|
+
# text_diff_wire`, called bare inside its counting_wire wrapper. Same class as real/
|
|
36
|
+
# orig_send above.
|
|
37
|
+
original
|
|
38
|
+
# Also mis-flagged: plain English words inside a docstring that happen to look like
|
|
39
|
+
# `word(...)` bare calls to the guard's pattern match (e.g. "the client's stdin",
|
|
40
|
+
# "server->client", "closes the transport in turn"). Not code at all.
|
|
41
|
+
client
|
|
42
|
+
wire
|
|
43
|
+
cleanly
|
|
44
|
+
server
|
|
45
|
+
args
|
|
46
|
+
keys
|
|
47
|
+
measurement
|
|
48
|
+
one
|
|
49
|
+
unconditionally
|
|
50
|
+
flight
|
|
51
|
+
rows
|
|
52
|
+
inner
|
|
53
|
+
built
|
|
54
|
+
wide
|
|
55
|
+
name
|
|
56
|
+
callers
|
|
57
|
+
detail
|
|
58
|
+
scope
|
|
59
|
+
scopes
|
|
60
|
+
implicitly
|
|
61
|
+
# report.py's _build_diff_style_report docstring: "pre-split into lines (not a single
|
|
62
|
+
# string)" — "lines (not" reads as a bare call. Not code.
|
|
63
|
+
lines
|
|
64
|
+
# fluency.py's run_text_diff_payload docstring: "computes the diff wire exactly ONCE
|
|
65
|
+
# (unlike calling gen_text_diff_questions..." — "ONCE (unlike" reads as a bare call.
|
|
66
|
+
ONCE
|
|
67
|
+
# Also mis-flagged: importlib.metadata.version, imported locally inside
|
|
68
|
+
# _attestation_card (html_report.py) the same way report.py's build_verify_header
|
|
69
|
+
# already does — same hunk-splitting false positive as the stdlib symbols above.
|
|
70
|
+
# Confirmed resolving correctly: `terse verify --html` prints the real version.
|
|
71
|
+
version
|
|
72
|
+
# probes.py / cli.py #64 cross-server probe: hunk-split import + same-file false
|
|
73
|
+
# positives — the guard parses `from X import Y` only within the staged hunk and does not
|
|
74
|
+
# resolve same-file top-level defs (e.g. value_redundancy called inside cross_server_*).
|
|
75
|
+
# All confirmed defined/imported; a genuinely missing one is caught by pytest/import.
|
|
76
|
+
combinations
|
|
77
|
+
extract_records
|
|
78
|
+
encode_cl100k
|
|
79
|
+
value_redundancy
|
|
80
|
+
DEFAULT_PROBE_REPORT
|
|
81
|
+
# probes.py cross_call_overlap docstring: "When `idf` is supplied (from `token_idf`)" —
|
|
82
|
+
# "supplied (from" reads as a bare call to the guard's pattern match. Not code.
|
|
83
|
+
supplied
|
|
84
|
+
# proxy.py _emit_audit: `audit = self.audit` narrowed local (mypy baseline, audit
|
|
85
|
+
# fix #1), called bare — same local-variable false-positive class as real/orig_send.
|
|
86
|
+
audit
|
|
87
|
+
# test_codec_hotpath.py's counting wrapper: `real_minify = transforms.minify`, called
|
|
88
|
+
# bare inside the monkeypatch lambda. Same local-variable class as real/orig_send.
|
|
89
|
+
real_minify
|
|
90
|
+
# proxy.py run_proxy docstring (stats ledger): "record per result (sizes + decision"
|
|
91
|
+
# and "ON by default (cli.py resolves" — English words reading as bare calls. Not code.
|
|
92
|
+
result
|
|
93
|
+
default
|
|
94
|
+
# test_stats.py locals holding build_stats_writer's returned closure, called bare
|
|
95
|
+
# (writer(...) / quiet(...) / loud(...)). Same local-variable class as real/orig_send.
|
|
96
|
+
writer
|
|
97
|
+
quiet
|
|
98
|
+
loud
|
|
99
|
+
# policy.py select / proxy.py run_proxy docstrings (#83): English prose the guard's
|
|
100
|
+
# pattern match reads as bare calls — "when `server` is known (#83)", "self-prefix
|
|
101
|
+
# their own tool names (kb names ...)", "a server-scoped rule (`runecho.*`)", "the
|
|
102
|
+
# command basename (kb behind ...)". Not code.
|
|
103
|
+
known
|
|
104
|
+
names
|
|
105
|
+
rule
|
|
106
|
+
basename
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# terse — Benchmarks
|
|
2
|
+
|
|
3
|
+
**Last updated: 2026-07-17.** All figures below were produced on that date and are
|
|
4
|
+
**reproducible end-to-end** with the commands shown — nothing here is hand-typed or
|
|
5
|
+
estimated. If you re-run and get different numbers, the code changed; open an issue.
|
|
6
|
+
|
|
7
|
+
## What is being measured
|
|
8
|
+
|
|
9
|
+
- **Token reduction** = how many fewer tokens the compressed form costs vs the raw JSON,
|
|
10
|
+
counted in **`cl100k_base`** (the tiktoken vocabulary terse uses). Higher is better.
|
|
11
|
+
A payload that is *already* compact (no pretty-print whitespace) makes every number here
|
|
12
|
+
a *pure structural* gain — the hardest honest case.
|
|
13
|
+
- **Lossless** = `decompress(compress(x)) == x` exactly. Every terse row below is verified
|
|
14
|
+
lossless per payload; a payload is dropped from a total if either tool fails its round-trip.
|
|
15
|
+
- "raw", "terse", "TOON" columns are % fewer tokens than the raw JSON.
|
|
16
|
+
|
|
17
|
+
## Reproduce everything
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv sync
|
|
21
|
+
cd scripts/bench && npm install # pins the official @toon-format/toon encoder
|
|
22
|
+
cd -
|
|
23
|
+
uv run scripts/bench/benchmark.py # §1 terse vs TOON on real GitHub API payloads
|
|
24
|
+
uv run scripts/bench/width_sweep.py # §2 the column-width sweep
|
|
25
|
+
uv run scripts/bench/diff_demo.py # §3 cross-call diff (terse's own axis)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## §1 — terse vs TOON on real, public GitHub API payloads
|
|
31
|
+
|
|
32
|
+
The corpus is real GitHub API output (`scripts/bench/corpus/`) — the nested, record-shaped
|
|
33
|
+
tool traffic terse targets. `cl100k` tokens, all lossless.
|
|
34
|
+
|
|
35
|
+
| payload | records | raw tok | **terse** | TOON |
|
|
36
|
+
|---|--:|--:|--:|--:|
|
|
37
|
+
| gh_pulls | 30 | 151,165 | **76.1%** | −8.4% |
|
|
38
|
+
| gh_workflow_runs | 20 | 76,032 | **80.3%** | −7.5% |
|
|
39
|
+
| gh_issues | 30 | 48,032 | **32.7%** | −8.0% |
|
|
40
|
+
| gh_commits | 30 | 69,652 | **26.5%** | −4.5% |
|
|
41
|
+
| gh_dir_listing | 24 | 6,736 | **31.4%** | −7.7% |
|
|
42
|
+
| gh_rate_limit | 1 obj | 357 | **13.4%** | −36.7% |
|
|
43
|
+
| gh_repo_single | 1 obj | 1,652 | 0.0% | −4.4% |
|
|
44
|
+
| gh_commits_flat | 30 | 10,886 | **2.4%** | 1.7% |
|
|
45
|
+
| gh_labels | 9 | 632 | 15.2% | **19.0%** |
|
|
46
|
+
| **weighted total** | | **365,144** | **58.3%** | **−7.1%** |
|
|
47
|
+
|
|
48
|
+
**Plain reading:** on real nested records terse cuts tokens **58%**; TOON *regresses* to −7%
|
|
49
|
+
(worse than raw) because it adds a key-path per nesting level, while terse folds the repeated
|
|
50
|
+
subtrees and long repeated strings (e.g. `gh_pulls` = 60 copies of the same repo object
|
|
51
|
+
collapsed to one legend entry → 76%). TOON wins only on `gh_labels` — a flat, short-valued
|
|
52
|
+
uniform table, its designed sweet spot.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## §2 — Column-width sweep: is there a "narrow vs wide" crossover? (No.)
|
|
57
|
+
|
|
58
|
+
A natural hypothesis is that TOON overtakes terse once records get *wide* (many columns),
|
|
59
|
+
because TOON writes the header once per table. We tested it directly: **40 rows held fixed,
|
|
60
|
+
column count swept 2→12**, seeded, each row verified lossless for both tools.
|
|
61
|
+
|
|
62
|
+
| columns | terse% | TOON% | winner |
|
|
63
|
+
|--:|--:|--:|--:|
|
|
64
|
+
| 2 | 40.4 | 44.0 | TOON +3.6 |
|
|
65
|
+
| 3 | 52.1 | 48.7 | terse +3.4 |
|
|
66
|
+
| 4 | 46.8 | 48.8 | TOON +2.0 |
|
|
67
|
+
| 5 | 52.6 | 50.7 | terse +1.9 |
|
|
68
|
+
| 6 | 48.9 | 50.5 | TOON +1.6 |
|
|
69
|
+
| 7 | 52.7 | 51.5 | terse +1.2 |
|
|
70
|
+
| 8 | 50.0 | 51.2 | TOON +1.2 |
|
|
71
|
+
| 9 | 52.9 | 52.1 | terse +0.8 |
|
|
72
|
+
| 10 | 50.6 | 51.7 | TOON +1.1 |
|
|
73
|
+
| 11 | 52.9 | 52.3 | terse +0.6 |
|
|
74
|
+
| 12 | 51.1 | 52.1 | TOON +1.0 |
|
|
75
|
+
|
|
76
|
+
**Plain reading:** there is **no clean column-count crossover.** The winner oscillates by
|
|
77
|
+
parity, the margins are ~1–4 points, and they **converge toward a tie** as width grows — the
|
|
78
|
+
opposite of "TOON pulls decisively ahead when wide." (An earlier draft of this repo's README
|
|
79
|
+
claimed a ≤3/≥4-column boundary from a single synthetic construction; a seeded sweep does not
|
|
80
|
+
reproduce it, and the claim was corrected.)
|
|
81
|
+
|
|
82
|
+
**The real dividing axis is value repetition, not width.** On these stripped-flat synthetic
|
|
83
|
+
tables — no nesting, no long repeated strings — terse's dictionary/subtree tiers have little
|
|
84
|
+
to fold, so the two tools tie. terse's decisive §1 win comes precisely from the redundancy
|
|
85
|
+
that real records have and synthetic flat tables don't.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## §3 — Cross-call diff (an axis no stateless encoder has)
|
|
90
|
+
|
|
91
|
+
When the same tool is called again (poll a list, re-read a file), terse can emit a lossless
|
|
92
|
+
*delta* against the prior result instead of the whole payload. TOON, minify, and terse's own
|
|
93
|
+
single-shot codec all pay the full column every call. Modeling one repeat call per payload
|
|
94
|
+
(`diff_demo.py`), the **second** call costs:
|
|
95
|
+
|
|
96
|
+
| repeated call | full re-send | diff | smaller by |
|
|
97
|
+
|---|--:|--:|--:|
|
|
98
|
+
| gh_commits_flat | 10,681 | 812 | **92.4%** |
|
|
99
|
+
| gh_issues | 32,608 | 4,448 | **86.4%** |
|
|
100
|
+
| gh_pulls | 37,776 | 15,292 | **59.5%** |
|
|
101
|
+
| **weighted total** | 152,837 | 40,138 | **73.7%** |
|
|
102
|
+
|
|
103
|
+
**Honest caveat (read this):** these are *modeled* repeat-call savings. How *often* the
|
|
104
|
+
pattern occurs in a real agent loop is workload-dependent and is being measured directly (the
|
|
105
|
+
proxy now records a per-result `diff_reason` — run `terse stats` to see the breakdown for your
|
|
106
|
+
own traffic). Do **not** read §3 as a claim about aggregate real-world savings; read §1 for that.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## §4 — Competitor landscape (hands-on, tested 2026-07-17)
|
|
111
|
+
|
|
112
|
+
Installed and tested, not cited from marketing. Only TOON (§1) is directly comparable on a
|
|
113
|
+
lossless token axis; the rest measure *different guarantees*, so no head-to-head % is claimed.
|
|
114
|
+
|
|
115
|
+
| Tool | What it is (verified) | Comparable? |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| **headroom** (`headroom-ai`, v0.32.0) | JSON compressor is a **deterministic Rust transform, not ML**: lossless on uniform arrays, but **drops rows** on larger/irregular sets, recoverable only via a `retrieve` round-trip against a **time-boxed cache** (default 30-min TTL). Measured on our corpus: 33.1% (lossless reformat) to 42.5%/64.1% (lossy, dropping 13/30 and 13/20 rows). A separate optional text compressor *is* ML. | No — its larger numbers come from *dropping data* with time-limited recovery; terse's are unconditionally lossless. |
|
|
118
|
+
| **LLMLingua-2** (Microsoft) | Lossy prompt token-classifier. Fed JSON it strips syntax (`{`,`}`,`:`,`"`) as low-information and emits **invalid, unparseable JSON**; truncates past 512 tokens. ~50% on both prose and JSON. | No — different axis (prompts, not tool output), lossy, corrupts structure. |
|
|
119
|
+
| **Atlassian mcp-compressor** | Primarily lossless schema/description compression at connect time — **complementary and stackable** with terse (`terse proxy -- mcp-compressor -- <server>`). An opt-in `--toonify` flag also reformats results into TOON (off by default; no diffing/policy/state). | Adjacent, not competing. |
|
|
120
|
+
| **Anthropic / OpenAI context editing** | Native, server-side, **lossy** history-pruning; no local artifact to run keylessly. | Different mechanism (drops old results server-side). |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## §5 — Generate your own real-session evidence
|
|
125
|
+
|
|
126
|
+
The benchmarks above are a fixed corpus. terse also keeps an always-on, **payload-free**
|
|
127
|
+
savings ledger from your real sessions (sizes + decisions only, never content), so you can
|
|
128
|
+
measure what terse saved *you*, not a synthetic corpus:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
terse stats # rollup: results, decisions, tokens saved, per-tool rows
|
|
132
|
+
terse stats --since 7d # windowed
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This is terse's "evidence over stars" path: your own numbers, on your own traffic, verifiable
|
|
136
|
+
without trusting this file.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Methodology & honesty notes
|
|
141
|
+
|
|
142
|
+
- Tokenizer is `cl100k_base`; absolute % shift under a different vocabulary but the ranking is
|
|
143
|
+
stable (terse's cross-tokenizer-invariance claim, tested separately in the suite).
|
|
144
|
+
- §1 corpus is real, public GitHub API output. §2 is **synthetic and seeded** — illustrative
|
|
145
|
+
of a mechanism, not production-representative; the exact numbers depend on the construction
|
|
146
|
+
(short keys, value cardinality), which is why §2's takeaway is "no crossover," not a constant.
|
|
147
|
+
- Every terse figure is verified lossless per payload. §4's headroom row is the only place a
|
|
148
|
+
"reduction %" is reported for a tool that achieves it by *discarding* data — flagged inline.
|
|
149
|
+
- Adoption honesty: terse is new (pre-PyPI as of this date); TOON and headroom are far more
|
|
150
|
+
established. terse's wedge is narrow and specific — *unconditionally lossless, no ML, no
|
|
151
|
+
egress* — not breadth of adoption.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to terse are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
Releases are cut from git tags (`vX.Y.Z`, via hatch-vcs) — an entry moves from
|
|
8
|
+
`[Unreleased]` to a versioned section when its tag is pushed.
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
## [0.3.1] - 2026-07-18
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Automated PyPI publishing via GitHub Actions **Trusted Publishing** (OIDC) with
|
|
16
|
+
PEP 740 provenance attestations — tagged releases upload to PyPI with no stored
|
|
17
|
+
token. This is the first PyPI release of `terse-mcp`. (#111)
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- `release` workflow: corrected the built-wheel name check for the `terse-mcp`
|
|
21
|
+
rename (`terse_mcp-*.whl`), which had blocked the tagged release run. (#110)
|
|
22
|
+
|
|
23
|
+
## [0.3.0] - 2026-07-18
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **Installable package** — MIT license and PyPI-ready metadata. The distribution is
|
|
27
|
+
named `terse-mcp` (the bare `terse` is taken on PyPI); the import package stays
|
|
28
|
+
`terse`, so `python -m terse` is unchanged. (#103)
|
|
29
|
+
- `verify --json`: emit the lossless-gate verdict and cl100k savings totals as JSON
|
|
30
|
+
on stdout instead of the markdown report — CI-checkable (`… | jq -e
|
|
31
|
+
.lossless_gate.ok`), parity with `stats --json` / `mcp-status --json`. (#107)
|
|
32
|
+
- diff-moat instrumentation: `stats` records why the cross-call diff tier did or did
|
|
33
|
+
not fire per call, to measure the diff feature's real-world reach (Phase 0+1). (#101)
|
|
34
|
+
- Property-based fuzzing of the lossless round-trip guarantee. (#105)
|
|
35
|
+
|
|
36
|
+
### Documentation
|
|
37
|
+
- Onboarding quickstart + per-client install recipes. (#108)
|
|
38
|
+
- Codeshot architecture diagram embedded in TECHNICAL.md. (#104)
|
|
39
|
+
|
|
40
|
+
## [0.2.0] - 2026-07-17
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- `mcp-status`: each wrapped server now shows what it actually fronts
|
|
44
|
+
(`wraps=<downstream cmd/url>`), whether the cross-call diff tier is
|
|
45
|
+
on/off/default, and whether the stats ledger is on; a policy file that has gone
|
|
46
|
+
missing since install is flagged `(MISSING)`; new `--json` output for
|
|
47
|
+
scripts/CI. (#97)
|
|
48
|
+
- `tune`: each drop-candidate bucket ends with a savings rollup — estimated gross
|
|
49
|
+
tokens the whole bucket would evict and its share of the corpus. (#98)
|
|
50
|
+
- `fluency --html`: writes the forest-plot comprehension-gap report next to
|
|
51
|
+
`--out` for the paired diff-family evals (`--diff`, `--diff-soak`,
|
|
52
|
+
`--text-diff-eval`); same inline-SVG/no-JS/no-CDN form as `measure --html`. (#99)
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
- `stats`: the per-tool table falls back to character columns when tiktoken token
|
|
56
|
+
counts are absent (was rendering as all-zeros); an empty `--since` window now
|
|
57
|
+
reports the window rather than "nothing ever recorded"; added a per-tool
|
|
58
|
+
cross-call diff hit-rate (`diff%`) column. (#96)
|
terse_mcp-0.3.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eric Minish
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|