Skip to main content

Git LFS for LLM context: a versioned, loss-aware context compression MCP server.

Project description

🗜️ SaveContext

Git LFS for your LLM context window.

Keep big documents outside the model. Hand it a small searchable handle instead — so it reads a cheap summary to find what it needs, then pulls back the exact original words to quote, using a fraction of the tokens.

License: Apache 2.0 Python 3.10+ MCP server tests status


  ingest 150k-token contract ─▶ ctx://acme@v1
                                 ├─ ~700-token semantic brief
                                 ├─ block map  (b0000 … b0042)
                                 ├─ protected atoms  (money · dates · obligations …)
                                 └─ raw source stored compressed, never auto-sent

  ask 6 questions ─▶ lookup(...)  ─▶  one call · top blocks · exact atoms
                                       · verbatim quotes · confidence flag

LLM workflows waste tokens re-reading the same long documents. SaveContext loads the source once, returns a tiny handle plus a compact brief, and lets the model pull only the spans it needs — while guaranteeing the meaning-critical facts (dollar amounts, dates, obligations, identifiers) come back verbatim, with exact source offsets. It's not gzip-style byte compression; it's token reduction.

Quick start — one command, zero install

With uv installed, add SaveContext to Claude Code in a single line. No clone, no pip, no virtualenv — uvx fetches and runs it in an ephemeral, auto-updating environment:

# straight from GitHub (no PyPI needed):
claude mcp add savecontext -- uvx --from git+https://github.com/AlexanderBoger/SaveContext savecontext

# once published to PyPI:
claude mcp add savecontext -- uvx savecontext

Open Claude Code, run /mcp, and you'll see the 11 savecontext tools. Point the model at a big document and just ask — it ingests, looks up, and quotes on its own. To pin the database location, add -e SAVECONTEXT_DB=/abs/path.db before the --.

Why it's different

Most context tools are lossy summarizers — helpful, but they blur the exact numbers and can't tell you when they're guessing. SaveContext does the two things they can't:

  • 🎯 Exact, with receipts. Facts come back as the original characters with their source location, never a paraphrase. A summary is for finding; the quote is for trusting. Every span reconstructs byte-for-byte.
  • 🙅 Says "not found" instead of inventing. When the answer isn't in the document, retrieval is flagged weak and the agent abstains. In testing, agents returned not found for a missing database engine and on-call name rather than hallucinating plausible ones.
  • 📉 Flat cost. The payload to answer questions stays ~7,300 tokens whether the document is 15k or 150k — pasting scales linearly, this doesn't.
  • 🔁 One call, many questions. lookup answers a batch of queries in a single round trip, not one brief/expand/quote cycle per question.

By the numbers

20.5×

less context
at 150k tokens

~7.3k

flat token cost,
any document size

54/54

live answers correct,
incl. not found

100%

byte-exact quotes,
0 hallucinations
Bar chart: context tokens to answer six questions. Pasting the whole document scales with size (15,135 to 150,135 tokens) while SaveContext stays flat near 7,300 — 20.5x less at 150k.

Measured on a contract set plus held-out documents (an RFC and a synthetic incident log) the retriever was never tuned on — real agents answering fact questions with distractor values planted next to the true ones, graded against ground truth. Crossover is ≈ 5k tokens: below that, just paste; above it, SaveContext wins and the gap widens with size.

Reproduce: examples/benchmark_suite/eval_paraphrase.py (phrasing robustness) and eval_holdout.py (RFC + logs, abstention grading).

Honest note: live runs are single samples — treat small differences as noise. The durable findings are the flat-vs-scaling cost curve, byte-exact retrieval, and zero hallucination on absent facts. Retrieval on dense technical prose is the current frontier (see the roadmap's embedding backend).

Tools

All tools return clean JSON. Handles are stable and readable: ctx://label@vN for contexts, out://label@vN for outputs.

Tool Purpose
ingest Store long text; return handle, brief, block map, protected-atom summary, safety notes. Does not return the raw source. Optional agent_brief lets the calling model supply its own summary.
lookup Batched retrieval — answer several queries in one call. Per query: the top block(s), the exact atoms inside them, a verbatim best-matching sentence, and a confidence flag (strong/weak) so the agent can abstain instead of guessing.
brief Task-specific brief: relevant blocks + atoms for a given task, with uncertainty notes.
expand Lazily expand a block / atom / section / search match at fidelity = summary | facts | quotes | full.
quote Exact verbatim quote (by atom id or search query) with surrounding context and source ref.
map Full (or paginated) block map + atom summary, fetched lazily.
set_brief Replace a context's brief with an agent-authored one (brief_mode='agent'). Raw source and atoms unchanged.
diff Diff new source against a stored context; report added/removed/changed atoms and meaning impact. Creates a new version.
zip_output Store a large generated output; return section map + preview.
expand_output Expand part of a stored output at fidelity = preview | section | full.
audit Compression ratio, protected-atom counts, per-category preservation estimate, safe_for / unsafe_for, warnings.

Fidelity levels (expand): summary (extractive lead sentences, cheapest) · facts (summary plus exact atoms, [money] $500,000) · quotes (verbatim spans you can cite) · full (raw text of the matched block — the only path that returns source prose).

How a turn flows

1. User pastes a 150,000-token contract.
2. Claude → ingest(source_text=…, label="acme")
        ← ctx://acme@v1, ~700-token brief, block map, atoms.
3. User: "Liability cap? Notice period? Governing law?"
4. Claude → lookup(ctx://acme@v1, ["liability cap", "termination notice", "governing law"])
        ← per query: the governing block, its atoms, a verbatim sentence, confidence.
5. Claude → quote(ctx://acme@v1, search_query="shall not exceed")   # verify exact wording
        ← "…total liability shall not exceed $750,000…" + source offsets.
6. Claude answers using only the relevant, verbatim, cited context.

A runnable version is in examples/demo.py (python examples/demo.py).

Configuration

Set via environment (or the MCP env block):

Variable Default Purpose
SAVECONTEXT_DB ~/.savecontext/savecontext.db Store location. :memory: for ephemeral. Shared by the CLI and the MCP server, so anything ingested on one is queryable from the other.
SAVECONTEXT_TRANSPORT stdio stdio | http | sse
SAVECONTEXT_HOST / SAVECONTEXT_PORT FastMCP defaults For http/sse transport
SAVECONTEXT_EMBEDDINGS off 1 enables a local sentence-transformers retrieval backend (default is dependency-free BM25)
SAVECONTEXT_LLM_SUMMARY off 1 has a local Ollama model write an abstractive brief at ingest (fails closed to extractive)

The ctx:// handle scheme is stable. SAVECONTEXT_* variables take precedence; the pre-rename CONTEXTSAVER_* / CONTEXTVAULT_* names are still honored as fallbacks.

CLI

The package ships a CLI (savecontext, short alias savectx) that shares the same store as the MCP server:

savectx ingest report.md --label q4 --type auto     # ingest a file
savectx ingest - --label pasted                      # or read stdin
savectx list                                         # what's in the store
savectx lookup ctx://q4@v1 --query "liability cap" --query "fees"   # batched
savectx brief  ctx://q4@v1 --task "liability risks"
savectx expand ctx://q4@v1 --selector liability --fidelity facts
savectx quote  ctx://q4@v1 --search "shall not exceed"
savectx audit  ctx://q4@v1
savectx diff   ctx://q4@v1 report_v2.md              # version + change report
savectx serve                                        # run the MCP server

Bare savecontext (and python -m savecontext) runs the MCP server, so MCP configs keep working unchanged.

How it works

Layer Module Behaviour
Tokenizer tokenizer.py tiktoken cl100k_base, else chars/4
Chunking chunking.py deterministic: headings + paragraph packing to a token budget, exact offsets
Extraction extraction.py regex atoms: dates, money, %, numbers, URLs, emails, entities, negations, obligations, code ids, file paths
Retrieval retrieval.py BM25 + concept-group expansion + answer-type intents + saturation scoring; optional embedding backend behind the same rank interface
Summary summarize.py extractive: ranked blocks + lead sentences + high-value atom lines; optional local-LLM
Diff diffing.py atom-set diff + coarse block text diff + risk impact
Storage storage.py SQLite metadata + zstd-compressed raw source; blocks/atoms as offsets
Service / Server service.py, server.py the eleven tools; FastMCP over stdio / HTTP / SSE

Loss-aware guarantee. Blocks and atoms are stored as offsets into the raw source, so any span reconstructs byte-for-byte. The brief is explicitly flagged lossy; exact wording always comes from quote() or expand(fidelity="quotes"). The full raw source is only ever returned via expand(fidelity="full").

Who writes the brief?

Three summarizer options, best first — all keep the protected atoms exact, only the prose brief changes:

  1. The calling agent (recommended). Claude already has the document in context at ingest time, so it can write a faithful, dense brief for free — inline via ingest(agent_brief=…) or afterwards via set_brief. (brief_mode='agent')
  2. Local LLM (optional). SAVECONTEXT_LLM_SUMMARY=1 → a local Ollama model writes an abstractive brief; fails closed to extractive. (brief_mode='abstractive(local-llm)')
  3. Extractive (default). Rule-based real sentences from the source — free, offline, deterministic, always available. (brief_mode='extractive')

Development

git clone https://github.com/AlexanderBoger/SaveContext && cd savecontext
python -m pip install -e ".[tokenizer,dev]"
python -m pytest            # 80 tests

Covers tokenizer / handles / chunking / extraction / BM25 retrieval units, an end-to-end pass over all eleven tools (offset exactness, verbatim quotes, versioning, money-change diff, audit preservation buckets, error handling), the batched-lookup retrieval + confidence logic, and the CLI flow.

Roadmap

  • ✅ BM25 + hybrid semantic ranking (concept groups, answer-type intents) — no model required
  • ✅ Batched lookup with per-query confidence / abstention
  • ✅ Verbatim-integrity + extraction-recall reporting in audit
  • ✅ PDF / DOCX / HTML loaders and recursive folder ingest
  • ✅ HTTP / SSE transport in addition to stdio
  • ⬜ Embedding backend benchmarked against the held-out suite (dense-prose retrieval)
  • ⬜ Vector-store persistence so embeddings aren't recomputed per query
  • ⬜ LLM-assisted atom extraction for fuzzy / multi-span facts

License

Apache License 2.0 — see also NOTICE. If you redistribute SaveContext or build on it, retain the copyright and the NOTICE file, per the license (this is how attribution is required).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

savecontext-0.1.0.tar.gz (65.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

savecontext-0.1.0-py3-none-any.whl (57.5 kB view details)

Uploaded Python 3

File details

Details for the file savecontext-0.1.0.tar.gz.

File metadata

  • Download URL: savecontext-0.1.0.tar.gz
  • Upload date:
  • Size: 65.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for savecontext-0.1.0.tar.gz
Algorithm Hash digest
SHA256 937dee0e296ddd0d01940d27115dae7a1596873d5b7da71cee5b6a56f39745c5
MD5 081326127e88c14568ee666071b885b4
BLAKE2b-256 cac0b837cbb592d7a55745dab027fd2a2a0a91f1cbdfafed8ff3c8c318cc83b8

See more details on using hashes here.

File details

Details for the file savecontext-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: savecontext-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 57.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for savecontext-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 86543a38cdd67e5e4eeb79eb67cf4f6a72b7c024bac256214489f5d6dc485d7f
MD5 d55f4eba3e475e37939bcd737b5c873c
BLAKE2b-256 1f4c2c42dbf01a38f8f5d2e0e1f05d62102804d789476c9afea50ff6ad37c9fb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page