A token optimization proxy that filters tool definitions before they reach the LLM provider. Sits transparently between any Anthropic-compatible client and any provider.
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Claude Code │ │ Dynamic Tool │ │ Your │
│ Cursor │────>│ Loader │────>│ Provider │
│ Windsurf │ │ localhost:8090 │ │ │
│ Cline │ │ │ │ Anthropic │
│ │ │ classify task │ │ OpenCode Go │
│ sends 27 │ │ filter tools │ │ Bedrock │
│ tool defs │ │ 27 -> 6 │ │ Vertex │
│ │ │ │ │ │
│<────────────│ │ pass response │<────│ │
│ │ │ through │ │ │
└─────────────┘ └──────────────────┘ └──────────────┘
Claude Code (and similar tools) send 27+ tool definitions with every API request. Most tasks only need 4-6 of those tools. This proxy classifies your task and strips the irrelevant tools before forwarding.
Client (27 tools) ──> Dynamic Tool Loader ──> Provider (6 tools)
│
classify + filter
~12k tokens saved per request
WITHOUT proxy:
27 tools ████████████████████████████████ ~1,200 tokens
model parses all 27, picks 1
WITH proxy:
6 tools ██████████░░░░░░░░░░░░░░░░░░░░░░ ~300 tokens
model parses 6, picks same 1
User: "Fix the nav labels in pricing.html"
┌──────────────────────────────────────────────────┐
│ CLASSIFICATION │
│ │
│ context = "Fix the nav labels in pricing.html" │
│ │
│ regex "fix" -> file_edit: +2 │
│ regex "change" -> file_edit: +1 │
│ regex "html" -> file_edit: +1 │
│ │
│ category = file_edit (score: 4) │
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ FILTERING │
│ │
│ 27 tools -> intersection(file_edit, always) │
│ │
│ KEEP: Bash, Read, Write, Edit, Grep, Glob │
│ DROP: 21 others (GitHub, IDE, Context7, etc.) │
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ RESULT │
│ │
│ Input: 2,310 tokens (full set) │
│ -> 849 tokens (filtered) │
│ │
│ Saved: 1,461 tokens (63%) │
│ │
│ Model picks: Read("pricing.html") │
│ Same result with or without filtering. │
└──────────────────────────────────────────────────┘
27 tools: ████████████████████████████████░░░░░░░░░░░░ ~3.1s
6 tools: ████████████████████░░░░░░░░░░░░░░░░░░░░░░░░ ~1.7s
─────────────────────────────────────────────
0s 5s
Both produce identical SSE streams. The only difference is time-to-first-token.
event: message_start
data: {"type":"message_start","message":{...}}
event: content_block_start
data: {"type":"tool_use","name":"Read"}
event: content_block_delta
data: {"type":"input_json_delta","partial_json":...}
event: message_stop
- Intercepts the API request from your client
- Reads the last 3 messages for context
- Classifies the task via regex keyword matching
- Filters tool definitions (27 -> 6)
- Forwards the lean request to your provider
- Passes through the response unchanged
┌────────────┬──────────────────────────────────────────┐
│ Category │ Tools Sent │
├────────────┼──────────────────────────────────────────┤
│ file_edit │ Read, Edit, Grep, Glob, Bash, Write │
│ search │ Read, Grep, Glob, ListDirectory │
│ git │ Bash, Read, Grep, Glob │
│ debug │ Read, Grep, Bash, Glob │
│ web │ WebFetch, WebSearch, Read │
│ arch │ Read, Grep, Glob, Bash, Agent │
│ full │ All tools (fallback for ambiguous msgs) │
└────────────┴──────────────────────────────────────────┘
Ambiguous messages fall back to full (all tools) — no quality loss.
| Client | Supported | Notes |
|---|---|---|
| Claude Code | ✅ | Primary target |
| Cursor | ✅ | Same Anthropic format |
| Windsurf | ✅ | Same Anthropic format |
| Cline/Roo Code | ✅ | Same Anthropic format |
| OpenCode | ✅ | Same Anthropic format |
| Any Anthropic-format client | ✅ | Tool definitions in request body |
| Provider | Supported | Notes |
|---|---|---|
| Anthropic API | ✅ | Direct |
| OpenCode Go | ✅ | Confirmed working |
| AWS Bedrock | ✅ | Same format |
| Google Vertex | ✅ | Same format |
| Any OpenAI-compatible | ✅ | Via format translation proxy |
| Routatic | ✅ | Your existing setup |
You don't need routatic to use this. Point it at any provider.
# Install
cd dynamic-tool-proxy
pip install fastapi httpx uvicorn
# Start the proxy (defaults to forwarding to localhost:3456)
python3 standalone_proxy.py
# Point your client at it
ANTHROPIC_BASE_URL=http://localhost:8090 claude# Edit standalone_proxy.py, set:
ROUTATIC_URL = "https://api.anthropic.com"
# Then use your normal API key
ANTHROPIC_API_KEY=sk-ant-... ANTHROPIC_BASE_URL=http://localhost:8090 claudeRTK and Dynamic Tool Loader solve different problems and are complementary:
┌──────────────────────────────────────────────────────────────┐
│ │
│ RTK Dynamic Tool Loader │
│ Filters tool OUTPUTS Filters tool DEFINITIONS │
│ (command results) (schemas sent to model) │
│ │
│ After tool runs Before request sends │
│ │
│ git status: 27 tools -> 6 tools │
│ 2000 -> 200 tokens 16k -> 4k tokens │
│ │
│ Use both for maximum savings. │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Assuming ~12,000 tokens saved per request: │
│ │
│ Requests/day Tokens saved/day Monthly cost saved │
│ ───────────── ───────────────── ───────────────────── │
│ 10 120,000 ~$10.80 │
│ 50 600,000 ~$54.00 │
│ 100 1,200,000 ~$108.00 │
│ 500 6,000,000 ~$540.00 │
│ 1,000 12,000,000 ~$1,080.00 │
│ │
│ (At $30/MTok input pricing) │
└──────────────────────────────────────────────────────────────┘
- 20 real-world scenarios: 90% classification accuracy
- OpenCode Go: Confirmed accepting filtered tool sets
- Token savings: ~12k tokens per request (75% on tool schemas)
- Safe fallback: Ambiguous messages get all tools (no quality loss)
dynamic-tool-proxy/
├── tool_map.py # Task classifier and tool mapping
├── standalone_proxy.py # Runnable proxy (port 8090)
├── proxy.py # Custom proxy for other setups
├── test_comprehensive.py # 20-scenario test suite
├── test_proxy.py # Classification tests
├── test_live.py # Provider compatibility test
├── demo_logs.py # Visual demo with proxy logs
├── demo_slow.py # Slow-motion request path demo
└── README.md # This file
Add categories in tool_map.py:
"my_category": {
"tools": ["Read", "Grep", "Bash"],
"keywords": [r"\b(my|pattern)\b"],
},Adjust always-include tools:
ALWAYS_INCLUDE = {"Read", "Write", "Edit", "Bash", "Grep", "Glob"}- Multi-intent messages: "fix bug AND search github" -> picks first match
- No history awareness: Each request classified independently
- Regex-based: Could use LLM classification for edge cases (adds latency)