External MCP servers
The console is normally an MCP server producer: runspec serve publishes
each runnable as a leaf MCP tool for an outside agent to call. This page is about
the other direction — making the console's chat agent an MCP client, so tools
published by an external MCP server show up in the agent's toolset alongside
your runnables and the built-in tools.
Declare the servers in config.toml under [mcp]; the console connects to each
at launch and folds its tools in. External tool calls run through the same
autonomy / confirm gate as everything else, and — like Telemetry and Phoenix —
the feature is behind an opt-in extra.
Install the extra
pip install "runspec-console[mcp]"
Without it, any configured servers are simply skipped (the Settings hint and
mcp_status report the SDK is missing). It's also part of
runspec-console[all].
Configure servers
The easiest way is Settings → MCP: a tab that lists your servers with a live
connection status (green connected · N tools / red error with the reason /
disabled), an Add/edit dialog for each, a Reconnect button, a section
on/off switch, and the connect/call timeouts. The edit dialog has the transport
picker, the
transport-specific fields (command/args/env or url/headers), a credential
dropdown (from your Credentials tab), the credential-header override, the proxy,
and the autonomy level. Saving validates the entry, writes config.toml, and
reconnects immediately.
Everything the tab writes is plain config.toml, so you can also hand-edit it.
Each server is a [[mcp.servers]] entry. Three transports are supported:
[mcp]
# enabled = true # section kill switch (default true)
# call_timeout = 60 # seconds per tool call
# connect_timeout = 20 # seconds to wait for the initial connect
# A local server started as a subprocess (stdio).
[[mcp.servers]]
name = "files"
transport = "stdio"
command = "uvx"
args = ["mcp-server-filesystem", "/srv/data"]
# env = { TOKEN = "…" } # added to the child's environment
# autonomy = "confirm" # confirm (default) | autonomous | manual
# A remote server over streamable-HTTP, authenticated from a named credential
# and reached through this network's proxy.
[[mcp.servers]]
name = "search"
transport = "http"
url = "https://mcp.example.com/mcp"
credential = "cred-search-token" # keychain-backed; becomes Authorization: Bearer
proxy = "http://proxy.corp:8080" # this server's egress (see "Per-server proxy")
# A remote server over SSE with a custom auth header.
[[mcp.servers]]
name = "wiki"
transport = "sse"
url = "https://wiki.example.com/sse"
credential = "cred-wiki"
credential_header = "X-API-Key" # send the secret raw under this header
Fields:
| Field | Applies to | Meaning |
|---|---|---|
name |
all | Short id; required. Namespaces the tools (mcp_<name>_<tool>). |
transport |
all | stdio, http (streamable-HTTP), or sse. Inferred from command/url if omitted. |
command / args / env / cwd |
stdio | The subprocess to spawn. env is added on top of the console's environment. |
url / headers |
http, sse | The server endpoint and any auth headers. |
enabled |
all | Per-server toggle (default true). |
autonomy |
all | confirm (default), autonomous, or manual for this server's tools. |
credential |
all | A named-credential id (Settings → Credentials) — see Credentials. |
credential_header |
http, sse | Header the secret is sent under (default Authorization). |
proxy |
all | Per-server HTTP CONNECT proxy — see Per-server proxy. |
How the tools appear
Each external tool is exposed to the agent as mcp_<server>_<tool> — e.g. a
search_issues tool on a server named github becomes mcp_github_search_issues.
The name is sanitised to the tool-name grammar and namespaced so it never
collides with a runnable or a built-in. The tool's own JSON-Schema input is
passed through unchanged, and its description carries a (via MCP server '…')
note so the agent knows the call leaves the machine.
In [llm] tool_mode = "search" the external tools join the deferred long tail
(reached via describe_tool / use_tool) rather than the always-inline kernel,
so a large server doesn't bloat every turn's tool list.
Autonomy — trust is opt-in
An external tool runs on someone else's server, so by default every call is
confirm-gated: the operator approves it (and can edit the arguments first),
exactly like a side-effecting runnable. Set autonomy = "autonomous" on a server
you trust to let its tools run without a prompt, or autonomy = "manual" to make
them human-only (the agent can never call them). This mirrors the console's
"safe by default, opt in to trust" posture everywhere else.
In an unattended trigger or schedule agent turn with a non-empty
action_allow list, external MCP tools are governed like the other action tools:
name a tool in action_allow to keep it, or it's dropped from that turn.
Credentials
Rather than pasting a token into headers or env, bind a named credential
(Settings → Credentials) to a server with credential = "<id>". The secret lives
in the OS keychain (Windows Credential Manager / macOS Keychain / Secret Service)
and is resolved only at connect time — the config file holds just the id, and the
secret never appears in the config, mcp_status, or the logs. How it's applied:
- http / sse — becomes an
Authorizationheader:Bearer <secret>for atoken/passwordcredential, HTTPBasicfor auserpassone. Setcredential_header(e.g.X-API-Key) to send the raw secret under a different header instead. A header you set explicitly inheadersalways wins. - stdio — injected as the credential's name-derived env vars (the same
convention the runnable credential channel uses — e.g. a
tokencredential labelledDeploysetsDEPLOY_TOKEN), so the server process reads its token from the environment.
Because only the id is stored, a bound credential is seed/sync-safe: the
credential's metadata (id, label, kind, username) rides Config Sync / the
config seed in runspec_credentials.toml, while each install supplies the actual
secret in its own keychain. In the Settings → MCP tab the server's credential
is a dropdown populated from mcp_credential_options() (id + label + kind, never
secrets).
Per-server proxy
Servers can live on different corporate networks, so the proxy is per server,
not global. Set proxy to an HTTP CONNECT proxy URL (http://proxy:8080; the
scheme is optional) — it mirrors the console's [ssh] proxy and Config Sync's
proxy. For an http/sse server the console reaches the endpoint through that
proxy; for a stdio server it's exported to the subprocess as HTTPS_PROXY /
HTTP_PROXY / ALL_PROXY (so a local server that itself calls out uses it too).
Leave it unset to use the machine's default egress.
Config seed & sync
[mcp] lives in config.toml, which is one of the files carried by Config
Sync and the config seed — so a white-label / air-gapped install can ship a
baseline set of MCP servers, and a team can distribute them from one versioned
source. Bound credentials come along too: runspec_credentials.toml (metadata
only) is also a synced/seeded file, and secrets stay in each machine's keychain.
See Team config — sync & rooms and the config-seed walkthrough
for how the layers combine (seed < git sync < local edits, with a preserve list
for machine-local keys).
Checking status
The agent's per-turn console context lists the connected servers and their tool
counts, so it knows the tools exist. Programmatically, the bridge exposes
mcp_status() (availability + a per-server summary of connected state, tool
count, and any connect error) and mcp_reload() (reconnect after editing
[mcp]). A server that is slow or down at launch simply lands disconnected and
shows in the status — it never blocks the console from starting.