9Router MCP Server

MCP server cho 9Router — cung cấp tìm kiếm web, tải web, TTS và các khả năng khác dưới dạng công cụ gốc, loại bỏ chi phí mỗi lần gọi của phương pháp dựa trên tệp kỹ năng (đọc lại hướng dẫn, định dạng thủ công, thử lại khi lỗi) để chuyển sang các lệnh gọi công cụ có cấu trúc qua stdio.

Tài liệu

9Router MCP Server

CI npm version Node

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):

  1. CLI flag --config <path> (or --config-file, -c, NINEROUTER_CONFIG env)
  2. ~/.config/ninerouter-mcp/config.toml
  3. 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.

ParameterTypeRequiredDescription
kindstringnoOne of chat, image, tts, embedding, web, stt, image-to-text. Omit for default chat models.
searchstringnoCase-insensitive substring match against model ids/names.
limitnumbernoPage size (default 20, max 500). Use 0 for the whole list.
offsetnumbernoIndex of the first model to return (default 0). Use nextOffset to page.
refreshbooleannoBypass 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.

ParameterTypeDefaultNotes
querystring—Required.
modelstring—9Router model id (e.g. tavily/search). Falls back to provider, then default_models.web_search.
providerstring—Alias for model.
maxResultsnumber51–20.
searchTypestringwebweb or news.
countrystring—
languagestring—
timeRangestring—
domainFilterstring—

web_fetch

Fetch a URL and return it as markdown, text, or HTML.

ParameterTypeDefaultNotes
urlstring—Required. Must be a valid URL.
modelstring—e.g. jina-reader/fetch, firecrawl/fetch.
providerstring—Alias for model.
formatstringmarkdownmarkdown, text, or html.
maxCharactersnumber8000Truncation 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.

ParameterTypeDefaultNotes
promptstring—Required.
modelstring—e.g. openai/dall-e-3, gemini/gemini-3-pro-image-preview.
providerstring—Alias for model.
nnumber11–10.
sizestring1024x1024
qualitystring—standard or hd.
outputPathstringOS tmpdirWhere 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.

ParameterTypeDefaultNotes
inputstring—Required.
modelstring—e.g. openai/tts-1, edge-tts/vi-VN-HoaiMyNeural.
providerstring—Alias for model.
outputPathstringOS tmpdirWhere 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.

ParameterTypeDefaultNotes
audioPathstring—Local file path.
audioBase64string—Base64 payload.
fileNamestringderivedUsed for the multipart upload filename.
modelstring—e.g. openai/whisper-1, groq/whisper-large-v3-turbo.
providerstring—Alias for model.
languagestring—ISO-639-1 code, e.g. en, vi.
promptstring—
responseFormatstringjsonjson, text, verbose_json, srt, vtt.
temperaturenumber—0–1.

embeddings

Generate embeddings for a string or a batch of strings.

ParameterTypeDefaultNotes
inputstring | array—Required. Either one string or an array of non-empty strings.
modelstring—e.g. openai/text-embedding-3-small.
providerstring—Alias for model.
encodingFormatstringfloatfloat or base64.
dimensionsnumber—Optional override.

Behavior notes

  • Fallback chain. For every model-using tool: if model/provider is set, only that model is tried. Otherwise the chain in default_models.<tool> is tried in order. If all entries fail, the tool throws an All models failed. Errors: ... error that includes every per-model message.
  • provider is an alias for model on 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_image and text_to_speech request b64_json / mp3 from upstream, write the bytes to outputPath (or the OS temp dir if you omit it), and return the asset as an MCP image / audio content block plus { outputPath, bytes, contentType }. The host can display inline or just use the path. Extension is derived from upstream content-type.
  • Config file wins over env vars. If you need different settings for a single run, prefer --config over exporting env vars.
  • STT multipart upload. The tool sends the audio as multipart/form-data; fileName only matters when the upstream provider inspects the filename.
  • list_models is trimmed and cached. The tool caps its output (default 20) and keeps the full upstream list in memory for about a minute. This is not server-side pagination: /v1/models ignores 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). Pass refresh: true to force a refetch.
  • Config is read once at startup. Edit config.toml or change NINEROUTER_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.toml with base_url.
  • No model specified and no default_models.<tool> configured — either pass model in the call or add a default_models entry 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_KEY or api_key in 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.