Skip to content

The farm's MCP server

An assistant that can read the farm answers "what is the farm doing", "why did that run fail", "which bundle did it flash" and "is this board healthy" from the farm itself, rather than from somebody pasting dashboard pages into a chat. alteriom-farm-mcp is that reader: a Model Context Protocol server over stdio, which turns each tool call into a read of the farm's API with a named key. It is step 6 of read-only first.

Read-only, on purpose

Every tool reads. None starts a run, cancels one, pins or deletes a bundle, or touches a board — and the client it is built on (alteriom_hil.farm_client, in the rig's core package) cannot build a request that would. Tools that change the farm come after this has been used, and will need a key whose role allows them: the farm refuses those routes to a user key whatever a client asks (keys and roles). A user key is all this needs, and it is the key to give it — never the farm's token.

Set it up

On the farm host, a key for the assistant:

sudo alteriom-hil-admin keys create --name assistant --role user --note "MCP"

On the machine the assistant runs on, the HAL installed (it brings the alteriom-farm-mcp command) and the key in a file only you can read:

pip install -e ./core -e ./rig
install -m 0600 /dev/null ~/.config/alteriom/farm-key && $EDITOR ~/.config/alteriom/farm-key

Then register it with the client. For Claude Code:

claude mcp add alteriom-farm \
  --env ALTERIOM_FARM_URL=https://hil.example.com \
  --env ALTERIOM_FARM_KEY_FILE=$HOME/.config/alteriom/farm-key \
  -- alteriom-farm-mcp

ALTERIOM_FARM_URL is where the dashboard is served — through the TLS proxy or a tailnet (reaching the dashboard). The client refuses plain http to anything but loopback, since a key sent in the clear is a key given away. ALTERIOM_FARM_KEY works in place of the file, for a client that injects secrets as environment.

Tools

Tool Answers
farm_whoami the name and role of the key it reads with
farm_status health and what is wrong with it, the queue, boards connected and missing, the ten latest runs
farm_devices every board — family, state, the run holding it, its last Rig Health Check verdict (the canary profile) and what failed — plus missing and unregistered devices and instruments
farm_capacity boards per family — connected, free, in use, and the tags the free ones carry — and how many runs may run at once, are running and wait: whether a run asking for one esp32-c3 would start now
farm_runs runs newest first, filtered by status, kind or a search
farm_run one run's stages and where it stopped, the failure detail, each capability's verdict, the failed tests, its bundle and its evidence
farm_run_log the last lines of a run's log
farm_bundles the bundles the farm holds, by profile or search
farm_bundle one bundle: manifest, images, files, the runs that flashed it
farm_statistics runs and pass rate, run time, queue wait, rig busy, failures by stage, per project

Results are cut to what a question needs — a run's page is tens of kilobytes of report and log, and farm_run returns its stages, verdicts and failures, with farm_run_log for the log — and capped at 60 KB. A wrong argument, a key the farm refuses or a farm that cannot be reached comes back as a result the model can read (isError), not a broken session.

Protocol

JSON-RPC 2.0, one message per line on stdin and stdout, logging on stderr. initialize (protocol versions 2025-06-18, 2025-03-26 and 2024-11-05), ping, tools/list and tools/call; notifications are accepted and not answered. Written against the protocol directly rather than an SDK: the HAL supports Python 3.9 and the protocol this needs is small.