Skip to main content
Every Lager Box runs an MCP (Model Context Protocol) server. It lets an AI agent understand the bench, understand the device under test (DUT), and plan hardware-in-the-loop tests. It runs on-box and is reachable over the box’s local IP. By default, the MCP server is read-only: it describes the bench and DUT, and it does not drive hardware or run code. Two opt-in environment variables change that; see Optional tool gates. The agent executes tests over a separate channel — the lager CLI.

Connecting an agent

Point any MCP-compatible client at the box:
Out of the box the MCP server is for discovery and planning only — it does not run code or drive hardware. Two opt-in environment variables extend it past that; see Optional tool gates. To execute a test, the agent writes a Python file locally. It then runs the file with lager python path/to/test.py --box <box-ip>, which syncs the project to the box and runs it with full project context. Pass the box’s IP address to --box — the same IP you connected to the MCP server on. Local box names are just client-side aliases, so the IP is the only identifier both sides can rely on. To make this concrete, discover_bench() echoes the address you actually connected on as box_address and hands back a ready-to-run lager python … --box <that-address> command.
The URL above assumes a box that publishes its ports, which is the default. A box started with start_box.sh --no-publish (or LAGER_NO_PUBLISH=1) does not publish port 8100 on the host. The MCP server still runs and binds 0.0.0.0:8100 inside the container. It is reachable only on the internal lagernet Docker network, where a reverse proxy owns the host ports. On such a box, point the client at the container’s lagernet address, because <box-ip>:8100 will not connect. Do not route port 8100 through the proxy, because the MCP server performs no authentication.The mode persists across restarts in /etc/lager/no_publish, and start_box.sh --publish clears it. At the end of each run, start_box.sh reports which mode the box is in.

What the agent sees

The server exposes two kinds of things: resources (read-only context the agent reads) and tools (callable functions).

Resources

Tools

By default the tool surface is read-only. The seven tools above do not drive hardware (set a voltage, toggle a GPIO, flash firmware) or mutate the box. All of that lives in the test script the agent writes and runs with lager python, or in dedicated CLI commands.This default holds only while both gates below are off.

Optional tool gates

Two environment variables, both off unless explicitly set, register additional tools when the box’s MCP server starts. Each widens what a connected agent can do to the box, so treat them as deployment decisions rather than conveniences.

LAGER_MCP_ALLOW_CONTROL

Adds three scoped tools. These read bench state and power-cycle a hub port — they do not execute arbitrary code.

LAGER_MCP_ALLOW_EXEC

This gate exposes arbitrary command execution and file writes on the box to any agent that can reach the MCP port. An agent can run any command the box’s service user can run, and overwrite any file it can write. The server itself logs a warning at startup when this is set. Enable it only on a bench you control, on a trusted network, and never on a shared or production box.

Prompts

The server also registers a few prompts. These are slash-command-style entry points that steer a client (e.g. Cursor) through the discover → plan → write → run workflow. They do no work themselves. Each returns an instruction that the agent follows with the tools above.
1

Orient

Read lager://dut/overview.md (or call discover_dut()) to learn what the box tests, the MCU and peripherals, the subsystems, and which documents to fetch.
2

Discover

Call discover_bench() to enumerate nets, instruments, and capabilities. Call discover_bench(net_name) for detail on a specific net, including its subsystem and the schematic sheet it lives on.
3

Plan

Call plan_firmware_test(...) to get a phased plan with API references and document pointers per step.
4

Write & Run

Author a Python test file using from lager import Net, NetType. Identify the box by the IP address you connected to the MCP server on — local box names are arbitrary client-side aliases. --box accepts a raw IP, so no registration is needed: run lager python path/to/test.py --box <box-ip>. The runnable can also be a folder (entrypoint main.py), which syncs and imports everything in it — handy for shipping reusable helper modules: lager python path/to/test_dir --box <box-ip>. (Optionally, lager boxes add --name <name> --ip <box-ip> --user <ssh-user> registers a friendly alias; all three options are required.)
5

Analyse & iterate

Review the CLI output, adjust the script, and re-run it with lager python.

Where context comes from

The quality of everything above depends on the metadata you author once, at bench setup:
  • Per-net purpose — set in the Net Manager TUI (lager nets tui), or with lager nets describe NAME --purpose "...". One sentence describing what each wire does on the DUT.
  • DUT context — set with lager dut: the box’s purpose, MCU, subsystems, and references to schematics and datasheets.
See Authoring DUT Context for the full guide.