Skip to main content
Glama

rtk-mcp

MCP proxy that filters oversized tool responses. Companion to 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):

  • 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.

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

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):

[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, 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:

{"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

  • Benchmark harness with quality gates (see 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