rtk-mcp
by KCuppens
README.md
# rtk-mcp
**MCP proxy that filters oversized tool responses.** Companion to [rtk](https://github.com/KCuppens/rtk) — rtk handles CLI output; rtk-mcp handles MCP tool responses.
Wraps another MCP server (Playwright, custom, etc.), intercepts tool responses, and filters the ones that are known token sinks: `browser_take_screenshot` (via downscale), `browser_snapshot` (via role pruning).
## Status
**Early scaffold.** Filters are implemented and benchmarked against real Playwright fixtures (see [BENCHMARKS.md](BENCHMARKS.md)):
- **Screenshot filter**: 36–79% LLM token savings on captured fixtures, quality gates pass.
- **Snapshot filter**: 0–0.6% savings on the fixtures tested. Structural role-pruning has diminishing returns on well-formed a11y trees. The screenshot filter is the primary win.
End-to-end testing against a live Playwright MCP server is pending. See [Roadmap](#roadmap).
## Why
Claude vision tokenizes images by **dimensions**, not file size. A 1920×1080 screenshot costs ~2,765 tokens regardless of PNG vs WebP. The lever is *downscaling*, not format conversion.
Playwright's a11y snapshots run 10–100 KB per call. Interactive elements (buttons, links, inputs) are usually <10% of the payload; the rest is landmarks, static text, and structural noise.
rtk-mcp targets these two hotspots without changing your Claude Code setup beyond registering it as your MCP server.
## Install
```bash
npm install -g rtk-mcp
# or from source
git clone https://github.com/KCuppens/rtk-mcp.git
cd rtk-mcp && npm install && npm run build && npm link
```
## Configure
Create `~/.config/rtk-mcp/config.toml` (see [`examples/config.toml`](examples/config.toml)):
```toml
[target]
command = "npx"
args = ["-y", "@playwright/mcp"]
[filters.browser_take_screenshot]
enabled = true
maxLongEdge = 1024
format = "webp"
quality = 85
[filters.browser_snapshot]
enabled = true
dropRoles = ["separator"]
[telemetry]
logPath = "~/.local/share/rtk-mcp/savings.jsonl"
```
Then in Claude Code's MCP config, point at `rtk-mcp` instead of the target server directly.
## Design
- **Line-based JSON-RPC**. Newline-delimited on stdio, per MCP convention.
- **Passthrough default.** Unknown tools, unknown message types, unfilterable content — all forwarded verbatim with zero overhead.
- **Fallback on filter failure.** If a filter throws, the raw response is forwarded and the failure is logged to stderr. The proxy is never allowed to break the wrapped tool.
- **No config = no filtering.** If you don't enable a filter, that tool passes through untouched.
- **Never drop interactive roles.** The snapshot filter refuses to drop `button`, `link`, `textbox`, and other interactive roles even if you configure them in `dropRoles`. Safety rail.
## Filters
### browser_take_screenshot
Downscales to `maxLongEdge` (default 1024px) via [sharp](https://github.com/lovell/sharp), re-encodes as configured format.
- **Zero LLM-token quality loss** at 1024px+ for most UI screenshots.
- **OCR floor around 800px** for small text. Below that, the LLM may misread numeric values or short labels.
- Below `minTriggerBytes` (default 20 KB), images pass through untouched.
### browser_snapshot
Line-based pruning of Playwright's YAML-ish a11y tree. Drops configured roles (`dropRoles`) and their child subtrees.
- **Interactive elements always preserved.** `button`, `link`, `textbox`, `checkbox`, `radio`, `combobox`, `menuitem`, `tab`, `switch`, `searchbox`, `slider`, `spinbutton`, `listbox`, `option`, `menu`, `form`, `dialog`, `heading`.
- **Default drops**: `separator`. Landmarks (`region`, `main`, `nav`) are NOT dropped by default because they aid orientation.
- On parse anomaly (indentation confusion, unexpected format), the raw snapshot is forwarded.
## Telemetry
If `[telemetry].logPath` is set, one JSON line is written per filtered call:
```json
{"ts":"2026-07-08T19:20:00Z","tool":"browser_take_screenshot","rawSize":184320,"filteredSize":42188,"savingsPct":77.1,"note":"1920x1080→1024x576 (180.0KB→41.2KB)"}
```
Consume with `jq`, tail, or a future `rtk gain --mcp` command.
## Roadmap
- [x] Benchmark harness with quality gates (see [BENCHMARKS.md](BENCHMARKS.md))
- [ ] End-to-end test against a real Playwright MCP server (needs `browser_snapshot` refs — current fixtures use `page.ariaSnapshot()`)
- [ ] Smarter snapshot pruning: drop leaf regions with no interactive descendants (subtree-aware)
- [ ] Additional benchmark fixtures: e-commerce, SPA-heavy, forms, admin dashboards
- [ ] `WebFetch` selector-scoped extraction (if any MCP server exposes it)
- [ ] Delta snapshots (diff vs prior snapshot within a session)
- [ ] Config validator: `rtk-mcp --check`
- [ ] Publish to npm
## License
MIT
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues