9Router MCP Server
9Router के लिए MCP सर्वर — वेब खोज, वेब फ़ेच, TTS, और अन्य क्षमताओं को मूल टूल के रूप में उजागर करता है, जिससे स्किल-फ़ाइल-आधारित दृष्टिकोणों (निर्देशों को फिर से पढ़ना, मैन्युअल फ़ॉर्मेटिंग, त्रुटि पर पुनः प्रयास) का प्रति-कॉल ओवरहेड समाप्त होता है, और इसके बजाय stdio पर संरचित टूल कॉल का उपयोग होता है।
दस्तावेज़
9Router MCP Server
MCP server that exposes 9Router capabilities as native tools for any MCP client: model discovery, automatic fallback, and Zod-validated inputs.
Status: 9Router already hides provider-specific complexity behind a single API. This server exposes that API through MCP, so agents and apps can call web search, web fetch, image generation, TTS, STT, and embeddings as standard MCP tools — no skill-file loading, no per-provider glue code.
Why use it
- Tools are always registered; no manual skill loading (saves tokens and context).
- Automatic model fallback when the primary model fails.
- Zod-validated inputs with clear error messages before any network call.
- One config file for endpoint, auth, and default models with fallback chains.
- Single transport (
stdio); works with any MCP-capable client.
Chat / code generation is intentionally not included — that is what the host model is for.
Contents
Installation
The package is ninerouter-mcp on npm. The JSON body is identical across every MCP client — only the top-level key and file path differ. Paste the block into the right key in your client's config:
{
"ninerouter": {
"type": "stdio",
"command": "npx",
"args": ["-y", "ninerouter-mcp"],
"env": {
"NINEROUTER_URL": "http://localhost:20128"
}
}
}
Top-level keys by client: VS Code (servers), OpenCode (mcp, rename env → environment, set type: "local" and enabled: true), Claude Code / Cursor / Windsurf / Claude Desktop / Zed (mcpServers or context_servers, drop the type line). JetBrains uses a UI dialog at Settings → Tools → AI Assistant → MCP with the same command, args, and env.
Claude Code
claude mcp add --scope user ninerouter -e NINEROUTER_URL=http://localhost:20128 -- npx -y ninerouter-mcp
Codex CLI
codex mcp add ninerouter --env NINEROUTER_URL=http://localhost:20128 -- npx -y ninerouter-mcp
Hermes Agent
Add to ~/.hermes/config.yaml:
mcp_servers:
ninerouter:
command: npx
args:
- -y
- ninerouter-mcp
env:
NINEROUTER_URL: http://localhost:20128
Configuration
The server needs a 9Router base URL. Optionally it accepts an API key and default-model fallbacks for each tool.
Sources, in priority order (highest first):
- CLI flag
--config <path>(or--config-file,-c,NINEROUTER_CONFIGenv) ~/.config/ninerouter-mcp/config.toml- Environment variables:
NINEROUTER_URL,NINEROUTER_KEY
If the config file exists, it wins over environment variables.
Quick setup
Generate a starter config file and edit it:
npx ninerouter-mcp create-config
This writes ~/.config/ninerouter-mcp/config.toml and refuses to overwrite an existing file.
Manual setup (env vars)
Windows (PowerShell):
$env:NINEROUTER_URL = "http://localhost:20128"
$env:NINEROUTER_KEY = "sk-..." # optional
macOS / Linux:
export NINEROUTER_URL=http://localhost:20128
export NINEROUTER_KEY=sk-... # optional
NINEROUTER_KEY is optional and only required when your 9Router instance has auth enabled.
Manual setup (config file)
# 9Router base URL (required)
base_url = "http://localhost:20128"
# Optional API key
# api_key = "sk-..."
# Default models with fallback support.
# Single string = one model.
# Array = tried in order, first success wins, errors are aggregated.
[default_models]
web_search = ["tavily/search", "brave-search/search"]
web_fetch = ["firecrawl/fetch", "jina-reader/fetch"]
generate_image = "openai/dall-e-3"
text_to_speech = "openai/tts-1"
speech_to_text = ["openai/whisper-1", "groq/whisper-large-v3-turbo"]
embeddings = "openai/text-embedding-3-small"
# Defaults for the list_models tool (all optional; these are the built-in values)
[list_models]
limit = 20 # default page size
offset = 0 # default start index
cache_ttl_seconds = 60 # 0 disables caching
The ninerouter table is also accepted as an alias for top-level base_url and api_key:
[ninerouter]
base_url = "http://localhost:20128"
api_key = "sk-..."
Point to a non-default config file:
npx -y ninerouter-mcp --config /path/to/config.toml
# or
NINEROUTER_CONFIG=/path/to/config.toml npx -y ninerouter-mcp
Run
After npm install from this repo:
npm run build
npm start
Or in watch mode during development:
npm run dev
Published package users can run it directly:
npx -y ninerouter-mcp
Tools
Every tool is registered with the MCP server at startup. Unless noted, all tools return a single text content block with pretty-printed JSON.
list_models
Look up model ids exposed by 9Router. This tool is optional: every other tool works without it, because each has a configured default model or fallback chain. Call it only to target a specific model or to discover ids that differ from the defaults. It filters by search, then pages through the matches with offset/limit, so a large catalog cannot flood the context window. Each response reports total, count, offset, and nextOffset (omitted on the last page). The upstream list is cached for about a minute, because 9Router rebuilds the whole catalog on every request (several seconds), so only the first call is slow.
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | string | no | One of chat, image, tts, embedding, web, stt, image-to-text. Omit for default chat models. |
search | string | no | Case-insensitive substring match against model ids/names. |
limit | number | no | Page size (default 20, max 500). Use 0 for the whole list. |
offset | number | no | Index of the first model to return (default 0). Use nextOffset to page. |
refresh | boolean | no | Bypass the cache and refetch from 9Router. |
Defaults can be overridden in config.toml:
[list_models]
limit = 20 # default page size
offset = 0 # default start index
cache_ttl_seconds = 60 # 0 disables caching
Built-in defaults (used when a key is omitted): limit = 20, offset = 0, cache_ttl_seconds = 60.
web_search
Search the web through 9Router.
| Parameter | Type | Default | Notes |
|---|---|---|---|
query | string | — | Required. |
model | string | — | 9Router model id (e.g. tavily/search). Falls back to provider, then default_models.web_search. |
provider | string | — | Alias for model. |
maxResults | number | 5 | 1–20. |
searchType | string | web | web or news. |
country | string | — | |
language | string | — | |
timeRange | string | — | |
domainFilter | string | — |
web_fetch
Fetch a URL and return it as markdown, text, or HTML.
| Parameter | Type | Default | Notes |
|---|---|---|---|
url | string | — | Required. Must be a valid URL. |
model | string | — | e.g. jina-reader/fetch, firecrawl/fetch. |
provider | string | — | Alias for model. |
format | string | markdown | markdown, text, or html. |
maxCharacters | number | 8000 | Truncation limit. |
generate_image
Text-to-image generation. Always writes a file and returns the image as a base64 content block plus { outputPath, bytes, contentType }. Omit outputPath to write to the OS temp dir.
| Parameter | Type | Default | Notes |
|---|---|---|---|
prompt | string | — | Required. |
model | string | — | e.g. openai/dall-e-3, gemini/gemini-3-pro-image-preview. |
provider | string | — | Alias for model. |
n | number | 1 | 1–10. |
size | string | 1024x1024 | |
quality | string | — | standard or hd. |
outputPath | string | OS tmpdir | Where to write the file. Default os.tmpdir()/ninerouter-<slug>-<ts>.<ext>. Ext from content-type. |
text_to_speech
Synthesize audio. Always writes a file and returns the audio as a base64 content block plus { outputPath, bytes, contentType }. Omit outputPath to write to the OS temp dir.
| Parameter | Type | Default | Notes |
|---|---|---|---|
input | string | — | Required. |
model | string | — | e.g. openai/tts-1, edge-tts/vi-VN-HoaiMyNeural. |
provider | string | — | Alias for model. |
outputPath | string | OS tmpdir | Where to write the file. Default os.tmpdir()/ninerouter-<slug>-<ts>.<ext>. Ext from content-type. |
speech_to_text
Transcribe audio. Provide exactly one of audioPath or audioBase64.
| Parameter | Type | Default | Notes |
|---|---|---|---|
audioPath | string | — | Local file path. |
audioBase64 | string | — | Base64 payload. |
fileName | string | derived | Used for the multipart upload filename. |
model | string | — | e.g. openai/whisper-1, groq/whisper-large-v3-turbo. |
provider | string | — | Alias for model. |
language | string | — | ISO-639-1 code, e.g. en, vi. |
prompt | string | — | |
responseFormat | string | json | json, text, verbose_json, srt, vtt. |
temperature | number | — | 0–1. |
embeddings
Generate embeddings for a string or a batch of strings.
| Parameter | Type | Default | Notes |
|---|---|---|---|
input | string | array | — | Required. Either one string or an array of non-empty strings. |
model | string | — | e.g. openai/text-embedding-3-small. |
provider | string | — | Alias for model. |
encodingFormat | string | float | float or base64. |
dimensions | number | — | Optional override. |
Behavior notes
- Fallback chain. For every model-using tool: if
model/provideris set, only that model is tried. Otherwise the chain indefault_models.<tool>is tried in order. If all entries fail, the tool throws anAll models failed. Errors: ...error that includes every per-model message. provideris an alias formodelon every tool that accepts a model. Set whichever reads better for your use case.- Image and audio are always written to a file and returned as a content block.
generate_imageandtext_to_speechrequestb64_json/mp3from upstream, write the bytes tooutputPath(or the OS temp dir if you omit it), and return the asset as an MCPimage/audiocontent block plus{ outputPath, bytes, contentType }. The host can display inline or just use the path. Extension is derived from upstreamcontent-type. - Config file wins over env vars. If you need different settings for a single run, prefer
--configover exporting env vars. - STT multipart upload. The tool sends the audio as
multipart/form-data;fileNameonly matters when the upstream provider inspects the filename. list_modelsis trimmed and cached. The tool caps its output (default20) and keeps the full upstream list in memory for about a minute. This is not server-side pagination:/v1/modelsignores query params and always returns the whole catalog. The cap protects the context window, and the cache protects latency, since 9Router rebuilds the catalog on every request (observed ~7s, even for/v1/models/<kind>, which returns a tiny body). Passrefresh: trueto force a refetch.- Config is read once at startup. Edit
config.tomlor changeNINEROUTER_URL/NINEROUTER_KEY, then restart the MCP server in your client. Hot-reload is not implemented.
Troubleshooting
NINEROUTER_URL is required— set the env var or create~/.config/ninerouter-mcp/config.tomlwithbase_url.No model specified and no default_models.<tool> configured— either passmodelin the call or add adefault_modelsentry to your config.All models failed. Errors: ...— every fallback model returned an error; the aggregated message includes each one for diagnosis.- Auth errors (401/403) — your 9Router instance requires a key; set
NINEROUTER_KEYorapi_keyin the config file. - STT fails with "Provide audioPath or audioBase64" — exactly one of those two must be set.
Development
npm install
npm run dev # tsx watch mode
npm run build # tsc -> dist/
npm start # node dist/index.js
npm run check # typecheck + lint + prettier --check
Project layout:
src/
index.ts # bin entry; dispatches create-config or server
server.ts # McpServer setup
ninerouter-client.ts # config + HTTP helpers
create-config.ts # `ninerouter-mcp create-config` subcommand
tools/
models.ts # list_models
web.ts # web_search, web_fetch
media.ts # generate_image, text_to_speech, speech_to_text
embeddings.ts # embeddings
common.ts # shared fallback + json helpers
config.example.toml # sample config (mirrors create-config output)
License
Apache-2.0. See LICENSE.