runspec-mcp — Claude Code across your fleet
runspec-mcp connects Claude Code to the runspec runnables installed across
your machines — local venvs and remote SSH venvs — from one config file. It is
a small local MCP gateway: Claude Code spawns it as a single MCP server; it
discovers the runnables in every configured venv and presents them all as MCP
tools, with each runnable's declared autonomy carried into Claude Code's
permission layer and credentials injected as environment variables.
It is the productised successor to the hand-rolled example at
examples/claude-code-plugin/ — one plugin for the whole fleet instead of one
copy per host, live discovery instead of a hand-run manifest generator, and real
per-call credential injection.
The model
Claude Code ──stdio MCP──▶ runspec-mcp serve (one local process)
▲ tools/list_changed │ reads runspec_mcp.toml (hosts + credential metadata)
└──── (auto re-fetch) ───┤ OS keychain ── secrets
├── local venv: runspec local --json ; exec the runnable
└── remote venv: ssh host runspec local --json ; ssh exec
PreToolUse hook ◀── autonomy.json (rewritten by the gateway on every change)
Discovery uses runspec local --format json in each venv, so the tools are
exactly what runspec serve on that host would expose (subcommands flattened to
leaf tools, x-autonomy carried, password args omitted). Execution mirrors
runspec-console: a discrete SSH exec of the runnable's own venv binary with the
credential environment injected per call.
Install & connect
pip install "runspec-mcp[credentials]"
# write runspec_mcp.toml (see Configuration below), then check it:
runspec-mcp check
# and print the exact client config for your MCP client:
runspec-mcp setup claude # Claude Code: prints `claude mcp add …` (or --apply to run it)
runspec-mcp setup vscode # VS Code / GitHub Copilot: prints the .vscode/mcp.json entry
runspec-mcp setup json # any other client: a plain mcpServers block
Always configure the client with the absolute path setup prints. An MCP
client starts the gateway itself, with its own PATH and working directory. If
you installed runspec-mcp in a venv, a bare runspec-mcp command isn't found,
and the client only reports that the server failed to connect.
runspec-mcp check warns when it isn't on your PATH.
Install the Claude Code plugin
The plugin is the best way to use runspec-mcp in Claude Code. It runs the same
gateway and adds the autonomy hook, so autonomous runnables run without a
prompt, confirm ones ask and manual ones are refused. It also adds the
/runspec-hosts and /runspec-refresh commands. Three ways to install it; pick
the one that fits the machine.
Option 1: from runspec.app, with uv (no pip install)
For a machine that has uv and can reach PyPI. In Claude Code:
/plugin marketplace add https://runspec.app/marketplace.json
/plugin install runspec-mcp@runspec
The plugin starts the gateway with uvx --from runspec-mcp runspec-mcp serve,
so uv fetches runspec-mcp itself — there's nothing to pip install. The plugin
files come from a zip published on runspec.app with the docs, checked against a
SHA-256 in marketplace.json.
Option 2: from runspec.app, with an installed runspec-mcp
For a machine without uv, or one that installs from a private index (Nexus):
install the gateway first, then add the runspec-pip marketplace:
pip install "runspec-mcp[credentials]" # from your usual index
/plugin marketplace add https://runspec.app/marketplace-pip.json
/plugin install runspec-mcp@runspec-pip
This marketplace's plugin source is a command: Claude Code runs
runspec-mcp claude-plugin, which writes a copy of the plugin wired to this
install (the absolute runspec-mcp path, and this install's Python for the
autonomy hook) and prints where. Claude Code shows you that command before it
runs it, and re-runs it once per session, so a pip install -U runspec-mcp is
picked up without reinstalling the plugin. runspec-mcp must be on the PATH
Claude Code starts with; if it isn't, use option 3 or add the venv's bin/
(Scripts\ on Windows) to your PATH.
Option 3: one session, no install
To try the plugin without installing it:
RUNSPEC_MCP_CONFIG_DIR=/path/to/runspec-console \
claude --plugin-dir "$(runspec-mcp claude-plugin)"
runspec-mcp setup claude prints these commands with your paths filled in.
Settings
When you install the plugin (options 1 and 2), Claude Code asks for two settings — fill in one, or neither:
| Setting | Use it for |
|---|---|
| runspec-console config folder | Reuse a runspec-console's hosts and credentials: the console's config folder (%APPDATA%\runspec-console, ~/.config/runspec-console, or a white-label package's own folder). |
| runspec_mcp.toml | The gateway's own config file, when you don't use the console. |
Leave both empty to use runspec_mcp.toml from its default location. A
RUNSPEC_MCP_CONFIG_DIR / RUNSPEC_MCP_CONFIG set in the environment Claude
Code starts with wins over both. Change them later with
/plugin configure runspec-mcp@runspec (or @runspec-pip).
From a shell, set them as you install:
claude plugin marketplace add https://runspec.app/marketplace.json
claude plugin install runspec-mcp@runspec --config config_dir=/path/to/runspec-console
The runspec-pip install shows you the runspec-mcp claude-plugin command and
asks you to confirm it before running it.
Notes
- The autonomy hook needs Python. It runs
python3(option 1) or the install's own Python (options 2 and 3). On Windows, option 1 needs apython3onPATH; if there isn't one, use option 2. If the hook can't run, Claude Code asks for every call — never less safe. - Updates. Each runspec-mcp release republishes the zip with the docs, and
/pluginoffers the update. Option 1 then runs the newest runspec-mcp uv can find; option 2 runs whatever you have installed. - Claude Code only. The plugin starts a local program, so it works in Claude Code (terminal, IDE, and the desktop app's Code tab), not in claude.ai chat or cloud sessions.
- A private GitHub repo isn't a problem. Neither marketplace fetches anything from GitHub: the zip is on runspec.app and option 2 uses your installed package.
/mcp lists the fleet server; tools appear as
mcp__plugin_runspec-mcp_fleet__<name> (host-prefixed when more than one host
is configured).
Connect from any MCP client
The Claude Code plugin is just packaging. Underneath, runspec-mcp serve is a
standard MCP stdio server, so any client that can spawn one works — Claude
Desktop, Cursor, Cline, or Claude Code without the plugin. Add a plain
mcpServers entry:
{
"mcpServers": {
"runspec-mcp": {
"command": "runspec-mcp",
"args": ["serve"],
"env": { "RUNSPEC_MCP_CONFIG": "/absolute/path/to/runspec_mcp.toml" }
}
}
}
Use absolute paths for both command and RUNSPEC_MCP_CONFIG
(runspec-mcp setup json prints them). An MCP client's PATH and working
directory are unpredictable, and the gateway otherwise looks for ./runspec_mcp.toml then its
app-dir default. Drop the block into the client's MCP config (Claude Desktop's
claude_desktop_config.json, Cursor/Cline's MCP settings, or a project
.mcp.json). For Claude Code without the plugin:
claude mcp add runspec-mcp --env RUNSPEC_MCP_CONFIG=/path/to/runspec_mcp.toml -- runspec-mcp serve
What the plugin adds over a bare client — autonomy. The PreToolUse hook that
maps autonomy to allow/ask/deny ships with the Claude Code plugin only.
Through a bare client, autonomous runnables are not auto-approved — the
client's own approve/deny prompt applies (more prompting, not less safety).
The authoritative controls hold regardless of client: manual runnables are
refused by the gateway itself, and password args never appear in a tool
schema. So a bare client is fully functional; the plugin just makes autonomous
runnables frictionless.
Configuration
Point the gateway at a config three ways (first that applies wins):
- A single
runspec_mcp.toml—--config,$RUNSPEC_MCP_CONFIG, or./runspec_mcp.toml. - An existing runspec-console config directory —
--config-dir <dir>(or$RUNSPEC_MCP_CONFIG_DIR) reuses that console'srunspec_hosts.toml+runspec_credentials.toml+config.toml, including its SSH key. On Windows the console's directory is%APPDATA%\runspec-console. - A bundled config seed package (see below).
[local]
venv_globs = ["~/venvs/*"] # local multi-venv discovery
[gateway]
refresh_interval = 30 # background rediscovery cadence (s); 0 = off
[[host]]
name = "prod"
ssh = "deploy@prod.example.com" # omit ssh → this machine
runspec_paths = ["/opt/venvs/fleet/bin/runspec"] # remote venv runspec binaries
jump = "bastion" # optional ProxyJump (system ssh)
[[credential]]
id = "Windows" # label defaults to the id → WINDOWS_* env vars
kind = "userpass" # userpass | password | token | ssh-key
username = "svc-bot"
# scope_hosts / scope_runnables optional — else auto-matched by a runnable's arg env
SSH connection details (user, port, ProxyJump) come from ~/.ssh/config and the
[ssh] section, exactly as in runspec-console; jump selects the system-ssh
transport for legacy hosts. A host's own key can be written identity_file or
identityFile (the console's spelling), so hosts read from a console directory
keep their per-host key.
Reusing a console's configuration from an MCP client
An MCP client starts the gateway itself, so the directory goes in the client's
config. setup writes it for you:
runspec-mcp setup claude --config-dir "%APPDATA%\runspec-console" # Windows
runspec-mcp setup vscode --config-dir ~/AppData/Roaming/runspec-console # Linux/macOS
That prints a client entry that runs runspec-mcp serve with
RUNSPEC_MCP_CONFIG_DIR set. An environment variable rather than an argument,
because the Claude Code plugin can't pass arguments. runspec-mcp serve
--config-dir <dir> works too, for clients that let you give arguments. An
explicit --config or --config-dir beats the environment variable.
SSH & keys
No SSH key ever lives in the MCP config. The MCP server entry (plugin.json)
and runspec_mcp.toml carry only paths and credential ids — never key
material; credential secrets live in the OS keychain.
To save you from setting anything up in ~/.ssh, the gateway manages its own
ed25519 keypair (in its app dir) and uses it as the default SSH identity for
every host. You authorise it on your hosts once, per-user — no shared service
account:
runspec-mcp key # print the gateway's managed public key
runspec-mcp copy-key --all # install it on every configured host (one password prompt)
runspec-mcp copy-key prod db-1 # …or just the named hosts
Precedence and fallbacks are unchanged: a per-host identity_file (a path) still
overrides the managed key, and the ssh-agent / default ~/.ssh/id_* keys remain a
fallback — so an existing SSH setup keeps working. runspec-mcp key --regenerate
rotates the managed keypair (re-run copy-key afterwards).
Credentials
Secrets live in the OS keychain (keyring), keyed by credential id under
the runspec-console service — so they are shared with the desktop console.
runspec-mcp cred set Windows # prompts, stores in the keychain
runspec-mcp cred list # shows which env vars each credential emits
On Linux, the keychain is the desktop's Secret Service (GNOME Keyring or
KWallet), running and unlocked in your session. Over SSH or on a headless server
there usually isn't one, and cred set says so. Either store secrets from a
desktop session, or install a file-based keyring backend (for example
keyrings.cryptfile, which encrypts with a password) and select it with
PYTHON_KEYRING_BACKEND.
A credential is injected as name-derived environment variables — label Windows
+ kind userpass → WINDOWS_USERNAME/WINDOWS_USER/WINDOWS_PASSWORD; Deploy
+ token → DEPLOY_TOKEN/DEPLOY_PAT. Two matchers decide which credentials a
call receives:
- Auto by arg
env— a runnable that declares it readsWINDOWS_USERNAMEauto-pulls theWindowscredential. Zero config: the runnable's own interface says what it wants. - Scope globs —
scope_hosts/scope_runnablescover runnables that read bare environment variables the MCP schema can't advertise.
Autonomy
Each runnable's autonomy maps onto a Claude Code permission decision via a
PreToolUse hook the plugin ships:
| runspec autonomy | Claude Code | effect |
|---|---|---|
autonomous |
allow | runs without prompting |
confirm / supervised |
ask | the operator approves each call |
manual |
deny | the agent may not run it — the gateway also refuses it |
| (unknown / no manifest) | ask | fail closed |
The gateway rewrites the hook's manifest on every discovery change, so it is
always fresh — there is no manual generation step. It lives in your config
directory (~/.config/runspec-mcp/autonomy.json, or %APPDATA%\runspec-mcp\ on
Windows; $RUNSPEC_PLUGIN_MANIFEST overrides it), where both the gateway and the
hook find it. The remote side stays
authoritative regardless of the client: the venv denylist and enforce_run_as at
parse() time, and password args omitted from agent-facing schemas.
Config seed — a "fleet adapter" package
For a team or air-gapped deployment, you don't want every user hand-writing a
runspec_mcp.toml. Ship a small pip package — a config-seed adapter — that
bundles the baseline hosts + credential metadata as package data, so
pip install mycorp-fleet leaves the gateway pre-wired. (Secrets are never in
the package — see below.)
1. Lay out the package with a config_seed/ data directory:
mycorp_fleet/
__init__.py
config_seed/
runspec_hosts.toml # the fleet's hosts
runspec_credentials.toml # credential metadata (no secrets)
config.toml # optional: [ssh] / [local] / [gateway] defaults
2. Declare the entry point in the package's pyproject.toml:
[project.entry-points."runspec_mcp.config_seed"]
mycorp = "mycorp_fleet" # the module holding the config_seed/ dir
3. Seed hosts (config_seed/runspec_hosts.toml) — same schema as a hand-written
config:
[[host]]
name = "prod"
ssh = "deploy@prod.example.com"
runspec_paths = ["/opt/venvs/fleet/bin/runspec"]
[[host]]
name = "db-1"
ssh = "svc@db-1"
jump = "bastion"
runspec_paths = ["/opt/venvs/ops/bin/runspec"]
4. Seed credential metadata (config_seed/runspec_credentials.toml) — ids,
labels, kinds, usernames, scopes. Never secret values:
[[credential]]
id = "Windows"
label = "Windows"
kind = "userpass"
username = "svc-bot"
scope_hosts = ["prod", "db-1"]
5. Install and verify. After pip install mycorp-fleet, the gateway folds the
seed in automatically on start; runspec-mcp seed-status lists the contributing
package and version, and runspec-mcp hosts shows the seeded fleet.
runspec-mcp seed-status # mycorp-fleet 1.0.0 → runspec_hosts.toml, runspec_credentials.toml
How the fold works. Seeds are the lowest config layer: they're merged
by identity (host name, credential id) under any local runspec_mcp.toml
edits and a live config-sync — so a package upgrade rolls the baseline forward
without clobbering an operator's own hosts. The gateway also reads the console's
runspec_console.config_seed group, so one corporate package can seed both the
desktop console and the MCP gateway.
Secrets stay per-user. The package carries only metadata; each operator supplies their own secret once into the OS keychain — no shared service account:
runspec-mcp cred set Windows
Further reading: docs/white-label.md walks the same mechanism
end-to-end on the console side (branding + preserve + re-assert-on-upgrade); the
docs/design/config-seed.md design note has the merge internals.
Commands
| command | what it does |
|---|---|
runspec-mcp serve |
the MCP gateway (what the plugin runs) |
runspec-mcp hosts |
list configured hosts + probe connectivity |
runspec-mcp discover [--host H] |
print discovered tools |
runspec-mcp key [--regenerate] |
show / rotate the gateway's managed SSH key |
runspec-mcp copy-key [--all] [HOST…] |
install the managed public key on host(s) |
runspec-mcp cred set\|list\|rm ID |
manage credential secrets in the keychain |
runspec-mcp seed-status |
show bundled config-seed contributors |
runspec-mcp plugin-path |
print the bundled Claude Code plugin source directory |
runspec-mcp claude-plugin |
render the plugin for this install and print its path (what the runspec-pip marketplace runs) |
runspec-mcp check |
validate config + connectivity + manifest |
Inside a session the plugin also offers /runspec-hosts and /runspec-refresh
(a manual force-refresh; discovery is otherwise automatic).
See also
- Jump hosts — the SSH+MCP transport the fleet reuses.
- Agent Integration — autonomy and the MCP surface.
examples/claude-code-plugin/— the minimal, zero-dependency single-host reference this package supersedes for the multi-host case.