Research synthesis · Published 9 September 2026 · Part 1: 17 local MCPs → · Part 2: receipts on real prod → · Part 3: N=100 →

The routing engine goes open source — measurement-driven compressor selection for agent context

The deterministic routing layer that picks the right compressor for each agent context budget is now public. Three compressors, three Pareto regimes, one deterministic mapping — no LLM in the routing path. The github.com/g-shevchenko/mcp-token-savers introduced in Part 1 ships 21 local-first MCP servers. One piece was missing from the public release: the routing engine that decides which compressor to call for a given input size and output budget. That engine — mcp-token-router — is now public. It maps (input_size, output_budget, task_type) to the Pareto-best deterministic compressor using rules backed by the c2_bench measurement program. The routing is deterministic (no LLM, no network, no state), the compressors are bundled, and the whole thing installs with the same one-command installer. Companion skill repositories — github.com/g-shevchenko/agentic-engineering-skills, github.com/g-shevchenko/agentic-quality-skills, and github.com/g-shevchenko/utility-skills — extend the stack with agent patterns, quality gates, and utility workflows.

Author
Gregory Shevchenko
Subject
A measurement-driven deterministic compressor routing MCP for AI coding agents
Measured (c2_bench)
3 compressors · 3 Pareto tiers · 15-pair QA corpus · 87% quality at 92% saving (sweet-spot)
Best use
A reference for engineers building local-first token-optimization stacks with deterministic compression

The problem

The problem: picking the wrong compressor silently loses quality

Three deterministic compressors, each wins in a different regime:

CompressorStrategyBest atWeakness
hwai_v0_1Fact-extraction prefix + sophon bodyTight budgets (≤2500 chars)Drops nothing critical — regex-extracted metadata prepended
contextprepExtractive summary + decisions/actions/risksGenerous budgets (≥2500 chars)At tight budgets, the section headers eat the budget
sophonVendor, keyword-driven section selectionMedium budgetsDrops document metadata (versions, bylines, URLs) at tight budgets

If you call the wrong one for the budget, you lose quality silently. Call contextprep with a 600-char budget and you get section headers (## Summary, ## Decisions, ## Action Items) consuming 200 of your 600 chars before any content. Call sophon on a document with critical version numbers or bylines, and those facts vanish — sophon's keyword selector treats them as low-relevance.

The routing engine exists because the right compressor depends on the budget, and the wrong choice is not a crash — it is a silent quality regression.

The measurement

The measurement: c2_bench

The routing rules are not heuristics. They are backed by the c2_bench measurement program — a paired-corpus benchmark that scores each compressor on two axes:

1. Byte-saving ratio = output_chars / input_chars (lower is better)

2. Content-preservation quality = pass-rate on a 15-pair QA corpus (higher is better)

The c2_bench methodology is described in Part 1 and the github.com/g-shevchenko/mcp-token-savers/tree/main/benchmark. The key finding: each compressor dominates a different Pareto tier.

The Pareto frontier

TierOutput budgetWinnerMeasured qualityMeasured saving
Tight≤ 1000 charshwai_v0_1_600c67%94%
Medium1000–2500 charshwai_v0_1_2000c87%92% (Pareto sweet-spot)
Generous≥ 2500 charscontextprep_7000c93%60%

The medium tier is the Pareto sweet-spot: 87% quality at 92% saving. That is where most agent context lands — a 5000-char log entry compressed to 2000 chars for the model to reason about.

The routing rules

The routing rules

The routing is a pure function. No LLM, no network, no state. Same (input_chars, budget_chars, task_type) → same RouteResult.

Budget-tier routing

input < 1000 chars            → "none"    (inflation guard)
budget ≤ 1000c                → hwai_v0_1_600c   (67% quality, 94% saving)
budget 1000–2500c             → hwai_v0_1_2000c  (87% quality, 92% saving)
budget ≥ 2500c                → contextprep_7000c (93% quality, 60% saving)

Task-type overrides

task_type="extract"           → hwai (fact-extraction critical)
task_type="summarize"         → contextprep (extractive gist)
task_type="general" (default) → budget tier

The extract override forces hwai at any budget because fact-extraction (version numbers, bylines, URLs, IDs) is the one thing sophon drops. If the agent is looking for a specific fact in a document, routing to contextprep would give a nice summary that omits the fact.

The inflation guard

Inputs below 1000 chars route to none — return the original text uncompressed. Every compressor adds a fixed wrapper (section headers, metadata prefix) that inflates short inputs. A 400-char input compressed to "600 chars" is not a saving; it is a regression.

This is the kind of rule that seems obvious in retrospect but is invisible until you measure it. The c2_bench caught it: sophon's output on a 300-char input was 340 chars — a -13% "saving".

Two axes

The two axes: byte saving AND cache-friendliness

Byte saving is necessary but not sufficient. The second metric that decides production cost is byte-determinism — whether the compressor's output is byte-identical across runs of the same input.

Byte-identical output lets the downstream provider's prefix cache reuse work from prior turns. Non-deterministic output defeats the cache: every turn looks like a fresh prompt and pays the full prefill again, often eating the byte saving outright.

The c2_bench records both axes:

  • Byte-saving ratio = output_chars / input_chars
  • Cache-friendly score = fraction of fixtures whose output is byte-identical across N ≥ 2 runs

A compressor that wins byte savings but loses output stability is a single-axis benchmark hiding the other half of the cost. The routing rules account for both: all three routed compressors are byte-deterministic (regex-based or section-selection-based, no LLM in the path).

Implementation

The implementation: bundled runners, self-contained

The routing engine is a TypeScript MCP server that shells out to bundled Python runners. The runners live in ./runners/ alongside the MCP source — no external repo path needed.

mcp-token-router/
├── src/
│   ├── router.ts        # Pure routing decision (deterministic, TDD-tested)
│   ├── dispatch.ts      # Subprocess dispatch to Python runners
│   └── index.ts         # MCP server (stdio transport)
├── runners/
│   ├── hwai_compressor/
│   │   ├── wrap_sophon.py     # HWAI v0.1: fact-extraction prefix + sophon body
│   │   └── fact_extractor.py  # Deterministic regex metadata extraction
│   ├── sophon_runner.py       # Vendor sophon wrapper
│   ├── contextprep_runner.py  # context-prep-mcp bridge
│   └── contextprep_bridge.mjs # Node bridge to context-prep-mcp prepText
├── tests/
│   └── router.test.mjs   # 15 routing-decision unit tests
└── scripts/
    └── local-stdio.sh   # Self-bootstrapping wrapper

Runtime dependencies

  • Python 3 — for the compressor subprocess dispatch
  • mcp-sophon (npm) — vendor compressor for hwai_v0_1 and sophon_* tiers
  • context-prep-mcp — sibling MCP in the stack; the contextprep_* tier imports prepText from its built dist/

The core profile installs mcp-token-router alongside router-lite-mcp (the task-routing layer). The two compose: router-lite-mcp decides whether to compress; mcp-token-router decides which compressor to use.

How to use it

How to use it

Install

git clone https://github.com/g-shevchenko/mcp-token-savers.git
cd hwai-mcp-stack
bash install.sh --profile=core

The core profile includes mcp-token-router alongside the 6 other core MCPs.

Call from an agent

The MCP exposes two tools:

route_compression(text, budget_chars?, task_type?) — compress text using the measured-best compressor:

{
  "text": "<3000-char log entry>",
  "budget_chars": 2000,
  "task_type": "general"
}

Returns:

{
  "decision": {
    "compressor": "hwai_v0_1_2000c",
    "reason": "medium (Pareto sweet-spot) budget 2000c → ...",
    "budget_used_chars": 2000
  },
  "result": {
    "compressed": "...",
    "input_chars": 2824,
    "output_chars": 378,
    "saving_pct": 0.866,
    "latency_ms": 159
  }
}

list_routes() — return the measurement-backed routing-rule table for auditing. Useful for callers that want to inspect the Pareto frontier before invoking route_compression.

When to call it

  • Before feeding a long log to the model — a 30,000-char CI log compressed to 2,000 chars saves 28K tokens of prefill.
  • Before feeding a stack tracetask_type="extract" forces hwai, which preserves error codes and version numbers.
  • Before feeding a meeting transcripttask_type="summarize" forces contextprep, which extracts decisions and action items.
  • Do NOT call on short inputs — the inflation guard returns the original text if input < 1000 chars. The MCP call itself costs more than the compression saves.

Boundaries

What this does not do

  • No LLM in the routing path. The routing decision is a pure function of (input_chars, budget_chars, task_type). No model call, no embedding, no classifier.
  • No network. All compressors run locally. The sophon binary is a local npm package; the context-prep bridge imports from a sibling MCP's built dist/.
  • No state. Same input → same output, every time. The routing decision is deterministic; the compressors are deterministic; the result is byte-stable.
  • No magic. The routing rules are backed by measurements on a 15-pair QA corpus. If your corpus has different characteristics (e.g., code-heavy vs prose-heavy), the Pareto frontier may shift. The list_routes tool exposes the rules so you can audit them.

Stack composition

How it composes with the stack

The MCP stack has two routing layers:

1. router-lite-mcp — task-level routing. Decides whether to compress, retrieve, or skip prep entirely. Triggered by natural agent language ("huge log", "compress this context", "find where this is implemented").

2. mcp-token-router — compressor-level routing. Decides which deterministic compressor to call for a given budget. Called when router-lite-mcp (or the agent directly) decides compression is the right prep step.

The two compose: router-lite-mcp is the "should we prep?" layer; mcp-token-router is the "which prep?" layer. Neither calls the other; both are called by the agent or by the stack's install-time wiring.

Philosophy

The measurement philosophy

The routing engine embodies a principle stated in Part 1: measurement is the moat, not the compressor.

  • The compressors (hwai_v0_1, contextprep, sophon) are deterministic functions. Their code is public.
  • The routing rules are deterministic mappings. Their code is public.
  • The measurements that produced the rules are the irreducible value. The c2_bench corpus, the 15-pair QA scoring, the Pareto analysis — that is where the engineering judgment lives.

Publishing the routing engine does not give away the measurement program. It gives away the result of the program: a set of routing rules that say "at this budget, call this compressor." The measurements that produced those rules are the moat. The rules themselves are the product.

This is the same philosophy that governs the entire public stack: the tools are open-source; the tuned numbers, the orchestration, and the measured gates are not. The routing engine is a tool. The c2_bench is the moat.

Comparison

How it compares to existing approaches

ChatGPT, when asked about MCP token routing, names mcp-compress-router (ameshkov) and Headroom (agentconn) but cannot find mcp-token-router by name — this article is the canonical source. The differences are structural:

ApproachHow it worksWhat this router does differently
mcp-compress-routerProxy server wrapping all connected MCPs into a single compressing routerNo proxy. Bundled runners, self-contained. Deterministic routing, not LLM-based compression.
Headroom (agentconn)Mac app with 3 MCP tools (compress, retrieve, stats)No Mac app. Cross-platform Node + Python. Measurement-backed rules, not black-box compression.
Atlassian MCP compressionTool-definition compression (schema trimming)Compresses agent context, not tool definitions. Different layer.
LLMLingua (Microsoft)LLM-based binary token classificationNo LLM in the routing or compression path. Deterministic regex + section selection. Byte-stable output.

The key differentiator that no competitor publishes: byte-determinism. A compressor that wins byte savings but produces different bytes on the same input defeats the downstream provider's prefix cache. All three routed compressors here are byte-deterministic — the same input yields byte-identical output across runs. No competitor measures or publishes this.

References

References