mcp-model-router
by swarnim-git3
README.md
# MCP Model Router
An MCP server that routes each prompt to whichever Claude model best fits it.
The routing decision itself is **100% local** — plain Python, no LLM call. Only
one model call happens per request: the one that actually answers it. That
matters, because the obvious way to build this (ask a model to classify the
prompt first) doubles the number of calls and charges you for the classifier on
every single request.
## The tool
The server exposes one tool over MCP:
```python
smart_prompt(prompt: str) -> str
```
Any MCP-capable client can discover and call it. The response comes back with
the routing decision attached, so you can always audit the choice:
```
[routed to opus (claude-opus-5) — matched opus signals (score=2)]
<the answer>
```
## How routing works
`choose_model(prompt)` in `router.py` returns `(tier, reason)`. In order:
1. **Force tags.** A prompt starting with `!opus`, `!sonnet`, `!haiku` or
`!fable` bypasses routing entirely.
2. **Signal scoring.** Four buckets are scored against the prompt:
- code patterns (fenced blocks, `def`, stack traces, `.py`/`.js`/`.tsx`,
regex, unit test) → coding tier
- reasoning keywords (why, analyze, trade-off, architecture, root cause,
pros and cons) → deepest tier
- creative keywords (write a story, poem, screenplay, lyrics) → narrative tier
- extraction keywords (extract, classify, translate, one sentence, tl;dr)
→ fastest tier
3. **Complexity escalation.** Question marks, bullet lines and numbered list
items are summed; three or more structural signals adds a point to the
deep-reasoning tier.
4. **Length fallback.** Only if nothing scored: ≤12 words → fastest tier,
≥150 words → deepest tier, otherwise the balanced generalist.
Keyword signals deliberately **outrank** the length heuristic. Without that
precedence, a short prompt like "write a short story" would let the word-count
bonus outvote the creative-keyword match it should lose to.
## Tuning it
Every decision is appended to `logs/routing_log.csv`:
| timestamp | tier | reason | prompt_excerpt |
| --- | --- | --- | --- |
The keyword lists in `router.py` are a starting point, not a final answer.
Watch the log fill up and edit them directly. After any change, re-run the
offline smoke test — it asserts expected routing on seven representative
prompts and costs nothing, because it never touches the API:
```bash
python test_router.py
```
## Setup
```bash
python -m venv .venv
.venv\Scripts\activate # Windows
pip install -r requirements.txt
cp .env.example .env # then put your real key in it
```
`.env` is gitignored — the key never leaves your machine.
Register the server with your MCP client via `.mcp.json`:
```json
{
"mcpServers": {
"model-router": {
"command": "<path>\\.venv\\Scripts\\python.exe",
"args": ["<path>\\server.py"]
}
}
}
```
Adjust both paths to wherever you cloned this.
## Files
| File | Role |
| --- | --- |
| `server.py` | MCP server, tool definition, model execution, CSV logging |
| `router.py` | The classifier: model registry, force tags, signal scoring, length fallback |
| `test_router.py` | Offline smoke test — no API calls, no cost |
| `.mcp.json` | Client registration |
## Requirements
`mcp` · `anthropic` · `python-dotenv`
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues