# Agents

> Give an agent machines of its own: the wm-mcp server for MCP clients, a key that holds only what the agent needs, every tool it offers, and how failures come back.

An agent can make machines, run commands in them, read and write their files, pause them and delete them, through `wm-mcp`, an MCP server. Give it a key of its own, holding only what it needs.

Your MCP client's settings:

```json
{
  "mcpServers": {
    "wheelmachine": {
      "command": "/path/to/wm-mcp",
      "args": [],
      "env": { "WHEELMACHINE_API_KEY": "ak_live_..." }
    }
  }
}
```

## Set it up

### Build wm-mcp

It is built with `wm`, from the same source: `go build -o bin/ ./cmd/...` in `frontend/cli/` writes both. See [Install](https://docs.wheelmachine.ai/cli.html#install).

### Give it a key of its own

Terminal:

```sh
$ wm keys create agent --scope machines:read,machines:write,machines:exec --expires 30d
```

Those three scopes are all its tools need. Leave out `keys:write` and `projects:write`, add `--project` to keep the agent to one project's machines, and revoke the key when the work is done: `wm keys revoke agent`.

### Add it to your MCP client

Add `wm-mcp` as a server your MCP client starts itself; most clients take an entry of the shape above. Put the key in its `env`, or run `wm login` on the same computer and leave `env` out, so the key stays out of the client's settings. `WHEELMACHINE_BASE_URL` works here as it does for `wm`.

## What the agent can do

A machine is named by its id (`m_...`) or its name in every tool. Each tool answers compact JSON.

| Tool | What it does | Arguments |
|---|---|---|
| `list_machines` | The account's machines, newest first, a page at a time. | `project`, `state`, `labels`, `limit`, `after` |
| `create_machine` | Make a machine; it answers once the machine runs. | `name`, `project`, `region`, `cpus`, `labels`, `from_snapshot`, `sleep_when_idle`, `idle_after_s`, `secrets`, `network`, `wait_s` |
| `get_machine` | One machine as it is now. | **`machine`** |
| `run_command` | Run a command; answers its `exit_code`, `stdout` and `stderr`. | **`machine`**, `command` or `shell`, `cwd`, `env`, `root`, `max_output_bytes` |
| `read_file` | Read a file: text as it is, other bytes as base64. | **`machine`**, **`path`**, `max_bytes` |
| `write_file` | Write text to a file, making the folders on the way. | **`machine`**, **`path`**, **`content`** |
| `pause_machine` | Pause a machine, memory kept. | **`machine`** |
| `start_machine` | Run a paused, stopped or archived machine again. | **`machine`**, `wait_s` |
| `stop_machine` | Stop a machine; its files stay. | **`machine`** |
| `copy_machine` | A new machine, a copy of this one as it is now. | **`machine`**, `name`, `labels`, `wait_s` |
| `snapshot_machine` | Keep a machine's files as they are now. | **`machine`**, `note` |
| `delete_machine` | Delete a machine and its files, for good. | **`machine`** |
| `list_regions` | Places a machine can be asked to run near. | none |
| `list_gpus` | GPUs, where they are and their price an hour (not open to every account yet). | none |
| `list_secrets` | The account's secrets by name, each with the machines given it; never a value. | none |
| `give_secrets` | Say which of the account's secrets a machine's commands get, by name. | **`machine`**, **`secrets`** |
| `set_network` | Set what a machine may reach on the internet: open, closed, or only what it lists; at once, without a restart. | **`machine`**, **`network`** |
| `whoami` | The account and key in use, and whether the account can spend. | none |

Arguments in bold are needed. Tools that only read say so to the client, and `delete_machine`, `stop_machine`, `write_file` and `run_command` say they can change or take things away, so a client can ask you before it lets the agent use them.

## When a tool fails

A call the API refuses comes back as the tool's result, marked as an error, with the API's own words, so the agent can put it right:

A failed tool call's result:

```json
{"status": 403, "code": "forbidden", "detail": "this key does not hold the cloud:machines:exec scope", "request_id": "req_01jb2xk3n4p5q6r7s8t9v0w1x2"}
```

The `code` is one of the API's [error codes](https://docs.wheelmachine.ai/errors/index.html); when the API asked for a wait, `retry_after_s` says how long.

## Good habits for an agent

- **Label what it makes.** `labels` such as `{"task": "issue-412"}` let it, and you, find its machines again with `list_machines`.
- **Let machines sleep.** A machine pauses by itself once it is idle (five minutes when not said), and wakes for the next command. Delete it when the task is done.
- **Start long work in the background.** `run_command` may run 10 minutes. For more, start the work with `nohup ... > log.txt 2>&1 &` and read the log with `read_file`.
- **Mind the output.** `run_command` gives back at most `max_output_bytes` of each of stdout and stderr (64 KiB when not said): their start and their end, with `truncated` true. Write long output to a file and read the part it needs.
- **A failed command is not a failed tool.** A non-zero `exit_code` is the command's own answer.

## MCP versions

`wm-mcp` speaks MCP over standard input and output (JSON-RPC 2.0, one message a line) and writes its log to standard error only. It speaks revision 2026-07-28, where every request carries its version, and the revisions that begin with `initialize`: 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.

## Agents that read the docs

For an agent that reads rather than calls tools: [llms.txt](https://docs.wheelmachine.ai/llms.txt) lists every page, each page is there as Markdown (the same address, ending `.md`), [llms-full.txt](https://docs.wheelmachine.ai/llms-full.txt) is the whole site in one file, and [openapi.yaml](https://docs.wheelmachine.ai/openapi.yaml) (or [openapi.json](https://docs.wheelmachine.ai/openapi.json)) is the API's contract.
