Skip to content

Repository files navigation

labgrid-mcp

PyPI labgrid-mcp MCP server

Drive real embedded hardware from Claude, AI editors, and any MCP client.

labgrid is the open-source framework embedded teams use to share lab hardware: boards ("places") with remotely switchable power, serial consoles, USB muxes, and flashing tools. labgrid-mcp is a Model Context Protocol server that plugs any labgrid lab into the MCP ecosystem, so agents and dev tools can work with the lab directly:

"Acquire the rk3399 board, flash last night's image, power-cycle it, and tell me whether it reaches a login prompt. Paste the console log if it doesn't."

Not only for chat: any MCP client, scripted or human-driven, gets a policy-gated remote-control surface over labgrid's mature driver ecosystem, with the reservations and ownership arbitration ad-hoc device servers don't have.

Claude acquiring a board, powering it on, reading its serial console, then powering off and releasing it — all against the built-in demo lab

Claude driving the built-in demo lab — no hardware, one command: uvx labgrid-mcp demo

Features

  • Full device lifecycle: discover, reserve, acquire, release; keepalive-backed so holds never expire mid-task
  • Hardware control: power on/off/cycle, digital I/O, SD/USB mux switching
  • Interactive serial console: open, read, send, close; ring-buffered
  • SSH to the device: run commands, transfer files, tunnels in both directions
  • Flashing (opt-in): DFU, fastboot, bootstrap loaders, image writing, all as background jobs with status/log polling
  • Lab housekeeping: tags, aliases, comments, place management, change monitoring
  • Safety gating: read-only mode and per-category allowlists; the irreversible families (flash, place deletion) are off by default

47 tools, 5 browseable labgrid:// resources, honest readOnly/destructive annotations on every tool.

Try it in 5 minutes (no hardware needed)

Requires uv (its bundled uvx does the rest, including provisioning Python):

uvx labgrid-mcp demo

This boots a complete fake lab on your machine: a real labgrid coordinator and exporter, one demo board with a fake power switch and a fake serial console. It prints a paste-ready .mcp.json snippet. Then ask your agent:

  • "List places, then acquire demo-place"
  • "Power demo-place on and read its power state"
  • "Open the console on demo-place and read its output"

Ctrl-C tears everything down.

Connect your lab

No separate install stepuvx fetches labgrid-mcp from PyPI the first time it runs. (Prefer pip? pip install labgrid-mcp, then use "command": "labgrid-mcp" with no args below.)

You need a running, gRPC-era labgrid coordinator (labgrid ≥ 24; tested against 26.x) reachable from this machine.

1. Register the server with your MCP client. The server definition is the same everywhere — command uvx, args ["labgrid-mcp"], plus your LG_* env vars — only the config file location and top-level key differ. Pick your client:

Claude Code

Save as .mcp.json in your project root:

{
  "mcpServers": {
    "labgrid": {
      "command": "uvx",
      "args": ["labgrid-mcp"],
      "env": { "LG_COORDINATOR": "your-coordinator-host:20408" }
    }
  }
}

or one command: claude mcp add labgrid --env LG_COORDINATOR=your-coordinator-host:20408 -- uvx labgrid-mcp

Claude Desktop

Settings → Developer → Edit Config, then add under mcpServers in claude_desktop_config.json:

{
  "mcpServers": {
    "labgrid": {
      "command": "uvx",
      "args": ["labgrid-mcp"],
      "env": { "LG_COORDINATOR": "your-coordinator-host:20408" }
    }
  }
}
Cursor

Save as .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):

{
  "mcpServers": {
    "labgrid": {
      "command": "uvx",
      "args": ["labgrid-mcp"],
      "env": { "LG_COORDINATOR": "your-coordinator-host:20408" }
    }
  }
}
VS Code (Copilot)

Save as .vscode/mcp.json — note VS Code uses a servers key:

{
  "servers": {
    "labgrid": {
      "command": "uvx",
      "args": ["labgrid-mcp"],
      "env": { "LG_COORDINATOR": "your-coordinator-host:20408" }
    }
  }
}
Windsurf

Add under mcpServers in ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "labgrid": {
      "command": "uvx",
      "args": ["labgrid-mcp"],
      "env": { "LG_COORDINATOR": "your-coordinator-host:20408" }
    }
  }
}
Any other MCP client / SDK

labgrid-mcp is a standard stdio MCP server: spawn uvx labgrid-mcp (or labgrid-mcp after pip install labgrid-mcp) with LG_COORDINATOR set in its environment, and speak MCP over stdin/stdout. Works with any client or agent SDK that supports stdio servers.

2. Restart the client so it picks up the new server.

3. Confirm it's connected — ask your agent "List the labgrid places"; you should get your lab's boards back. You're ready.

Identity works exactly like labgrid-client: set LG_HOSTNAME / LG_USERNAME, or omit them to use your real hostname/user. Security is delegated to the network (VPN / SSH tunnel), same as labgrid-client.

(Running from a clone instead of PyPI? Use "command": "uv", "args": ["run", "--directory", "/path/to/labgrid-mcp", "labgrid-mcp"].)

Run with Docker

Build and run the server as a container (locked-down hosts, or hosting it inside the lab network)

Build the image from the repo's Dockerfile:

git clone https://github.com/onurcelep/labgrid-mcp && cd labgrid-mcp
docker build -t labgrid-mcp .

Then point your MCP client at it:

{
  "mcpServers": {
    "labgrid": {
      "command": "docker",
      "args": ["run", "-i", "--rm",
               "-e", "LG_COORDINATOR=your-coordinator-host:20408",
               "labgrid-mcp"]
    }
  }
}

A useful pattern for labs: build and run the container on a host inside the lab network while your MCP client runs anywhere, one container per user so identity and ownership stay per-person:

{
  "mcpServers": {
    "labgrid": {
      "command": "ssh",
      "args": ["labhost", "docker", "run", "-i", "--rm",
               "-e", "LG_COORDINATOR=127.0.0.1:20408",
               "-e", "LG_USERNAME=your-name",
               "labgrid-mcp"]
    }
  }
}

Don't share one running server between users: each instance holds a single labgrid identity, so a shared instance would make everyone's acquisitions indistinguishable. One container per user/agent keeps the lab's ownership model intact. (Note: an image you build bundles labgrid, LGPL-2.1-or-later — fine to use anywhere; if you redistribute the image, the LGPL's terms apply to that copy, with labgrid's license texts already inside it.)

Your first session

Just ask in plain language — the agent maps it to the right tools. A typical first workflow:

  • "Which places are free right now?"
  • "Acquire board-7 for me."
  • "Power it on, then open the serial console and show me the boot output."
  • "SSH in and run uname -a."
  • "Power it off and release the board."

Read-only asks ("list places", "who's holding board-7?") work immediately. Anything that changes hardware state is gated (see below), and the two irreversible families — flashing and place deletion — stay off until you explicitly enable them.

Configuration

Env var Default Effect
LG_COORDINATOR 127.0.0.1:20408 Coordinator address
LG_HOSTNAME / LG_USERNAME real host/user Identity, as in labgrid-client
LABGRID_MCP_READONLY off 1 = only read-only tools are registered: the Read group plus wait_for_change and forward_list
LABGRID_MCP_ALLOW unset Comma list of categories to register; flash and place_delete must be listed explicitly; they're off even by default
LABGRID_MCP_SSH_KEYFILE unset Private key for the SSH tools; unset, they error clearly at call time
LABGRID_MCP_ACQUIRE_TIMEOUT 120 Max seconds acquire_place waits for allocation

Safety in one paragraph: flashing and place deletion can do irreversible damage, so each needs its own explicit LABGRID_MCP_ALLOW entry. SSH tools are arbitrary command execution on the acquired board, the same trust class as a console session; LABGRID_MCP_READONLY=1 drops them along with every other gated tool (forward_list stays, since it only lists in-memory tunnel state). And a labgrid caveat worth knowing: the coordinator enforces no ownership guard on place metadata: this server refuses to edit an acquired place without force=True, but nothing can protect a place nobody holds (and an empty tag value in set_place_tags deletes that key, which is labgrid's own semantics). Details: docs/DESIGN.md §4 and §11.12.

Tools

All 47 tools by group
Group Tools
Read coordinator_info, list_places, show_place, who, list_resources, list_reservations
Acquisition / reservation acquire_place, release_place, allow_place, release_from, reserve, cancel_reservation, reservation_wait
Drivers get_power_state, set_power, get_io, set_io, get_sd_mux, set_sd_mux, set_usb_mux
Console console_open, console_read, console_send, console_close
SSH / forward ssh_run, put_file, get_file, forward_open, forward_remote_open, forward_close, forward_list
Flash (opt-in) flash_dfu, flash_fastboot, flash_script, bootstrap, write_image, flash_status, flash_logs
Place metadata add_place, add_place_alias, delete_place_alias, set_place_tags, set_place_comment, add_place_match
Place deletion (opt-in) delete_place, delete_place_match
Change monitoring wait_for_change

Resources: labgrid://places, labgrid://places/{name}, labgrid://resources, labgrid://reservations, labgrid://sessions.

Per-tool arguments and behaviors are documented in each tool's own description (visible in your MCP client) and in docs/DESIGN.md §5/§11.

Limitations

  • No video/audio/screen capture or USB instruments (need gstreamer + physical USB; no sane MCP surface)
  • No live event stream; wait_for_change long-polling instead
  • Old crossbar coordinators (labgrid < 24) can't connect
  • Authentication is network-level (VPN/tunnel), exactly labgrid's own model
  • The real flash/mux driver step needs a real board: the job machinery is fully CI-tested against fakes, the silicon-touching step is not

Development

Working on labgrid-mcp itself

The integration suite runs the whole stack, including the demo, against real coordinator/exporter processes with fake hardware, in CI on every PR, plus a weekly canary against labgrid master:

git clone <this-repo> labgrid-mcp && cd labgrid-mcp
uv sync
uv run pytest              # unit
uv run pytest -m integration

Architecture, decision log, and a verified reference of labgrid's internals: docs/DESIGN.md.

License

Apache-2.0. Copyright 2026 Onur Celep.

labgrid itself is LGPL-2.1-or-later and is used as a regular, unmodified dependency.

About

MCP server exposing labgrid hardware-in-the-loop device operations to LLM agents — 47 tools over coordinator gRPC + labgrid's client-side driver stack (power/console/io/flash/mux/SSH). Standalone, coordinator-agnostic.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages