Skip to content

Latest commit

 

History

History
277 lines (216 loc) · 13.7 KB

mcp.md

File metadata and controls

277 lines (216 loc) · 13.7 KB

MCP server

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.

Install a client entry

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 vscode

The 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 --project

The 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 doctor

mcp doctor marks an entry as stale when its command points to a different Stoat executable. Re-run mcp install after changing the installation path.

Other clients

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)" mcp

That 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.

Transport

The default transport is stdio:

stoat mcp
# Equivalent explicit form:
stoat mcp serve

Streamable HTTP is available for a client that cannot launch a subprocess:

stoat mcp --http 127.0.0.1:7777

The 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/mcp

The 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.

A background server

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 down

up 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.

VM access levels

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.

Tools

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.

Host and recipe tools

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.

Guest tools

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 and scope

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.

Output and limits

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.