-
-
Notifications
You must be signed in to change notification settings - Fork 277
labgrid-mcp: drive a labgrid lab from an LLM agent (or any MCP client) #1951
Hi all. I've open-sourced labgrid-mcp, a standalone MCP server that exposes the labgrid client workflow to LLM agents and other MCP clients (Claude, Cursor, VS Code agent mode, or anything speaking MCP over stdio). You can ask an agent to list places, reserve/acquire a board, control power, read the serial console, SSH in, or flash it, and it maps onto the same operations labgrid-client does.
I wonder what people think about it :)
Repo: https://github.com/onurcelep/labgrid-mcp
A few notes on how it's built, since that's what I'd want to know first:
- It does not wrap the
labgrid-clientCLI or reuseClientSession. The interesting parts of an MCP server are session-ful (keepalive-backed reservations, interactive console sessions, background flash jobs) and a subprocess-per-call model can't hold any of that. Acquire/release are also session-bound to the gRPC stream that did the handshake, and parsing the CLI's human-oriented output isn't a stable API either.ClientSessionitself is argparse/print-coupled. So the server speaks the coordinator's gRPC protocol directly (persistent stream, reconnect, keepalive) and uses labgrid's client-side driver stack for power/io/mux/console/flash/SSH. Identity and config are the nativeLG_*env vars, and security stays at the network layer, same as labgrid itself. - The honest cost of that choice: it depends on internals that aren't a stable public API. That's why labgrid is pinned to
>=26.0,<27and a weekly CI job runs the full suite againstmasterto catch drift. The internals I had to verify empirically are documented with file/line citations in docs/DESIGN.md §11. Corrections welcome if I misread anything. - Safety: tools register per category with an allowlist plus a read-only mode, and flashing and place deletion are off unless explicitly enabled. Worth knowing as an operator: the coordinator doesn't enforce ownership on place-metadata RPCs, so the server adds a client-side refusal for editing an acquired place without an explicit force flag.
- Almost everything is tested end-to-end without hardware, in CI: real coordinator/exporter subprocesses against a fake HTTP power switch, a raw-protocol TCP serial bridge, and a user-mode sshd. Real flash/mux drivers and video/audio/tmc genuinely need a device and are untested against hardware so far.
To try it without a lab, uvx labgrid-mcp demo boots a complete fake lab locally (real coordinator + exporter, simulated power and console) and prints a paste-ready MCP client config.
Apache-2.0, with labgrid used as a regular unmodified dependency.
If you run a shared lab and try it, I'd like to hear what breaks.
All reactions
Replies: 2 comments 3 replies
Hi, thanks for sharing this! Interesting work.
I went through the repo and had a few questions.
Curious about scope beyond power-cycle and console check. Thinking of a scenario like a developer on our side is making changes on the ethernet driver, and we would want to actually run tests against that change on real hardware, not just power it on and check the console comes up. I see we can send a shell command to the DUT, so something like ethtool could probably be run manually through that. But I did not see pytest or test-suite integration, or a way to say "run this hardware test and report pass/fail," it looks like it is mainly CLI primitives right now. So is there an infrastructure planned that would let a developer actually trigger a real test run against their change and get a result back, or is this meant to stay more of a wrapper around the CLI?
Also, any plans on testing this on real hardware? I want to see how the agent behaves against real world problems, flaky serial, power glitches, timing issues that fakes will not reproduce. I think we need to be careful here, I do not want an agent power cycling my board 1000 times during a nightly test :)
One more small thing, I went looking for a monitor.sh you used in demo above, thought maybe it parsed labgrid-client show output, but could not find it in the repo. Just curiosity.
Hope to see you at the next community call and discuss more!
All reactions
First my short answers: test execution stays out of the MCP by design, real hardware works today, and a board your nightly holds cannot be power-cycled by an agent.
On scope and test suites. The way I approached this: I treat the MCP as a regulated API surface for the agent, the alternative being a plain shell and hoping for the best. So it exposes the same primitives a developer has with labgrid-client, with gating on top, and deliberately no "run my test suite" verb.
pytest stays pytest. For CI, a conftest fixture that acquires and releases around the session (or the classic reserve --wait / acquire / pytest / release wrapper) is the right shape, reproducible and with no agent involved. I would not put an agent in the quality gate: gates need reproducibility, and agent runs are not reproducible yet. I would say it's more of a tool for helping you during development, and on the fly it can create and run a test against the hardware, but that part is independent of the MCP itself. The MCP is just giving the agent the capability of knowing how to use labgrid. Making it smarter or more specific to your workflow is more the job of skills: a reusable procedure the agent loads (acquire/deploy/run/collect/release, always release when done) that teams adapt to their internal policies, while the MCP stays thin.
Where the agent earns its place is the loop after CI goes red, or during bring-up. A concrete everyday case: a test fails in CI, you tell the agent to investigate the failure from the report, and with the MCP in place it can reserve a device and go debug on real hardware, instead of guessing and asking you to copy and paste outputs back and forth. Your ethernet driver scenario is the same shape: the agent acquires the board, deploys the change, runs whatever you would run by hand (ethtool over ssh_run, or the pytest suite from its own shell), and when a case wedges the board it does what a script did not anticipate: reads the console history, power cycles, reruns just that case, and writes up what differed from the last failure. Every one of those steps is scriptable in advance; composing them in response to what actually happened is not, and that composition is what eats your afternoon. Holding the board through MCP across that whole loop is the point: a per-run fixture releases the moment pytest exits, losing the state you wanted to look at.
A run_pytest style tool (host-side suite as a background job, report pass/fail) would fit naturally; open to it if there is real interest.
And no, not a CLI wrapper underneath: it talks gRPC to the coordinator and drives labgrid's own driver stack, because reservations expire within about a minute without keepalive polling, and console sessions, flash jobs and port forwards all need state that lives across tool calls. The internals are not a stable API, so the version is pinned, a weekly CI job runs against labgrid master, and what I verified is documented with file/line citations in docs/DESIGN.md section 11. Corrections welcome there. Tools: https://github.com/onurcelep/labgrid-mcp#tools
On real hardware. Works today: coordinator locally, plus an exporter with a small YAML for the attached device (resources only exist through an exporter, so that part stays classic labgrid). From there the agent creates the place, adds matches, acquires and drives it. I will reproduce exactly this with a device I have around and share a recording :)
For the nightly case: every driver operation requires the place to be acquired by the agent's own identity, and acquire fails on a held place, so your nightly's board is out of reach. labgrid's own escape hatches (kick, force) still exist as explicit flags, same as labgrid-client gives any human. Beyond that: read-only mode, per-category allowlists, and flashing plus place deletion off unless enabled. The honest limit: nothing rate-limits an agent on a board it legitimately owns; that discipline stays with the agent harness permissions and lab policy, exactly as with a human.
For monitor.sh question it was a local helper for the recording, not in the repo:
#!/bin/bash
# Live observer: labgrid's own client, polling the coordinator's view of
# demo-place. Shows ownership flip as the agent acquires / releases.
export PATH="$HOME/.local/bin:$PATH"
export LG_COORDINATOR=127.0.0.1:20499
green=$'033円[32m'; yellow=$'033円[33m'; dim=$'033円[2m'; bold=$'033円[1m'; rst=$'033円[0m'
while true; do
out=$(labgrid-client -p demo-place show 2>/dev/null)
acq=$(printf '%s\n' "$out" | grep -m1 'acquired:' | sed 's/.*acquired:[[:space:]]*//')
clear
printf ' %slabgrid-client%s %s· live view%s\n\n' "$bold" "$rst" "$dim" "$rst"
printf ' place demo-place\n'
if [ "$acq" = "None" ] || [ -z "$acq" ]; then
printf ' status %s○しろまる free%s\n holder %s—%s\n' "$dim" "$rst" "$dim" "$rst"
else
printf ' status %s●くろまる acquired%s\n holder %s%s%s\n' "$green" "$rst" "$yellow" "$acq" "$rst"
fi
printf '\n %s(polling every second — the AI drives, this only observes)%s\n' "$dim" "$rst"
sleep 1
done
As of v0.1.4 I made acquire information part of uvx labgrid-mcp demo command so no need to run additional monitor on top :)
See you at the next community call. Thanks for actually digging into the repo :)