Skip to content
plugmcp
DirectorySkillsCategoriesBlog
EN/中文
Sign inSign up
Home/Servers/Autorouter

Autorouter

I
by io.github.Webb-Ventures

An MCP capability router: one search tool instead of every server's tool schema.

Power your agent

Add this server

The publisher's own command. Not a PlugMCP endpoint.

Local command
npx -y [email protected]
Credentials from the publisher
  • AUTOROUTER_CONFIG — Path to autorouter.json. Defaults to the standard lookup chain (./.autorouter.json, ~/.config/autorouter/config.json, …).
  • AUTOROUTER_IMPORT — Comma-separated harness configs to import: claude, codex, cursor, vscode, plugins.
  • AUTOROUTER_SELECTOR_MODE — Reranker backend: auto | sampling | cli | api | off.
  • AUTOROUTER_SELECTOR_MODEL — Model the selector reranks with, e.g. haiku.
Provider
Claude Code
AUTOROUTER_CONFIG=<value> AUTOROUTER_IMPORT=<value> AUTOROUTER_SELECTOR_MODE=<value> AUTOROUTER_SELECTOR_MODEL=<value> claude mcp add io.github.webb-ventures-autorouter -- npx -y [email protected]

Run in a terminal.

Claude Desktop
{
  "mcpServers": {
    "io.github.webb-ventures-autorouter": {
      "command": "npx",
      "args": [
        "-y",
        "[email protected]"
      ],
      "env": {
        "AUTOROUTER_CONFIG": "<value>",
        "AUTOROUTER_IMPORT": "<value>",
        "AUTOROUTER_SELECTOR_MODE": "<value>",
        "AUTOROUTER_SELECTOR_MODEL": "<value>"
      }
    }
  }
}

claude_desktop_config.json

Step-by-step: add this in Claude

Remote URL or a command on your machine? How the two installs differ

Run on PlugMCP

Host a copy with our keys and logs.

A new server you create. It is not this listing's official endpoint. Use the publisher install when you want their URL and their credentials.

Launch

Related MCP

View more
  • Firecrawl🔥 Official Firecrawl MCP Server - Adds powerful web scraping and search to Cursor, Claude and any other LLM clients.
  • N
    NotionToken-auth Notion MCP: pages, databases, blocks, comments, files. Runs headless in CI and agents.
  • Context7Context7 Platform -- Up-to-date code documentation for LLMs and AI code editors

Related skills

View all
  • reaReverse engineer anything with agents, from app behavior down to native binaries.
  • rea-changelog-updatePrepare or rewrite REA release changelogs and GitHub release notes from pinned Git history, with verified contributor thanks and Release Please synchronization.
  • rea-tool-designDesign or change REA investigation tools, CLI/MCP contracts, provider capabilities, and Evidence semantics.

Questions

How do I install Autorouter?
Copy the stdio config for Cursor, Claude Desktop, Claude Code, or VS Code on this page. It uses the publisher's install, not a PlugMCP proxy.
Does Autorouter need an API key?
The publisher lists these environment variables: AUTOROUTER_CONFIG, AUTOROUTER_IMPORT, AUTOROUTER_SELECTOR_MODE, AUTOROUTER_SELECTOR_MODEL.
What transport does Autorouter use?
Autorouter runs locally over stdio.
When was Autorouter last checked?
Last checked 2026-10-16. An MCP capability router: one search tool instead of every server's tool schema.

An MCP capability router: one search tool instead of every server's tool schema.

autorouter

One tool instead of every tool.

Every MCP server you register injects its full tool list — names, descriptions, JSON schemas — into the system prompt on every single turn. Five servers and a handful of skills is routinely 20–30k tokens of permanent overhead, paid whether or not the task touches any of them. It also makes tool selection worse: more candidates means more mis-picks.

autorouter is an MCP server that exposes a handful of stable tools and hides everything else behind a search. The model asks for what it needs in natural language, gets back a few ranked candidates with schemas attached, and calls one through the router. A tool it actually uses is then promoted into the real tool list, so the context is spent on what the session needs rather than on what it might.

find_capabilities({ query: "inspect recent deployment errors" })
  → mcp:deployments/get_logs       (tool)
  → mcp:monitoring/search_events   (tool)
  → skill:incident-triage          (skill)

describe_capability({ id: "mcp:deployments/get_logs" })
call_capability({ id: "mcp:deployments/get_logs", arguments: { lines: 50 } })

Install

npm install -g autorouter-mcp        # or: npx autorouter-mcp <command>
autorouter init --target claude      # claude | codex | cursor | vscode
autorouter adopt --target claude     # ← the step that actually saves context

That is a one-time migration of what you already have. New servers do not need it — see Adding an MCP server.

adopt is not optional. Registering the router alongside your existing servers is a net increase: their schemas are still loaded and the router adds four more tools. adopt moves the downstream entries out of the harness config and into the router's own, so the harness loads one server and the router still reaches all of them.

MCP servers are not the whole bill. Skills and plugins load through entirely separate mechanisms — every SKILL.md contributes its name and description to the system prompt, and an enabled plugin contributes its own skills, commands and servers just by being installed. On Claude Code, adopt handles those too, using the only two levers the harness exposes:

whathoweffect
skillsskillOverrides[name] = "user-invocable-only"out of the model's context; /name still works for you
pluginsenabledPlugins[id] = falseplugin's skills, commands and servers all stop loading

The router still finds all of them — it reads ~/.claude/skills and installed_plugins.json directly, which record installation, not what the harness has enabled. That is what makes disabling a plugin a move rather than a deletion.

Two things are refused rather than done quietly. A plugin whose MCP servers the router cannot reach (usually OAuth, where the harness holds the token and the router does not) stays enabled — disabling it would take away a server that currently works. So does a plugin whose servers the router never learned about, which happens if you drop "plugins" from import. Both are reported as skip lines with the reason.

Use --servers-only to keep the old behaviour, --skill-mode off to remove the slash command as well, and --keep-skill / --keep-plugin to exempt individual ones. Codex, Cursor and VS Code have no skill or plugin concept, so there is nothing extra to do there.

Run autorouter doctor to see the difference:

## Context cost
  exposing everything: ~87,524 tokens
  router surface:      ~752 tokens
  best case:           ~86,772 tokens saved (99%)

  per harness (a session only ever runs in one):
    codex   still loaded ~35,500 → saves ~51,272 (59%)
    claude, cursor, vscode: fully adopted → saves ~86,772 (99%)

## Not yet adopted
  codex
    servers: project-tools, docs-search, issue-tracker
    → autorouter adopt --target codex

"Still loaded" is what adoption removes, and it is reported per harness rather than summed: a session runs in exactly one, so a server still registered in Codex costs a Claude Code session nothing. Every removal is backed up verbatim to ~/.autorouter/adopted/ before anything is written; autorouter restore --target claude puts it back byte for byte.

Doctor also reports what costs context but no adopt can move. It only reports and suggests; it never edits these:

  • Hooks: SessionStart hooks whose output lands in context, with the median size measured from your transcripts. A hook with an empty matcher runs again after every compaction, /clear and resume.
  • Stale tool references: lines in ~/.claude/CLAUDE.md, CLAUDE.md or AGENTS.md that name a tool as mcp__<server>__<tool> when that server now sits behind the router. The model looks for a tool that isn't there. The router's own primer block is skipped.
  • Injected servers: servers a launcher passes on the command line (claude --mcp-config ..., as cmux does with cmux-cua). These are in no config file, so adopt can't move them. Doctor flags them when the router imports a copy as well.

What a session actually pays: autorouter audit

Schemas are only part of the bill. Claude Code already defers MCP schemas to names, and every API call re-reads everything still in the window, so the real cost is context size × number of calls. autorouter audit reads Claude Code's transcripts (~/.claude/projects/*/*.jsonl) and uses the usage figures recorded for each call:

## Context bill
  avg context per call   181k
  input processed        4.79B (96% cache reads)
  compactions            269 (median 317k before → 90k after)

## What fills it (tokens × calls each stays in context)
   30.1%   1.44B  startup overhead (system prompt, tools, skills, CLAUDE.md)
   23.2%   1.11B  carried over through compaction (kept turns, re-attached files)
   15.4%  735.5M  Claude's thinking (kept in context)
   13.9%  666.0M  Bash output
   ...

## Compaction window (replay, API-equivalent cost)
  autoCompactWindow: 350000 (~/.claude/settings.json)
     window    change  compactions  avg context
       140k    -26.9%          834         102k
       170k    -25.9%          559         116k  ← recommended
       350k      0.0%          173         196k  ← current

Attribution charges each block of content once for every later call that re-reads it, until the next compaction. The window table replays your own sessions under each autoCompactWindow, calibrated against the compactions in your transcripts. Costs are weighted by API price ratios (cache read ×0.1, cache write ×1.25, output ×5) as a proxy for subscription usage. Read the percentages as relative, not exact. Among windows within 1.5 points of the cheapest, it recommends the largest, because every compaction loses some detail. --days N (default 14, or all) and --project DIR narrow the range; --json prints the full report.

How it finds things

Retrieve, then rerank.

  1. BM25 over every capability, always on, no dependencies. The tokenizer splits camelCase and snake_case (get_user → get, user, getuser) and boosts fields: name ×3, keywords ×2, description ×1, schema ×0.5.
  2. Embeddings, optional. Voyage or OpenAI; scores are min-max normalized and fused 0.5 × bm25 + 0.5 × cosine. An absent or unreachable provider is not an error — it degrades to pure lexical.
  3. A selector model reranks the shortlist. It should be the cheapest model in whichever harness you are using, so init asks which one when it cannot infer it, and stores the answer in that harness's own server entry.

The selector backend

Three ways to reach a model, tried in this order under "mode": "auto":

backendhowwhen it fires
samplingsampling/createMessage back to the hostthe host declares the capability — few do; Claude Code does not
apidirect HTTPS to Anthropic / OpenAI / Ollamaselector.apiKeyEnv names a variable that is actually set
cliclaude -p or codex exec, headlessa harness CLI is on PATH

The CLI backend is the one that usually fires, and it exists because the other two usually cannot. Sampling is the protocol's own answer and almost nothing implements it. The API backend then asks for an ANTHROPIC_API_KEY that a Claude Code subscriber has no reason to own — they logged in, they did not buy a key — so the router would degrade to raw index order while telling them to go purchase access to a model they are already paying for. Meanwhile claude is sitting on PATH, already authenticated. Shelling out to it reuses that login with nothing to configure and no second bill.

What it costs is process startup, which makes a CLI selector slower than a direct HTTPS request. That is why a configured API key still outranks it. Selections are memoized per query and candidate set for the life of the process, so the price is paid once per distinct search, not once per turn.

The harness is stripped back to a reranker. A default headless agent may boot hooks, language servers, plugin sync and project-instruction discovery that a short ranking task does not need. The subprocess therefore runs with --bare --tools "" --setting-sources "" and MAX_THINKING_TOKENS=0 (Codex: --ephemeral -s read-only). The empty tool list is a correctness property before it is a saving: a selector that could edit files would be a different program.

"selector": {
  "mode": "auto",          // auto | sampling | cli | api | off
  "provider": "cli",
  "cliCommand": "claude",  // only needed if the binary is not under its usual name
  "model": "haiku",
  "candidates": 30,
  "maxResults": 8,
  "timeoutMs": 20000       // raised automatically to the backend's own floor
}

It is also started with an empty MCP config (--strict-mcp-config, mcp_servers={}) — inheriting the router's own wiring would load the exact catalog the router exists to keep out of context, and on a bad day recurse into the router itself.

model is passed through only when you set it. Codex rejects model names an account's plan does not carry, so the account default is what it gets otherwise.

mode: "off" skips reranking entirely and returns raw index order, which is fast, free, and noticeably worse.

Downstream servers are not spawned at startup. The catalog is built once and persisted to ~/.cache/autorouter/catalog.json; a server is cold-started only when one of its capabilities is first called.

What it indexes

kindsource
tool, prompt, resourceevery configured MCP server (following nextCursor pagination)
skill**/SKILL.md under your skill paths and plugin skills/ dirs
command, agentplugin commands/*.md and agents/*.md

Plugin commands and agents are also republished as slash commands; skills are searchable but not republished by default, because the prompt list that would carry them is permanent context — see Slash commands.

Harness configs are imported rather than duplicated: ~/.claude.json (global and per-project) and the repo's .mcp.json (see Repo skills and servers), ~/.codex/config.toml, ~/.cursor/mcp.json, ~/.vscode/mcp.json, and installed Claude Code plugins. While the cmux terminal is running, its Computer Use server (cmux-cua) is picked up too, since cmux injects it per session rather than writing it to any config. Servers a harness has switched off (disabled: true, or Codex's enabled = false) are skipped, and a command that would just open a desktop app — rather than start an MCP server — is never launched.

Repo skills and servers

Open Claude Code in a repo, or anywhere inside one, and the router picks up that repo's .mcp.json servers and .claude/skills — scoped to it, so they never show up in another project — and Claude Code stops pre-loading them.

  • Which repo. The host's roots/list (Claude Code answers with its launch directory and any --add-dirs, and announces changes), then CLAUDE_PROJECT_DIR, then the router's own working directory.
  • Which skills. .claude/skills in the working directory and each parent up to the git root, the way Claude Code reads them, plus every nested <subdir>/.claude/skills below the root. Claude Code only loads those once it touches a file in that subdirectory; the router can search a monorepo's per-app skills from the top.
  • How they leave context. Only through the repo's own, private .claude/settings.local.json: disabledMcpjsonServers for the routed servers, skillOverrides: "user-invocable-only" for the skills (so /name still works). If the router creates that file it adds it to .git/info/exclude. Nothing committed is ever edited, and values you set yourself — even "on" — are left alone.
  • Which servers. .mcp.json is checked in, so the router only launches a server from it that Claude Code would run: the folder must be trusted, and the server must be HTTP or approved — in Claude Code (enabledMcpjsonServers), or with autorouter approve <name>, which pins the exact command. Trust and approvals hold for every git worktree of the same repository. Anything unapproved stays with Claude Code, which asks about it as usual.
autorouter adopt --project [dir]     # the same thing by hand (--dry-run to preview)
autorouter restore --project [dir]   # take back exactly what that added
autorouter approve playwright        # let the router launch a .mcp.json stdio server

Set "autoAdoptProject": false to keep this manual.

Upgrading from an earlier version? Before this, adopt emptied a repo's committed .mcp.json and moved its servers into the router's global config. autorouter repair lists what it moved, from the adopt backups; --apply puts the entries back (byte-for-byte when the backup allows), scopes private per-project servers to their project, and adopts each repo the new way. doctor flags it when there is something to repair.

Adding an MCP server

Register it with the router directly — it never enters anyone's context:

autorouter add linear --url https://mcp.linear.app/mcp
autorouter add foo -- npx -y foo-mcp            # stdio, like `claude mcp add`
autorouter add --json '{"mcpServers":{"linear":{"url":"…"}}}'   # paste the vendor snippet
autorouter remove linear

The entry is saved before the connection is checked, because the usual reason a new server does not answer is that it has no OAuth grant yet — and autorouter login <name> needs the entry to exist before it can authorize it.

You can also just use the harness. claude mcp add foo … still works: a running router notices the harness config changed, moves the entry into its own config with the usual backup, and tells you on the next search. So there is no second command to remember, and no window where you have forgotten it and are paying for that server's schemas on every turn. Set "autoAdopt": false to keep adoption manual.

From inside a session, set "allowAddServer": true and ask for it in words — the router then exposes an add_server tool. It is off by default because a tool definition is context on every request and autorouter add covers the same ground. Registering a stdio server means this machine will run that command from then on, so the first call only reports what would be registered; it takes a second call with confirm: true to write anything.

Configuration

autorouter.json — looked up at $AUTOROUTER_CONFIG, ./.autorouter.json, ./autorouter.json, ~/.config/autorouter/config.json, ~/.autorouter.json.

{
  "import": ["claude", "codex", "cursor", "vscode", "plugins", "cmux"],
  "servers": { "custom": { "command": "…", "args": [] } },
  "skillPaths": ["~/.claude/skills", ".claude/skills"],
  "exclude": ["media.generate_video"], // never surfaced at all
  "alwaysExpose": ["code-search.search"], // stays first-class, never adopted
  "confirm": ["database.execute_statement"], // first call is rejected, must re-issue
  "autoAdopt": true, // move servers added to a harness behind the router
  "autoAdoptProject": true, // route the current repo's .mcp.json servers and skills
  "allowAddServer": false, // expose the add_server tool to the model
  "promptMode": "commands", // slash commands: "all" | "commands" | "none"
  "activation": "lazy", // promote to a real tool: "eager" | "lazy" | "off"
  "spillOverTokens": 3000, // larger results are saved, not inlined
  "spillOverrides": { "mcp:linear/*": 8000 }, // per-id thresholds; 0 = never
  "resultsMode": "auto", // "auto" | "file" | "page" | "off"
  "resultsDir": "", // default <cache>/results; never inside the working tree
  "selector": { "mode": "auto" },
  "embeddings": {
    "provider": "voyage",
    "model": "voyage-3-lite",
    "apiKeyEnv": "VOYAGE_API_KEY",
  },
}

Env overrides (AUTOROUTER_IMPORT, AUTOROUTER_SELECTOR_MODEL, AUTOROUTER_SELECTOR_MODE, AUTOROUTER_PROMPT_MODE, AUTOROUTER_ACTIVATION, AUTOROUTER_AUTO_ADOPT, AUTOROUTER_ALLOW_ADD_SERVER, AUTOROUTER_EMBEDDINGS_PROVIDER, AUTOROUTER_RESULTS_MODE, AUTOROUTER_SPILL_OVER_TOKENS, …) let each harness pin its own behaviour without a shared global file. AUTOROUTER_HOME redirects every home-relative path, for tests and containers.

Slash commands

Adopting a plugin into the router would otherwise cost you its slash commands: Claude Code reads commands/*.md off disk, and nothing over MCP can add to that list. What it does surface as slash commands are MCP prompts, so the catalog's plugin commands and subagents are republished as ones:

/mcp__autorouter__find <what you want to do>
/mcp__autorouter__plugin_name_command_name

Skills are not republished by default (promptMode: "commands"). The prompt list is permanent context — the host fetches it once and carries it for the whole session — and skills are the bulk of it: one plugin shipping 141 of them costs ~11.5k tokens on every turn, which is more than adopting that plugin saves. The alias is also mostly redundant, since adopt --skill-mode user-invocable-only leaves a local skill's native /name working and a plugin's skills are surfaced by the harness on demand rather than injected. Skills stay fully searchable either way; set "promptMode": "all" to get /mcp__autorouter__skill_name back, or "none" to publish only find.

Bodies are substituted the way the native loader substitutes them — $ARGUMENTS for everything you typed, $1..$9 positionally — so a command file written for Claude Code behaves identically through the router. argument-hint frontmatter is passed through as the argument description. Names are prefixed with the owning plugin, so two plugins shipping a setup command both stay reachable rather than one silently shadowing the other.

Tools are deliberately not published here. A prompt returns text for the model to act on and cannot return a tool result, so a slash command for database_write would look callable and do nothing. Tools reach the model through search instead.

Harness support

Searching returns a tool with its full description and input schema, not a summary — the same information a native tool listing carries, because a truncated description leaves the model guessing argument names, and a guessed argument is the whole difference between roughly reliable and reliable.

Better still, where the client honours notifications/tools/list_changed, a tool you use is promoted to a real first-class tool. From then on the model makes an ordinary tool call: the host validates arguments against the real schema, the permission prompt names the real tool rather than call_capability, and the router is not in the execution path at all. It becomes a loader that decides what is in your tool list, not a middleman on every call.

Promotion is deferred to the first successful call (activation: "lazy") because a promoted tool is permanent context and a search hit is not evidence of need — most of what a search matches is never called. Set "activation": "eager" to promote every hit as before, or "off" to route everything through call_capability and carry no tool definitions at all.

supports listChangedproxy only
Claude Code ≥ 2.1.232, GitHub Copilot, opencodeCodex, Gemini CLI, Claude Desktop, Vercel AI SDK

Where it is unsupported the schema travels inline in the search result instead, and activate_capabilities (which also unpins, with off: true) is never advertised, so the model is never told about a tool it cannot use. Override the detection with AUTOROUTER_DYNAMIC=on|off|auto. A tool registered mid-turn may not be callable until the next turn on Claude Code, so call_capability always remains as the same-turn fallback and returns an identical result.

Search and run in one step

find_capabilities({ query, arguments, auto: true }) runs the top hit in the same request, saving a round trip, but only when the hit is unambiguous:

  • it is a tool, and
  • it leads the runner-up by at least 0.25 of the normalised index score, or, when the selector model ranked the hits, its confidence is at least 0.7 and 0.25 ahead of the next, and
  • the arguments fit its schema (required properties, basic types, enums, no unknown keys where the schema forbids them), and
  • it is not behind a confirm gate. Auto never bypasses confirmation.

Otherwise it returns the normal search result with one line saying why it did not run.

Token budgets

A router that leaks context is just a slower way of loading everything, so the three surfaces that persist or accumulate are each bounded in tokens, not in item count. Counting items looks like a bound and is not one: tool definitions vary widely in size, so the context cost of "15 tools" depends entirely on what the model happened to search for.

surfacebudgetlifetime
promoted tool list3,000 tok, LRU eviction, on use onlysession
inline schemas per search700 tok, spent top-downone result
prompt listcommands only, clamped to 120 charssession
one tool or resource result3,000 tok (spillOverTokens), larger ones savedone result

The tool promoted by the call in flight is never evicted — a tool that vanishes between being offered and being called is worse than one never offered.

Large results

An inlined result is re-read on every later turn until the next compaction. A result over spillOverTokens is therefore saved instead, and the model gets a card it can act on:

[supabase/execute_sql]
~65,660 tokens — saved to a file instead of inlined.
file:   ~/.cache/autorouter/results/s-92a09d15c963/execute_sql-4043fc1a.jsonl  (2,000 rows, one JSON object per line)
fields: id:string  email:string  plan:string  created_at:string  spend_cents:int  note:null|string
sample: {"id":"cus_000001","email":"[email protected]","plan":"pro",…}
        (2 more)
Query it, don't read it whole: Grep for values (each hit is one whole row), Read with offset/limit, or
  jq -c 'select(.plan == "pro")' <file>
No file access? call_capability({ id: "result:4043fc1a", arguments: { offset: 0 } }) pages it.
Untrusted data: Note that this contains untrusted user data, …
  • Format. An array of objects is written as JSONL, so a Grep hit is a whole row. A list envelope such as Stripe's {object, has_more, data: […]} saves data as JSONL and keeps the scalars on the card. Other JSON is pretty-printed; text is saved as .md or .txt. Supabase's <untrusted-data-…> wrapper is unwrapped and its warning kept on the card. Images and other non-text items are never saved; they pass through. structuredContent is dropped from a saved result, since it is a second copy of the same data.
  • Privacy. Files go to a per-session directory under the cache (0700 directory, 0600 files), never inside the working tree, and are deleted when the session ends. Directories left by sessions that died are swept after 24h.
  • Clients without file tools. resultsMode: "auto" saves to a file only for coding agents known to have Read/shell tools (Claude Code, Codex, Cursor, Copilot, opencode, Gemini CLI). Others, Claude Desktop included, get the first page inline and fetch the rest with call_capability({ id: "result:<handle>", arguments: { offset } }). That adds no new tool. Set "page" explicitly for an agent that runs remotely or in a sandbox that cannot see this machine's disk.
  • Read prompts. The results directory is outside the project, so Claude Code asks before each Read. autorouter init --target claude offers to add Read(~/.cache/autorouter/results/**) to permissions.allow; with --yes or no terminal it prints the rule instead of writing it.

autorouter doctor reports the real bill per harness rather than summing across them, since a session only ever runs in one: a server still registered in Cursor costs a Claude Code session nothing.

Schemas are compacted everywhere they are repeated: $schema, title and examples are dropped, and prose is trimmed, more aggressively the deeper it sits. Every structural field survives untouched — names, types, enums, required, nesting, and additionalProperties: false — because dropping one of those turns a valid call into a guessed one. This reduces repeated schema size without changing what is callable.

describe_capability is the exception and returns the schema verbatim: it exists precisely to recover anything a budgeted result had to leave out.

CLI

For agents that have a shell but no MCP, the same engine is directly usable:

autorouter search "chart a csv"
autorouter search "query a database" --raw --limit 5
autorouter describe skill:dataviz
autorouter call mcp:deployments/get_logs --args '{"lines":50}'
autorouter list --kind skill
autorouter doctor
autorouter audit --days 14                          # the per-call bill from transcripts
autorouter reindex

autorouter add linear --url https://mcp.linear.app/mcp
autorouter add foo -- npx -y foo-mcp
autorouter remove linear

autorouter adopt --target claude --dry-run          # preview, change nothing
autorouter adopt --target claude --servers-only     # skip skills and plugins
autorouter adopt --target claude --keep project-tools --keep-plugin ui-toolkit
autorouter restore --target claude                  # undo the most recent adopt
autorouter adopt --project                          # route this repo's .mcp.json servers and skills
autorouter restore --project                        # take that back
autorouter approve playwright                       # let the router launch a repo stdio server
autorouter repair                                   # what older adopts moved out of repos (--apply)

autorouter update                                   # upgrade in place
autorouter update --check                           # is there a newer version?

autorouter login                                    # which servers need a grant
autorouter login remote-server                      # authorize one (opens a browser)
autorouter login remote-server --device             # headless: enter a code elsewhere
autorouter login remote-server --no-reindex         # authorize, index later
autorouter logout remote-server                     # forget a stored grant

Updating

autorouter update upgrades in place, using the package manager that installed the copy you are running:

autorouter update            # detect, then upgrade
autorouter update --check    # report the available version, install nothing
autorouter update --dry-run  # print the command, do not run it

Which manager to use is decided by where the running file sits on disk, not by what happens to be on PATH — running npm i -g against a pnpm-managed global installs a second copy that shadows the first, and you would then be upgrading one install while running the other.

Three cases do not run a package manager, and say so instead: a copy unpacked by npx/bunx/pnpm dlx (nothing to upgrade — those refetch every run), a git checkout (git pull && bun install && bun run build), and a layout it cannot identify. All three still report whether a newer version exists.

The router is a long-lived stdio server, so a harness that already has it running keeps the old build until it respawns it — restart the harness, or reconnect the MCP server, after updating.

OAuth servers

Some remote MCP servers carry no credentials in their visible configuration. The working token is an OAuth grant obtained by the harness, stored in the harness's credential store, and issued specifically to that harness.

The router does not read it. It runs its own authorization-code flow and holds its own grant, which means it also works under Codex and Cursor, neither of which has a token to borrow:

autorouter login remote-server

A new grant is followed by a reindex automatically, because the capabilities behind it are not searchable until the catalog has seen them — a grant on its own changes nothing the router can reach. --no-reindex skips it when you are authorizing several servers in a row and would rather index once at the end.

Only a login that actually stores a new grant triggers it. --list-scopes and a server that already had a grant both change nothing, so neither pays for an index rebuild.

Registration is RFC 7591 dynamic client registration, so there is no app to create first. Tokens live in ~/.autorouter/oauth/<server>.json at 0600 and are refreshed automatically; logout deletes them. The loopback redirect uses a fixed port (33418, --port or $AUTOROUTER_OAUTH_PORT to change it) because the redirect URI is baked into the registration a provider stores — a grant obtained on one port cannot be refreshed from another.

Headless machines

The browser flow cannot work over SSH: it binds a loopback listener and expects a browser on the same host to redirect into it. Two flows replace it, and on a box with no display autorouter picks one of them by itself rather than timing out waiting for a browser that was never going to open.

autorouter login remote-server --device   # RFC 8628: enter a code on your phone
autorouter login remote-server --manual   # paste the redirect URL back

--device is the better one where the provider offers it. Nothing has to reach back into the machine: it prints a short code and a URL, you enter them in a browser on any other device, and the headless side polls until you are done. Detection is the device_authorization_endpoint in the provider's metadata.

--manual is the fallback for providers that do not implement RFC 8628. It prints the authorization URL, you open it elsewhere, and the browser is then redirected to http://localhost:33418/callback, which will not load — nothing is listening. That is expected: copy the URL out of the address bar and paste it back. Pasting the whole URL is worth preferring over just the code, because the state in it is what proves the code came from the login you started.

Neither flow binds a port. A device login registers a client that also works for a later browser login from the same machine, so nothing has to be redone if the box grows a display. Set AUTOROUTER_ASSUME_HEADLESS=1 to force the detection on a machine where it guesses wrong.

Choosing permissions

A dynamically registered client may default to every scope the provider advertises, including write or administrative permissions. That can leave a search tool holding a token capable of destructive actions. Scopes are the only restriction that survives a prompt injection, so pick them deliberately:

autorouter login remote-server --list-scopes
autorouter login remote-server --read-only
autorouter login remote-server --scopes "projects:read,data:read"

--read-only keeps the scopes whose names do not grant mutation. It is a heuristic over naming conventions (:write, admin, manage, all) and it cannot infer the permissions behind opaque scope names. --scopes is the exact lever when the heuristic cannot tell. Where a provider offers no read-only subset at all, --read-only fails loudly rather than requesting an empty scope, which most providers read as "give the default".

Scopes are fixed when the grant is issued, so narrowing an existing one means --force and a fresh authorization. A re-login inherits the previous narrowing unless you pass a new one, and autorouter login with no argument prints what each stored grant actually covers.

Until a server has a grant, adopt refuses to move it or to disable the plugin that supplies it. Moving a server the router cannot reach would delete a working capability, so doctor and adopt both print the exact login command instead.

The trade-off, stated plainly

Routing means your host's permission prompt sees call_capability, not database.execute_statement. That is a real loss of granularity and this tool does not pretend otherwise. Three mitigations: exclude means a capability is never surfaced; confirm rejects the first attempt with the resolved target spelled out and requires the model to re-issue with confirm: true; and every result is prefixed with the resolved server/tool so the transcript stays auditable.

Adopting skills and plugins edits ~/.claude/settings.json, a file you also edit by hand. The whole file is backed up verbatim alongside the server moves and restore rewrites it byte for byte, but it is a shared file and worth knowing about. Hidden skills default to user-invocable-only rather than off for the same reason: /name keeps working, so the capability is relocated, not removed.

Development

bun install
bun test
bun run src/cli.ts doctor
bun run build          # bun build src/cli.ts --target=node --outfile dist/cli.js

The build targets Node so npx works for people who do not have Bun.

See CONTRIBUTING.md for the invariants worth knowing before changing anything — chiefly that nothing is spawned at startup, that every write adopt makes has to restore byte for byte, and that the selector subprocess runs with no tools by design.

Licence

MIT. Security reports go through SECURITY.md, not the public issue tracker.

How do I install Autorouter?
Copy the stdio config for Cursor, Claude Desktop, Claude Code, or VS Code on this page. It uses the publisher's install, not a PlugMCP proxy.
Does Autorouter need an API key?
The publisher lists these environment variables: AUTOROUTER_CONFIG, AUTOROUTER_IMPORT, AUTOROUTER_SELECTOR_MODE, AUTOROUTER_SELECTOR_MODEL.
What transport does Autorouter use?
Autorouter runs locally over stdio.
When was Autorouter last checked?
Last checked 2026-10-16. An MCP capability router: one search tool instead of every server's tool schema.

No tool list yet. Remote servers get one after the first health check.

plugmcp

Plug any tool into any agent. MCP servers for people who do not write servers. Gateway for volume, n8n for apps.

  • github.com/TODO
  • x.com/TODO
  • linkedin.com/company/TODO

PlugMCP, 2026

Product
  • Browse
  • Install
  • Host your own
  • Pricing
  • Voice mode
  • Browser
Directory
  • All MCP servers
  • MCP Apps
  • Tools
  • Agent skills
  • Categories
  • New
  • Voice
  • Submit a server
Developers
  • Docs
  • Blog
  • Club
  • Work with us
  • What is MCP
  • What is WebMCP
  • Status
Company
  • About
  • Contact
  • Terms
  • Privacy
  • DPA