@dongnh311/claude-context-saver
v0.2.1
Published
MCP server that cuts Claude Code token usage. Two capabilities: log compression (build/test/install output) and smart file reading (AST-backed symbol-level source access). Context-efficient tooling for Pro-tier users.
Maintainers
Readme
@dongnh311/claude-context-saver
MCP server that cuts Claude Code token usage through two cooperating capabilities: (1) log compression for build/test/install output, (2) smart file reading for source code at the symbol level. Same philosophy: don't let raw, unfiltered content flood Claude's context — let Claude pull exactly what it needs.
Note: this package was originally published as
claude-log-compressor(log side only). It has been renamed to@dongnh311/claude-context-savernow that file-side capabilities are landing. The old name is deprecated; please update your config (see Quick start).
Quick start
1. Add to your Claude Code MCP config at ~/.claude/mcp.json (or project .claude/mcp.json):
{
"mcpServers": {
"context-saver": {
"command": "npx",
"args": ["-y", "@dongnh311/claude-context-saver@latest"]
}
}
}Or via CLI:
claude mcp add context-saver -s user -- npx -y @dongnh311/claude-context-saver@latest2. Restart Claude Code. Tools smart_run, smart_build, smart_test, read_log_section show up now. File-side tools (smart_read, list_symbols, read_symbol, find_references_in_file, read_lines) ship in v0.2.0.
3. (Recommended) Nudge Claude in your project's CLAUDE.md:
## Token-efficient tooling (claude-context-saver)
- Build/test: use `smart_build` / `smart_test` instead of bash gradle/npm/jest/pytest.
- File reading (from v0.2): prefer `smart_read` over `Read` for source files > 300 lines.Why
Two dominant sources of wasted tokens in a Claude Code session:
- Noisy command output.
gradle build,npm install,pytestcan emit 5k–20k tokens per invocation, 90% of which is progress bars, deprecation warnings, and framework noise. - Whole-file reads for narrow questions. Claude reads a 2,000-line
MainActivity.ktto fix one function; 90% of what goes into context is irrelevant.
Both compound quickly: the Pro 5-hour limit hits before the real work is done, prompt-cache hit rate drops, and the model's attention gets diluted.
How it works
Claude calls the MCP tool instead of bash/Read. The server runs the command (or parses the file), keeps the heavy output on disk, and returns only a compact summary plus an id Claude can use to retrieve detail on demand.
[Claude] ──► smart_build("./gradlew assembleDebug")
│
▼
[context-saver]
│
├─► run command → 12k tokens raw
│ │
│ ▼
│ classify → "gradle"
│ │
│ ▼
│ gradle compressor (errors, deduped warnings, failing task)
│ │
│ ▼
│ cache full log → ~/.cache/claude-context-saver/logs/grd_abc12345.log (7d TTL)
│
└─► return 0.8k token summary + log_id="grd_abc12345"
[Claude] ◄──Same pattern for smart_read: parse file → return outline first, focused symbols on demand, cache AST for the session.
Tools — log side
| Tool | Input (highlights) | What it does |
|---|---|---|
| smart_run | command, cwd?, timeout_seconds?, max_output_tokens? | Runs any shell command, auto-classifies output, returns compressed summary + log_id. |
| smart_build | tool? (gradle/npm/cargo/make/auto), args?, cwd? | Auto-detects build tool from cwd (gradlew, package.json, Cargo.toml, Makefile). |
| smart_test | framework? (jest/pytest/junit/go/auto), pattern?, cwd? | Auto-detects test framework (go.mod, pytest config, package.json, gradlew). |
| read_log_section | log_id, grep?, lines_around?, start_line?/end_line?, max_tokens? | Slice a cached full log. Escape hatch when compressed view isn't enough. |
Tools — file side
| Tool | Input | What it does |
|---|---|---|
| smart_read | path, focus?, mode? (outline/full/auto), max_tokens? | Smart replacement for Read. Returns outline first, focused symbols on demand. |
| list_symbols | path, kinds?, depth? | Full symbol tree without bodies. |
| read_symbol | path, names[] (dotted: Class.method), include_surrounding? | Return bodies of named symbols. |
| find_references_in_file | path, identifier, context_lines? | AST-aware "where is X used inside this file" with inside_symbol annotation. |
| read_lines | path, start_line, end_line | Line-range fallback when symbol approach doesn't fit. |
Languages supported for file side: Kotlin, Java, TypeScript/TSX, JavaScript/JSX, Python, Go, Rust. Swift deferred to a future release.
Benchmark — real Android project
Measured live on ./gradlew … against a real Android Compose + C++/NDK project (MasterCamera, 2 Gradle modules):
| Command | Raw tokens | Compressed | Reduction |
|---|---:|---:|---:|
| clean :app:assembleDebug | 2,090 | 82 | 96.1% |
| clean :app:assembleRelease (R8) | 2,720 | 82 | 97.0% |
| :app:installDebug → emulator | 955 | 3 | 99.7% |
| :app:testDebugUnitTest | 780 | 82 | 89.5% |
| :app:compileDebugKotlin (2 injected errors) | 667 | 114 | 82.9% ⟵ both errors with file:line:col preserved |
Average ≈ 93% reduction, 100% of errors and warnings kept.
Benchmark — real source files (file side)
Measured on actual Kotlin source from the same MasterCamera project:
| File | Lines | Full | smart_read outline | smart_read focus | Reduction (outline) |
|---|---:|---:|---:|---:|---:|
| CameraControl.kt | 1,040 | 7,949 | 839 | 1,261 (focus=startRecord) | 89.4% |
| Camera2Control.kt | 951 | 8,721 | 878 | 893 (focus=captureImage) | 89.9% |
Both files cleared the SPEC §2 target ("≤20% of full-file tokens for a ≥1000-line Kotlin file"). Focused mode still beats 80% reduction because the outline gets bundled with the body.
Benchmark — synthetic fixtures (log side)
| Fixture | Kind | Original | Compressed | Reduction | |---|---|---:|---:|---:| | gradle-success.log | gradle | 1,005 | 64 | 93.6% | | gradle-failure.log | gradle | 1,708 | 173 | 89.9% | | jest-passing.log | jest | 242 | 34 | 86.0% | | jest-failing.log | jest | 578 | 363 | 37.2% | | junit-failing.log | junit | 827 | 151 | 81.7% | | npm-install-success.log | npm | 648 | 218 | 66.4% | | npm-install-fail.log | npm | 337 | 279 | 17.2% (info-dense) | | pytest-passing.log | pytest | 224 | 20 | 91.1% | | pytest-failing.log | pytest | 583 | 333 | 42.9% |
Run locally: npm run bench.
What each log compressor keeps / drops
Gradle
- Keep: BUILD status + duration, every Kotlin/javac error with
file:line:col, deduped warnings with occurrence counts ([×3]), failing task name, "What went wrong" block. - Drop:
> Task :xxx UP-TO-DATE/NO-SOURCE,Download …URLs, Daemon startup chatter.
npm / yarn / pnpm
- Keep:
npm ERR!blocks with error code,added X packages, deduped deprecations (per package, top 10), audit summary. - Drop: duplicate deprecation lines, download progress, boilerplate audit text.
Jest / Vitest
- Keep:
Test Suites:/Tests:/Time:summary, every● Test › namefailure block with assertion diff. - Drop:
PASS src/…names (collapsed to count), duplicated "Summary of failing tests" block.
Pytest
- Keep: platform header,
ERRORS:+FAILURES:blocks verbatim, short summary info, final result line. - Drop: progress dot lines (
tests/foo.py ...... [10%]), rootdir/plugin chatter.
JUnit (Gradle/Maven test output)
- Keep: each
FAILEDtest with FQCN + assertion + app stack frames, total count. - Drop:
PASSEDnames (count only), framework frames (org.junit.*,java.base/jdk.internal.*,kotlinx.coroutines.internal.*,android.os.*, reflection).
Generic (fallback)
- Keep: every line matching
/error|fail|exception|fatal|panic/i. - Transform: consecutive identical lines deduped, middle-truncated when over budget.
FAQ
What if the compressor drops something I need?
Every response includes log_id. Call read_log_section with a grep pattern or start_line/end_line. Full log is on disk for 7 days.
Does it work on Windows? WSL only in v0.1/v0.2. Native Windows is Phase 3.
Does it send anything to a server? No. Commands run locally, logs live on your disk, nothing phones home.
Will Claude actually use smart_* instead of bash?
With the CLAUDE.md snippet in Quick start, yes. Tool descriptions also explicitly say "ALWAYS prefer this". In practice adherence is high.
Why a scoped package name (@dongnh311/…)?
The plain claude-context-saver unscoped name was already taken by an unrelated package. Scoped keeps the descriptive name.
Development
npm install
npm run typecheck
npm test # vitest
npm run bench # compressor benchmark table
npm run build # tsc → dist/index.js (+ shebang + chmod)
npm run dev # tsc --watch
npx @modelcontextprotocol/inspector node dist/index.js # manual tool exerciseLayout, conventions, milestones: see CLAUDE.md.
Unified implementation spec: SPEC.md. Archived log-only spec: SPEC-v0.1.md. High-level pitch: spec-overview.md.
Links
- npm:
@dongnh311/claude-context-saver - GitHub: dongnh311/claude-context-saver
- Issues / feedback: GitHub Issues
License
MIT
