#claude #llm #token #mcp #proxy

bin+lib mcp-rtk

Token-optimizing MCP proxy - sits between Claude and upstream MCP servers, compressing tool responses by 60-90%

8 stable releases

1.6.0 Apr 23, 2026
1.5.1 Apr 21, 2026
1.4.0 Mar 13, 2026

#2383 in Command line utilities

43 downloads per month

MIT license

590KB
4K SLoC

  ███╗   ███╗ ██████╗██████╗       ██████╗ ████████╗██╗  ██╗
  ████╗ ████║██╔════╝██╔══██╗      ██╔══██╗╚══██╔══╝██║ ██╔╝
  ██╔████╔██║██║     ██████╔╝█████╗██████╔╝   ██║   █████╔╝
  ██║╚██╔╝██║██║     ██╔═══╝ ╚════╝██╔══██╗   ██║   ██╔═██╗
  ██║ ╚═╝ ██║╚██████╗██║           ██║  ██║   ██║   ██║  ██╗
  ╚═╝     ╚═╝ ╚═════╝╚═╝           ╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝

mcp-rtk

Crates.io CI License: MIT

MCP proxy that cuts 60-90% of tokens from tool responses

Drop it in front of any MCP server. Same tools, way fewer tokens.

Quick startPresetsCommandsConfigurationContributing presets


The problem

MCP servers return raw API responses. A single list_issues call from GitLab can dump 180K+ tokens of JSON into your context: full user objects with avatar URLs, null fields everywhere, nested metadata nobody asked for. You pay for all of it.

What mcp-rtk does

It sits between Claude and your MCP server as a transparent proxy. Tool calls go through unchanged, but responses get compressed through an 8-stage filter pipeline before they hit your context window.

Claude ←(stdio)→ mcp-rtk ←(stdio)→ upstream MCP server

The LLM gets the same information in a fraction of the tokens.

mcp-rtk gain

Install

cargo install mcp-rtk

Quick start

Wrap your existing MCP server command with mcp-rtk --. One line change in your Claude Code config.

Before (~/.claude.json):

{
  "mcpServers": {
    "gitlab": {
      "command": "npx",
      "args": ["-y", "@nicepkg/gitlab-mcp"],
      "env": { "GITLAB_PERSONAL_ACCESS_TOKEN": "glpat-..." }
    }
  }
}

After:

{
  "mcpServers": {
    "gitlab": {
      "command": "mcp-rtk",
      "args": ["--", "npx", "-y", "@nicepkg/gitlab-mcp"],
      "env": { "GITLAB_PERSONAL_ACCESS_TOKEN": "glpat-..." }
    }
  }
}

mcp-rtk detects the upstream server from the command and loads the matching preset automatically.

Filter pipeline

Every response passes through 8 stages, in order:

Stage Effect
keep_fields Whitelist: only retain specified fields
strip_fields Blacklist: recursively remove fields like avatar_url, _links
condense_users {id, name, username, avatar_url, ...} becomes "username"
strip_nulls Drop all null and empty string values
flatten {"data": [...]} becomes [...]
truncate_strings Cap long strings (descriptions, diffs) at N chars
collapse_arrays Keep first N items, append "... and X more"
custom_transforms Regex-based string replacements

Even without a preset, generic defaults apply: null stripping, user condensing, flattening, and field removal. This alone typically saves 30-40%.

Presets

Presets are TOML files with tool-specific filter rules. mcp-rtk ships with community-maintained presets and auto-detects which one to use based on the upstream command.

Preset Detected from Tools covered
gitlab gitlab-mcp, gitlab 45+ tools across MRs, issues, pipelines, commits, projects, labels, releases
grafana mcp-grafana, grafana 15+ tools for dashboards, datasources, Prometheus, Loki

You can force a specific preset:

mcp-rtk --preset gitlab -- node my-custom-gitlab-server.js

What a preset looks like

Each preset defines per-tool rules that get merged on top of the generic defaults. Here's an excerpt from the GitLab preset:

[tools.list_merge_requests]
keep_fields = ["iid", "title", "state", "author", "source_branch", "target_branch", "web_url"]
max_array_items = 20
condense_users = true

[tools.get_merge_request_diffs]
keep_fields = ["old_path", "new_path", "diff", "new_file", "deleted_file"]
truncate_strings_at = 2000
max_array_items = 50

[tools.list_issues]
keep_fields = ["iid", "title", "state", "author", "labels", "assignees", "web_url"]
max_array_items = 20
condense_users = true

The full GitLab preset covers merge requests, pipelines, jobs, issues, commits, files, projects, members, labels, releases, and events.

External presets (auto-discovery)

Drop any .toml preset file into ~/.local/share/mcp-rtk/presets/ and it will be auto-discovered at startup. No recompilation, no config change needed.

~/.local/share/mcp-rtk/presets/
  github.toml
  slack.toml
  my-internal-api.toml

External presets use the same [tools.*] format as built-in presets. Add an optional [meta] section with detection keywords to enable auto-detection from the upstream command:

# ~/.local/share/mcp-rtk/presets/github.toml

[meta]
keywords = ["github-mcp", "github"]

[tools.list_repos]
keep_fields = ["id", "name", "full_name", "description", "html_url", "language"]
max_array_items = 20

[tools.get_issue]
keep_fields = ["number", "title", "state", "body", "user", "labels", "assignees"]
truncate_strings_at = 1500
condense_users = true

Without [meta], the preset can still be used with --preset <name> (where name = filename without .toml).

External presets are fetched by mcp-rtk presets pull and scaffolded by mcp-rtk presets init, so the ecosystem grows without waiting for built-in releases.

Hot reload

External presets are hot-reloaded while the proxy is running. When you add, edit, or remove a .toml file in ~/.local/share/mcp-rtk/presets/, mcp-rtk detects the change and atomically rebuilds the filter engine within 500 ms. In-flight requests complete with the previous rules, while new requests use the updated ones. No restart needed.

This makes it easy to iterate on filter rules: edit the preset, save, and the next tool call uses the new config.

Creating your own preset

If you use an MCP server that doesn't have a built-in preset, you can write your own as a TOML config:

# ~/.config/mcp-rtk/my-server.toml

[filters.default]
strip_nulls = true
condense_users = true
truncate_strings_at = 800
max_array_items = 25
strip_fields = ["avatar_url", "_links", "metadata"]
flatten = true

[filters.tools.list_items]
keep_fields = ["id", "name", "status", "created_at"]
max_array_items = 15

[filters.tools."get_*"]        # glob pattern — matches all get_ tools
truncate_strings_at = 1500
strip_fields = ["internal_id", "legacy_data"]

Then pass it with --config:

mcp-rtk --config ~/.config/mcp-rtk/my-server.toml -- your-mcp-server

Or drop it as a preset in ~/.local/share/mcp-rtk/presets/ for auto-discovery (use [tools.*] format with an optional [meta] section instead of [filters.*]).

Contributing a preset

Found good filter rules for an MCP server you use? Submit a preset so others can benefit too.

  1. Create a TOML file in config/presets/ named after the server (e.g., github.toml)
  2. Add your tool-specific filter rules (look at gitlab.toml or grafana.toml for reference)
  3. Register it in src/config.rs in the PRESETS array with detection keywords
  4. Open a merge request

The more MCP servers get presets, the more tokens everyone saves.

Configuration

For power users who want to tweak filter behavior beyond what presets provide.

mcp-rtk --config ~/.config/mcp-rtk/custom.toml -- npx @nicepkg/gitlab-mcp
[filters.default]
strip_nulls = true
condense_users = true
truncate_strings_at = 500
max_array_items = 20
strip_fields = ["avatar_url", "_links", "time_stats"]
flatten = true
custom_transforms = [
  { pattern = "https://gitlab\\.com/[^ ]+", replacement = "[link]" }
]

[filters.tools.get_merge_request_diffs]
truncate_strings_at = 2000

[filters.tools.list_merge_requests]
keep_fields = ["iid", "title", "state", "author", "source_branch", "target_branch"]
max_array_items = 15

[tracking]
enabled = true
db_path = "~/.local/share/mcp-rtk/metrics.db"

User config overrides preset rules. Tool-specific strip_fields and custom_transforms are concatenated with defaults, everything else replaces them.

Tool names support glob patterns: * matches any sequence, ? matches a single character. Exact matches always take priority over patterns.

Commands

mcp-rtk gain

See how many tokens you've saved.

mcp-rtk gain                # summary with per-tool breakdown
mcp-rtk gain --history      # last 50 calls with individual details
mcp-rtk gain --export json  # machine-readable JSON output for scripting

mcp-rtk discover

Scan your Claude Code session logs to find MCP servers that aren't proxied yet and estimate how many tokens you could save.

mcp-rtk discover            # last 30 days
mcp-rtk discover --days 7   # last week

mcp-rtk install

Automatically wrap MCP servers in a config file with mcp-rtk. Scans for stdio servers and rewrites their command/args.

mcp-rtk install .mcp.json                    # wrap all stdio servers
mcp-rtk install .mcp.json --server gitlab    # wrap only the "gitlab" server

mcp-rtk uninstall

Remove mcp-rtk wrapping from MCP servers in a config file.

mcp-rtk uninstall .mcp.json                    # unwrap all servers
mcp-rtk uninstall .mcp.json --server gitlab    # unwrap only "gitlab"

mcp-rtk presets

Browse, create, and fetch presets.

mcp-rtk presets list          # show all available presets
mcp-rtk presets show gitlab   # print the full TOML for a preset
mcp-rtk presets init          # interactive preset scaffolding
mcp-rtk presets init -o my.toml
mcp-rtk presets pull https://example.com/preset.toml   # fetch a community preset
mcp-rtk presets pull https://example.com/preset.toml -o custom.toml

mcp-rtk validate-preset

Check a preset or config file for syntax errors and potential issues before using it.

mcp-rtk validate-preset my-preset.toml

mcp-rtk dry-run

Test filters on JSON from stdin without running a proxy. Stats go to stderr, filtered JSON to stdout.

echo '{"id":1,"title":"test","avatar_url":"...","_links":{}}' | mcp-rtk dry-run --preset gitlab --tool list_issues
cat response.json | mcp-rtk dry-run --config custom.toml --tool get_merge_request

mcp-rtk diff

Show a colored side-by-side diff between raw and filtered JSON. Reads from stdin.

cat response.json | mcp-rtk diff --preset gitlab --tool list_merge_requests
cat response.json | mcp-rtk diff --config custom.toml --tool get_merge_request

Safety

Responses are filtered, never blocked. A few guardrails keep things predictable:

  • 10 MB response cap: oversized responses get truncated before parsing
  • 128-level recursion limit, matching serde_json's default
  • String truncation respects UTF-8 character boundaries
  • Non-JSON responses pass through with only string truncation applied
  • Images and resources are forwarded unchanged

License

MIT

Dependencies

~38–55MB
~778K SLoC