stoat mcp serves Stoat's Model Context Protocol server over stdio. MCP
clients launch it as a subprocess, normally with the working directory of the
project they should manage. The server shares the wire DTO package and
contract version with the JSON CLI. Tool schemas, payloads, and envelopes are
MCP-specific; see JSON output.
Stoat can write the stoat entry for these clients:
stoat mcp install claude-code
stoat mcp install claude-desktop
stoat mcp install cursor
stoat mcp install vscodeThe command preserves other entries in the client's JSON file. Use
--print to print the entry without writing a file. Use --project to write
the current directory's project entry where the client supports it:
stoat mcp install claude-code --project
stoat mcp install vscode --projectThe installed entry includes the absolute Stoat executable, the mcp
argument, and the current working directory. The working directory determines
which stoat.toml the server reads. Claude Code and VS Code support a project
entry; Claude Desktop and Cursor use their user configuration only.
Check the contract, transport, executable, and client entries with:
stoat mcp doctormcp doctor marks an entry as stale when its command points to a different
Stoat executable. Re-run mcp install after changing the installation path.
Codex keeps its servers in ~/.codex/config.toml, or in .codex/config.toml
for a trusted project. Add Stoat through the Codex CLI:
codex mcp add stoat -- "$(command -v stoat)" mcpThat entry carries no working directory, so Codex starts the server in the
directory Codex itself runs in. Start Codex from the project directory when the
server must read that project's stoat.toml.
Every other MCP client runs the same command. Take the entry from
stoat mcp install claude-code --print and translate it into the client's own
format. The three fields are the absolute Stoat executable, the single argument
mcp, and the working directory:
{
"command": "/home/you/.local/bin/stoat",
"args": ["mcp"],
"cwd": "/home/you/project"
}stoat mcp doctor reports the contract version and the entries of the four
clients Stoat writes. It does not see a Codex or hand-written entry.
The default transport is stdio:
stoat mcp
# Equivalent explicit form:
stoat mcp serveStreamable HTTP is available for a client that cannot launch a subprocess:
stoat mcp --http 127.0.0.1:7777The HTTP address must be loopback (127.0.0.1, ::1, or localhost). The
server has no authentication, so it refuses addresses that bind another
interface.
One HTTP server serves several clients at once. Point each client at the address:
claude mcp add --transport http stoat http://127.0.0.1:7777/mcp
codex mcp add stoat --url http://127.0.0.1:7777/mcpThe handler answers on every path, so the trailing /mcp is a convention.
stoat mcp install writes a stdio entry only; write the URL entry through the
client's own command or its config file.
The process holds one working directory, so every client on a shared HTTP
server reads the same project's stoat.toml. Run one server per project, on
its own port, when the projects differ. The rate limits below apply to the
server, so clients on one address share the budget.
stoat mcp serve --http runs in the foreground. To keep one server per project
without holding a terminal, use up, status, and down:
stoat mcp up # serves 127.0.0.1:7777 for this directory
stoat mcp up --http 127.0.0.1:7801
stoat mcp status
stoat mcp downup starts a detached stoat mcp serve --http for the current directory and
waits for the address to answer, so a bound port or a bad address fails at the
prompt. It records the pid, address, and directory under
$STOAT_HOME/mcp/<id>.json, and writes the server's output to
<id>.log beside it.
The directory is the identity. up refuses a second server for a directory
that already has one, and status lists every project's server. down stops
the server for the current directory.
Nothing restarts these servers after a reboot or a logout. status checks both
the pid and the address, and drops a record whose server is gone.
up starts the child with the default rate limits. To change them, run
stoat mcp serve --http yourself with --tool-burst, --tool-rate,
--burst or --rate.
The default rate limits are 30 calls in the per-tool burst with a refill of
0.5 calls per second, and 60 calls in the shared burst with a refill of 2
calls per second. Change them with --tool-burst, --tool-rate, --burst,
and --rate when starting the server.
agent_access in a VM's vm.toml controls guest access. Levels include all
permissions below them:
| Level | Guest operations |
|---|---|
none |
No guest operation. Host-side status, lifecycle, snapshots, logs, forwarding, recipe management and project operations remain available. |
observe |
read_file, list_dir, stat, ps, svc_status, and tail_log. |
manage |
Observe operations plus write_file, copy_to, copy_from, pkg_install, svc, useradd, and apply_recipes. |
exec |
Manage operations plus exec, exec_bg, job_status, job_wait, job_output, job_kill, and list_jobs. |
create defaults to manage. The CLI and TUI can raise or lower a VM's
level. The MCP update tool can lower a level but refuses to raise it. The
level is checked by the MCP server; stoat exec and stoat cp do not enforce
it when called directly by a person.
The server registers these tools. Most inspection tools return data without
changing the guest. recipe_schema may refresh bundled recipe files, and
search_recipes may refresh the local index clone; treat those host-side
caches as mutable. Other tools may change VM, recipe, or guest state; tools
that run guest code are marked by their required level.
The read-only capabilities tool reads host checks and stored VM metadata. Its optional vm argument selects one VM; omit it for host and project scope. It does not start or connect to a VM, and it reports MCP access limits plus unavailable fork and continuation proposals. Discovery does not mutate a VM.
| Tool | Purpose |
|---|---|
list_vms, vm_status |
List VMs or inspect one VM, including recipe state and health. |
list_images |
List catalog and downloaded images. |
list_recipes, check_recipes, recipe_schema, search_recipes |
Inspect recipe availability, contract, or index entries. list_recipes sets has_health for a recipe with a health check and leaves out the check script. recipe_schema returns the script. |
plan_recipes |
Show what apply_recipes would run or skip without running it. |
add_recipe, update_recipe, remove_recipe |
Add, repin, or remove remote recipes. add_recipe accepts curated index names only; it refuses Git URLs. remove_recipe has no force option. |
list_guests, guest_info |
Inspect loaded guest definitions and their package/service commands. |
doctor, logs |
Check host prerequisites or tail a VM's console/apply log. logs removes terminal escape sequences and carriage returns from the lines. |
capabilities |
Read host checks and optional stored VM metadata; report current capabilities and limits. |
create, start, stop, destroy, update, clone |
Manage VM definitions and lifecycle. destroy deletes the VM, its disk and its shared directory, and returns {name, destroyed}. |
snapshot, list_snapshots, restore, delete_snapshot, forward, wait, prune |
Manage disk-only snapshots, port forwards, state waits, and stale files. snapshot and delete_snapshot work on a running VM; restore needs a stopped VM. prune is dry-run unless apply=true. |
project_status, project_up, project_down, project_apply, project_wait |
Inspect or operate on every VM declared by the server working directory's stoat.toml, in declaration order. A failure stops the run and later VMs are marked skipped. |
start has no force option. When the VM needs more memory than the host has
free, or the RAM limit refuses it, the tool returns limit_reached with
needed_mb and available_mb. Lower ram_mb with update, or ask a person
to start the VM with stoat up -y. Inputs with a fixed set of values
(until, mode, agent_access, stream, action, which) list them as
enum in the tool schema.
wait accepts reachable, applied, or stopped; healthy=true waits for
the applied recipes' health checks. Its timeout_seconds is a count of
seconds, not a duration string. It defaults to 300 and is capped at 600. The
result has healthy only after a healthy=true wait.
Call wait after start or create and before any guest tool. sshd needs
tens of seconds to come up, and a guest tool called earlier fails with
cannot_reach and the text call wait first.
exec stops after timeout_seconds (default 60, cap 600). It then returns the
output collected so far with timed_out: true and a message that points at
exec_bg. The guest kills the command through its own timeout when the guest
has one.
create fills ram_mb, cpus, and disk from the defaults in its tool
description when you omit them. mode is live or disk for an image that
offers both. A cloud image is always cloud, an installer image is always
disk, and a mode that contradicts the image is refused.
update works on a running VM. ram_mb, cpus, and ssh_port take effect
at the next start, and the result lists the ones a running VM has not picked up
in pending_restart. update refuses a recipe name that no recipe root
declares, with the code not_found. A VM's health is not ok while a
configured recipe is unapplied.
| Tool | Required level | Purpose |
|---|---|---|
apply_recipes |
manage |
Run the VM's configured recipe scripts, optionally with a named subset. |
read_file, list_dir, stat, ps, svc_status, tail_log |
observe |
Read guest files, directories, process state, service state, or logs. Guest paths are absolute. mode is octal permission bits such as 0644, and list_dir names entries by basename. svc_status adds active and enabled. |
write_file, copy_to, copy_from, pkg_install, svc, useradd |
manage |
Modify files, copy data under the VM's shared directory, install packages, manage services, or add a user. |
exec, exec_bg, job_kill |
exec |
Run or signal arbitrary guest commands. argv is an argument array, not a shell string. job_kill does not signal a job that is not running. It returns the job's state with signaled false. |
job_status, job_wait, job_output, list_jobs |
exec |
Inspect background jobs created by exec_bg. A job reads starting until the guest records its pid, then running, then exited, and exit_code appears only then. A guest reboot clears the job files and a job becomes unknown. job_wait blocks until the job exits or timeout_seconds passes, and returns the state with the tail of stdout and stderr. list_jobs includes each job's state. |
write_file writes as the guest ssh user, so the file belongs to that user.
as_root=true writes as root and needs the exec level. At manage it is
refused with access_denied. parents=true creates missing parent
directories as the same user. It returns {path, bytes, mode}. A missing
path answers not_found and a path the user cannot write answers
access_denied. Earlier versions always wrote as root.
Guest operations require a running VM. copy_to and copy_from restrict the
host side to the VM's configured shared directory. pkg_install uses the
loaded guest definition's package manager. svc uses its service templates.
Project tools use the directory in which the server was started. They do not
accept a project path in a tool argument. Start the server from the project,
or install a project entry that records that working directory. If the server
has no stoat.toml, project tools fail with an instruction to run stoat init
or use a named VM tool.
Project recipe state follows the same rules as the CLI: stoat.toml declares
recipes, stoat.lock pins commits, and .stoat/recipes/ holds checkouts. A
project operation repairs a missing or stale clean checkout from the lock and
refuses a stale declaration or dirty checkout.
MCP tool results use the DTOs described in JSON output. Errors are
returned with isError: true. The first content block is a text block with the
same human-readable message as the CLI. The server also adds the complete
wire.ErrorInfo under _meta["io.github.novusedge.stoat/error"] and appends a
second text block containing JSON in the form {"error":{...}}. Callers
should inspect isError, then branch on error.code from metadata or the
second text block. They must not branch on structuredContent: for typed
handler errors, the SDK owns that field and keeps the schema-valid typed
output. Receiving-middleware errors, such as rate-limit refusals, can leave
that field unset.
Successful results keep their existing DTO, structuredContent, content
fallback, and output schema. SDK argument validation and MCP protocol errors
are outside this tool-result contract. Binary file content is base64 encoded.
Read, directory, process, log, and command output sizes are capped by the
server; the tool schema states the cap for each input.
Recipe and guest tools read manifests and definitions on the host. They do not
run a recipe unless the caller selects apply_recipes, a project apply tool,
or a lifecycle operation whose documented behavior starts a VM and applies
its configured recipes.