dxt-openrouter-router
# DXT OpenRouter Router
[](https://www.npmjs.com/package/dxt-openrouter-router)
[](./LICENSE)
A tiny MCP (stdio) server that returns a **ready-to-send OpenRouter request body** from a named preset and your current **energy budget**:
| `energy` | Model slug becomes | Meaning |
|---|---|---|
| `low` | `…:floor` | cheapest provider serving that model |
| `balanced` | `…` (base slug) | the model's default provider |
| `high` | `…:nitro` | highest-throughput provider |
Two things make this different from a normal cost router:
1. **It routes on energy, not just cost.** The input is a fact about *you*, not about the task. "Cheap" and "fast" are the same axis viewed from different energy levels.
2. **Zero Data Retention is the default.** Every body ships `provider.data_collection: "deny"`, so a preset has to *explicitly* opt out of privacy rather than opt in to it.
It **builds** the request. It does **not** send it — so it never needs your API key in-process, and never sees a response.
---
## Install
```bash
npm install -g dxt-openrouter-router
```
Or run it without installing:
```bash
npx dxt-openrouter-router
```
## Register it with an MCP host
Claude Desktop (`claude_desktop_config.json`) or any MCP client:
```json
{
"mcpServers": {
"openrouter-router": {
"command": "npx",
"args": ["-y", "dxt-openrouter-router"],
"env": {
"ROUTING_PRESETS_PATH": "C:\\path\\to\\routing.presets.json"
}
}
}
}
```
`ROUTING_PRESETS_PATH` is optional — omit it and the bundled `routing.presets.json` is used. The file is re-read on **every** call, so you can edit presets without restarting the host.
## The `routeLLM` tool
| Argument | Required | Description |
|---|---|---|
| `preset` | ✅ | A key from `routing.presets.json`, e.g. `research_long_context` |
| `energy` | | `low` \| `balanced` \| `high` (default `balanced`) |
| `user_prompt` | | The user message to place in the body |
| `system` | | Overrides the preset's own system prompt |
| `overrides` | | Extra body fields merged in last, e.g. `{ "temperature": 0.2 }` |
### Example
```jsonc
// call
{ "preset": "research_long_context", "energy": "low", "user_prompt": "Synthesize these sources..." }
```
```jsonc
// result
{
"url": "https://openrouter.ai/api/v1/chat/completions",
"method": "POST",
"headers": {
"Authorization": "Bearer $OPENROUTER_API_KEY", // literal placeholder — never your real key
"Content-Type": "application/json"
},
"api_key_configured": true,
"body": {
"temperature": 0.2,
"model": "meta-llama/llama-3.1-70b-instruct:floor",
"messages": [
{ "role": "system", "content": "You are a careful research synthesist. ..." },
{ "role": "user", "content": "Synthesize these sources..." }
],
"provider": { "data_collection": "deny" }
}
}
```
## Presets
`routing.presets.json` is a plain map of name → preset:
```jsonc
{
"presets": {
"daily_driver": {
"model": "google/gemini-3.6-flash", // no :floor/:nitro here — energy adds that
"description": "Default workhorse — unit tests, refactors, docs, CLI loops.",
"system": "Optional default system prompt.",
"provider": { "data_collection": "deny" }, // merged over the ZDR default
"response_format": { "type": "json_object" }, // passed straight through
"params": { "temperature": 0.4 } // any other OpenRouter body field
}
}
}
```
Bundled presets mirror a simple decision tree:
| Preset | Model | Reach for it when |
|---|---|---|
| `quick_ping` | `x-ai/grok-4.5` | formats, clarifications, vibe checks |
| `daily_driver` | `google/gemini-3.6-flash` | ~80% of volume — tests, refactors, docs |
| `logic_engine` | `openai/gpt-5.6-sol` | math, symbolic logic, formal reasoning |
| `high_reasoning` | `anthropic/claude-opus-4.8` | architecture, nuanced review, agentic work |
| `research_long_context` | `meta-llama/llama-3.1-70b-instruct` | long-context synthesis across sources |
| `structured_json` | `google/gemini-3.6-flash` | extraction that must return parseable JSON |
Slugs were verified against `https://openrouter.ai/api/v1/models` on **2026-07-22**. OpenRouter slugs change as models ship — re-check before relying on them.
## Secrets
🔒 This package contains **no API key**, and the tool output **never** includes one.
- `OPENROUTER_API_KEY` is read from the environment, and only ever reported as the boolean `api_key_configured`.
- The `Authorization` header is emitted as the literal string `Bearer $OPENROUTER_API_KEY`, so tool output is safe to paste into a chat log, an issue, or a commit.
- Copy `.env.example` → `.env` for local use. `.env` is gitignored.
## Use as a library
The pure core is exported, so you can build bodies without MCP:
```js
import { buildRequestBody, loadPresets } from "dxt-openrouter-router";
const presets = loadPresets("./routing.presets.json");
const body = buildRequestBody(presets, { preset: "daily_driver", energy: "low" });
```
## Develop
```bash
npm install
npm run build # tsc -> dist/
npm test # node --test test/
```
## License
MIT © Sasha Philius
TDQS
Scored across 1 tool
There is only one tool, so there is no possibility of ambiguity or overlap with other tools. The purpose of routeLLM is clearly defined and distinct.
With only a single tool, there is no pattern to evaluate for consistency. The name routeLLM uses a camelCase convention mixing a verb and a domain noun, which is readable though generic.
A single tool feels thin for a server named 'openrouter-router.' The router domain likely requires at least companion operations (e.g., send, list presets, get budget) to be practically useful on its own, so one tool is under-scoped.
The tool only builds a request body and explicitly does not send the request. For routing purposes, there is a notable dead end—no send capability, no way to list/manage presets, and no health or configuration operations. The surface is significantly incomplete for the stated routing purpose.