Metadata-Version: 2.5
Name: token-save-mcp
Version: 0.2.0
Summary: MCP server that keeps large files out of your coding agent's context by delegating reads to a cheap worker model. Enforced by a hook, measured per call.
Project-URL: Homepage, https://github.com/Habartru/token_save_mcp
Project-URL: Issues, https://github.com/Habartru/token_save_mcp/issues
License: MIT License
        
        Copyright (c) 2026 Habartru
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: ai-coding,claude,claude-code,context-window,cursor,developer-tools,llm,mcp,model-context-protocol,ollama,openrouter,token-optimization
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: mcp>=1.2
Requires-Dist: openai>=1.60
Requires-Dist: socksio>=1.0
Description-Content-Type: text/markdown

# token-save-mcp

**Your coding agent burns its context reading files. This stops it — and shows you the receipt.**

[![CI](https://github.com/Habartru/token_save_mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Habartru/token_save_mcp/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/token-save-mcp.svg)](https://pypi.org/project/token-save-mcp/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Habartru/token_save_mcp/blob/main/LICENSE)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)

An MCP server that sends big files to a cheap worker model and returns only the
answer. The file bytes are paid for once, in the worker's context — not
permanently in your agent's.

![token-save-mcp in action](https://raw.githubusercontent.com/Habartru/token_save_mcp/main/docs/demo.gif)

The hook **blocks** the expensive read and redirects it. The answer comes back
with the worker's **real token usage from the API response** — not an estimate,
a receipt:

```
─────────────────────────────────────────────────────────────
token-save: 1 file, 606 lines | direct read ≈7,042 tok →
into context ≈234 tok (saved 6,808 · 97%)
worker: glm-5.3-flash | 6,155 in / 278 out | 4.0s
```

---

## Install

Two commands. Nothing else to install, nothing to configure by hand.

```bash
pip install token-save-mcp
token-save-mcp init --hook
```

`init` finds a provider key you already have, registers the MCP server with
your agent, and installs the hook. If you have no key yet it prints the
options and where to get one.

That is the whole setup. There is no second package, no separate MCP server to
add, and no external tool to install — the hook is plain Python and ships in
the package.

<details>
<summary><b>What if I have no API key?</b></summary>

`init` will show you this:

```
  This tool sends files to a worker model of YOUR choosing.
  Nothing is connected automatically and no key ships with it.

  openrouter  one key, hundreds of models     export OPENROUTER_API_KEY=...
  deepseek    cheap and strong on code        export DEEPSEEK_API_KEY=...
  groq        fastest responses               export GROQ_API_KEY=...
  ollama      Ollama Cloud subscription       export OLLAMA_API_KEY=...
  local       your own machine — no key       nothing to set
```

Pick one, export the key, run `init` again. The key is read from your
environment and stored in your agent's MCP config — you never paste it into a
file yourself.

**No key at all?** `--provider local` runs against a model on your own machine
(Ollama on `localhost:11434`). Nothing leaves the computer.

**Browser login instead of a key?** Not supported. Tools like Kimi Code and
GitHub Copilot authenticate through a browser and expose no OpenAI-compatible
endpoint, so they cannot be used as the worker. Every provider listed above
uses a plain API key.

</details>

<details>
<summary><b>Any OpenAI-compatible endpoint</b></summary>

The presets above are conveniences. Anything that speaks the OpenAI API works:

```bash
export TOKENSAVE_BASE_URL=https://your-endpoint/v1
export TOKENSAVE_API_KEY=...
export TOKENSAVE_MODEL=your-model-id
token-save-mcp init --provider openrouter   # provider is ignored when BASE_URL is set
```

</details>

Verify anytime with `token-save-mcp doctor` — it checks the configuration and
makes one live call to prove the worker answers:

```
✓ provider: openrouter -> https://openrouter.ai/api/v1
✓ worker model: deepseek/deepseek-chat
✓ hook script present (no external tools required)
✓ MCP server registered and connected
✓ worker replied in 1.8s (21 in / 13 out)
```

### Requirements

- Python 3.10+
- An agent that speaks MCP (Claude Code, Cursor, Cline, Windsurf, Codex)
- An API key from any OpenAI-compatible provider — or a local model, which needs none

Everything else comes with the package.

---

## The part nobody else does: enforcement

Every token-saving tool has the same failure mode — **the agent forgets to use
it**. A tool the model may ignore gets ignored, and your savings are whatever
the model felt like that day.

`token-save-mcp install-hook` registers a `PreToolUse` hook that **blocks**
`Read` on files over the threshold and redirects the agent to `bulk_read`:

```
Read("src/server.py")
→ BLOCKED: This file is 606 lines (threshold: 350).
  Use bulk_read to delegate this read instead.
  Need exact content to EDIT? Re-read with offset/limit — that passes through.
```

What still passes through, by design:

- **Targeted reads** (`offset`/`limit`) — editing needs exact text
- **Small files** — under the threshold, delegating costs more than it saves
- **Binaries** and missing files — nothing to summarise

**Not ready to be told no?** Install it in warn mode instead — the read goes
through, but you see what it cost:

```bash
token-save-mcp install-hook --hook-mode warn
```

Enforcement is **opt-in** and reversible: `token-save-mcp uninstall-hook`.

> The hook is Claude Code only. The `bulk_read` / `code_write` tools are plain
> MCP and work in any client — Cursor, Cline, Windsurf, Codex — just without
> the enforcement layer.

---

## Tools

### `bulk_read(question, paths, model?, effort?)`

Read files without pulling them into context.

```
bulk_read(
  question="Which methods touch the database, and where is auth enforced?",
  paths=["src/service.py", "src/handlers.py"]
)
```

**Use for:** surveying unfamiliar code, "what does this do", tracing a flow
across files, finding where something is handled.

**Don't use for:** editing (you need exact text — use a targeted read),
debugging that needs your own reasoning over raw code, or files under ~350
lines where delegation overhead exceeds the saving. The tool tells you when
you've crossed that line rather than silently burning a call.

### `code_write(spec, reference, target?, model?, effort?)`

Generate boilerplate matching an existing file's style. With `target`, the code
is written **straight to disk** and only a confirmation returns — the generated
code never enters your context at all.

```
code_write(
  spec="pytest suite for clamp(value, lo, hi), covering both bounds and lo>hi",
  reference=["tests/test_total.py"],
  target="tests/test_clamp.py"
)
→ Wrote tests/test_clamp.py (32 lines). Not read into your context.
```

Never overwrites: the target is created with `O_EXCL`, which also refuses to
follow a dangling symlink.

### `status()`

Prints the live configuration and makes one tiny call to prove the worker is
actually reachable.

### `token-save-mcp stats`

Every call appends one line to a local ledger, so you can see what the tool has
actually saved you. Example output after a few weeks of use:

```
$ token-save-mcp stats --badge

  token-save-mcp — all time

  148 calls · 71,204 lines of code read by a worker
  context saved: 812,455 tokens (94%)
  worker time:   612s total

  Markdown badge:
  ![token-save](https://img.shields.io/badge/context%20saved-812K%20tokens-brightgreen)
```

The ledger is a plain JSONL file in `~/.token-save/` and never leaves your
machine. `--since 7` limits the window; `TOKENSAVE_NO_LEDGER=1` turns recording
off entirely.

---

## Measured savings

Real runs, not projections. Each number is the footer from an actual call:

| What | Size | Direct read | Via token-save | Saved |
|---|---|---|---|---|
| This project's own server.py | 606 lines | ≈7,042 tok | ≈234 tok | **97%** |
| A large TypeScript handler | 602 lines | ≈13,340 tok | ≈689 tok | **95%** |
| Production Python service | 443 lines | ≈5,788 tok | ≈684 tok | **88%** |
| 4 files across a codebase | 1,910 lines | ≈28,379 tok | ≈304 tok | **99%** |
| Code generation to disk | 58 lines written | — | 0 tok | **100%** |

**Method:** "direct read" is the file's own size at ~3.6 chars/token (source
code is denser than prose); "via token-save" is the returned answer measured the
same way. The worker's in/out numbers come from the provider's `usage` field.
Reproduce any row by running the same call — the footer prints on every one.

**Where it's weaker, honestly:** on a 281-line diff the saving was 67%, because
a short input with a long answer is the worst case. The tool says so in its own
output. Savings are best where the file is big and the question is narrow.

---

## How it compares

Different tools solve "too many tokens" in genuinely different ways:

| | Approach | Enforced? | Savings figure |
|---|---|---|---|
| **token-save-mcp** | LLM worker reads, returns an answer | **Yes** — hook blocks Read | Measured per call |
| Static AST tools | Parse the tree, return exact symbols | No | Deterministic |
| Other delegation MCPs | LLM worker, single provider | No | Usually estimated |

**Static AST tools are better than this one** at "give me the exact body of
`handleRequest`" — they're free, instant, and can't hallucinate. Reach for them
for symbol lookup.

This tool is for **semantic questions over large files** — "what does this
service do", "where does auth happen", "which of these files handle retries" —
where you want an answer, not an extract. That costs a worker call and a few
seconds, and a worker can be wrong. Use both.

---

## Configuration

| Variable | Default | Purpose |
|---|---|---|
| `TOKENSAVE_PROVIDER` | `ollama` | Preset: ollama, openrouter, deepseek, groq, local |
| `TOKENSAVE_API_KEY` | — | Overrides the preset's key variable |
| `TOKENSAVE_BASE_URL` | preset | Any OpenAI-compatible endpoint |
| `TOKENSAVE_MODEL` | preset | Worker model id |
| `TOKENSAVE_MIN_LINES` | `350` | Hook threshold, and the "too small" warning |
| `TOKENSAVE_HOOK_MODE` | `block` | `warn` allows the read but flags the cost |
| `TOKENSAVE_HOOK_MAX_BYTES` | `100000` | Also block on size — catches minified files |
| `TOKENSAVE_MAX_CORPUS_BYTES` | `2000000` | Ceiling on one request |
| `TOKENSAVE_TIMEOUT` | `600` | Seconds per call |
| `TOKENSAVE_MAX_RETRIES` | `4` | Retries on transient failures |
| `TOKENSAVE_MAX_CONCURRENCY` | `3` | Match your provider's limit |
| `TOKENSAVE_LEDGER` | `~/.token-save/ledger.jsonl` | Where `stats` reads from |
| `TOKENSAVE_NO_LEDGER` | unset | Set to disable local recording |

---

## When not to use this

Being clear about this is the point, not a disclaimer:

- **You need exact text to edit.** Use a targeted read. The hook lets those through.
- **You're debugging subtle behaviour.** Summaries lose the detail that matters.
- **The file is small.** Under ~350 lines, reading directly is cheaper and faster.
- **The worker can be wrong.** It's an LLM. For anything you'll act on blindly,
  verify against the source. Static tools don't have this failure mode.

---

## Development

```bash
git clone https://github.com/Habartru/token_save_mcp
cd token_save_mcp
pip install -e ".[dev]"

python tests/test_server.py    # 95 server tests — no API calls
python tests/test_cli.py       # 24 CLI / onboarding tests
bash tests/test_hook.sh        # 21 hook routing tests
```

The test suite stubs the transport, so it costs nothing to run and is safe in
CI. It covers the retry loop, corpus assembly, fence stripping, the disk-write
guards, and every hook routing decision.

---

## Credits

The delegation-plus-hook pattern is adapted from the `shunt` plugin in
[spotify/portal-ai-plugins](https://github.com/spotify/portal-ai-plugins)
(Apache-2.0), which routes the same kind of work through Spotify's internal
Portal CLI. This project keeps the idea and swaps the transport for any
OpenAI-compatible provider, so no corporate Portal instance is required. Files
also travel in-process rather than through `argv`, which removes the 128 KiB
per-argument limit on Linux.

MIT licensed.
