Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
LG_HOSTNAMENoIdentity hostname, as in labgrid-client; defaults to the real hostname
LG_USERNAMENoIdentity username, as in labgrid-client; defaults to the real username
LG_COORDINATORNoCoordinator address127.0.0.1:20408
LABGRID_MCP_ALLOWNoComma-separated list of tool categories to register; 'flash' and 'place_delete' must be explicitly listed
LABGRID_MCP_READONLYNoSet to '1' to register only read-only tools: the Read group plus wait_for_change and forward_listoff
LABGRID_MCP_SSH_KEYFILENoPrivate key for the SSH tools; if unset, SSH tools error clearly at call time
LABGRID_MCP_ACQUIRE_TIMEOUTNoMax seconds acquire_place waits for allocation120

Capabilities

Features and capabilities supported by this server

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
coordinator_infoA

Report the coordinator connection.

Returns the coordinator address, the client's claimed identity, whether the subscription is currently connected, and the coordinator version if the protocol provides one (currently null).

list_placesA

All places known to the coordinator, as labgrid place dicts.

show_placeA

One place by exact name. Raises a tool error if unknown.

whoA

Currently acquired places by host/user (derived from the snapshot).

list_resourcesA

All exporter resources known to the coordinator.

list_reservationsC

Current reservations (live unary RPC to the coordinator).

wait_for_changeA

Long-poll for the next place/resource change (design §11.12 -- the live-monitor substitute; FastMCP has no subscription surface).

    ``cursor=None`` bootstraps: returns the coordinator's current change
    cursor immediately, with ``changed=false`` (no waiting) -- call this
    once to get a starting point, then pass the returned ``cursor`` back
    on later calls. Otherwise blocks (event-driven, no busy-polling)
    until the cursor advances past ``cursor``, or ``timeout_s`` elapses,
    whichever comes first, then returns the current cursor and whether it
    changed. ``timeout_s`` is clamped to at most 25.0 regardless of what
    is requested, to stay under typical MCP client request timeouts --
    this assumes the caller's own MCP client timeout is set higher than
    25s; a lower client timeout just means the client gives up first and
    the orphaned poll on our side finishes harmlessly on its own.
    Read-only and idempotent: it never mutates anything, and repeated
    calls with the same arguments are safe to retry. Registered
    unconditionally, like the other read tools -- available even in
    readonly mode. The change cursor is process-local and resets to 0 on
    server restart (design §11.12); a cursor value held from a previous
    process is simply stale and self-heals after at most one full
    ``timeout_s`` -- the next call either sees an already-advanced
    cursor (``changed=True``) or times out and returns the current one.
    
reserveA

Create a reservation (with the given filters/priority) and keep it alive in the background until it is cancelled, expires, or is invalidated. Returns the serialized reservation, including its token.

cancel_reservationA

Cancel a reservation by token and stop its keepalive task.

reservation_waitA

Block-and-poll a reservation until it allocates a place, or times out.

        ``timeout_s`` is clamped to at most 25.0 (PlaceSession.reservation_wait,
        §11.14), like ``wait_for_change``. Only an *acquired* reservation
        auto-refreshes coordinator-side -- polling here IS what keeps a
        merely-``waiting``/``allocated`` reservation's TTL alive, so the
        token still needs an ``acquire_place`` (or another ``reservation_wait``/
        ``cancel_reservation``) soon after this returns, or it expires
        ~60s after creation regardless of the outcome reported here.
        Returns ``{"token", "state", "allocations", "changed"}`` --
        ``changed`` is True iff the reservation is allocated by the time
        this returns; a dead token (expired/invalid/unknown) or a genuine
        timeout both report ``changed=False`` without raising. Non-
        destructive but not idempotent (each call keepalive-polls).
        
acquire_placeA

Acquire a place by name for this session.

        Free places are acquired directly; a taken or reserved place is
        acquired via a name-filtered reservation, polled until it
        allocates our place (bounded by ``config.acquire_timeout``), then
        acquired and the reservation dropped (session.py, DESIGN.md
        section 11.8). Raises a tool error on RPC failure or timeout.
        
release_placeA

Release a place by name.

By default this verifies we currently hold the place before releasing it; pass kick=True to release unconditionally regardless of the current holder.

allow_placeA

Allow another user to use a place this session has acquired.

        ``user`` must be in "host/user" format; raises before any RPC if
        it is not.
        
release_fromA

Release place from a specific host/user identity.

        Unlike ``release_place`` (which only ever releases OUR OWN
        acquisition), this targets an arbitrary identity -- useful for an
        operator clearing a stale/foreign hold. Design §11.14/§11.8 trap:
        the coordinator's ``ReleasePlace`` does NO format validation on
        ``fromuser``, and a non-empty ``fromuser`` that does not match the
        place's actual holder is a SILENT no-op that still reports
        success -- so simply checking "not acquired by fromuser
        afterward" is USELESS (it is already true before the call
        whenever ``fromuser`` never held the place, which is exactly the
        mismatch case). The only way to tell whether THIS call actually
        changed anything is to compare a snapshot from BEFORE the RPC
        against one from after: ``released`` is True iff ``fromuser`` was
        the holder beforehand AND is no longer the holder afterward (a
        place ``fromuser`` never held reports ``released=False`` --
        nothing was there for this call to release). This tool talks
        directly to the coordinator client (like the place-metadata
        mutators) rather than through ``session.release_place``, since
        that helper only knows how to release OUR OWN identity and would
        refuse before ever sending the RPC for anyone else's.
        ``host``/``user`` must each be non-empty and "/"-free -- validated
        (mirroring ``allow_place``'s ``_validate_identity``) BEFORE any RPC,
        since the coordinator does no validation of its own (§11.14 above).
        
get_power_stateA

Current power state for a place this server has acquired.

        Reads via ``NetworkPowerDriver.get()`` (target.py). Category.POWER
        gated even though it only reads: it talks to a real hardware path,
        unlike the always-on Phase 1/2 read tools. ``resource_name``
        (§11.14) picks one of several same-class power resources on the
        place -- omit it for a single-power-resource place (unchanged
        behavior); an unnamed pick on a multi-resource place is a tool
        error naming the available resources. Echoed back in the result
        only when given (additive).
        
set_powerA

Drive power on/off/cycle on an acquired place, then return the resulting state.

        ``action`` must be one of "on", "off", "cycle"; anything else is
        rejected with a tool error before the place is even checked or any
        driver is touched. ``delay`` (§11.14) sets the off/on gap (in
        seconds) ``NetworkPowerDriver.cycle()`` sleeps for; it is only
        meaningful for ``action="cycle"`` -- passing it with "on"/"off" is
        a tool error (clearer than silently ignoring it), raised alongside
        the action check, before ownership or any driver call.
        ``resource_name`` picks one of several same-class power resources
        on the place (§11.14); omit it for a single-power-resource place
        (unchanged behavior).
        
get_ioA

Current digital IO state for an acquired place (HttpDigitalOutputDriver.get()).

        ``resource_name`` (§11.14) picks one of several same-class IO
        resources on the place; omit it for a single-IO-resource place
        (unchanged behavior). Echoed back in the result only when given.
        
set_ioA

Set digital IO on an acquired place, then re-read and return the resulting state (HttpDigitalOutputDriver.set() then .get()).

        ``resource_name`` (§11.14) picks one of several same-class IO
        resources on the place; the SAME name is used for both the set
        and the re-read, and omitting it preserves single-resource
        behavior unchanged.
        
get_sd_muxA

Current SD-mux mode for an acquired place (USBSDMuxDriver.get_mode()).

        SD-mux only (§11.14): ``LXAUSBMuxDriver`` (the ``usb_mux`` kind)
        has no read method, so there is no ``usb_mux`` equivalent.
        
set_sd_muxA

Set the SD mux mode on an acquired place (USBSDMuxDriver.set_mode()).

set_usb_muxB

Set USB mux links on an acquired place (LXAUSBMuxDriver.set_links()).

console_openA

Open an interactive console session on an acquired place.

        Binds a raw-protocol ``SerialDriver`` and starts its background
        reader (console.py, DESIGN §11.10); one session per place -- a
        second open on a place that already has one is rejected by the
        registry. The ownership check runs first, before any registry
        call, so an unacquired/unknown place never touches the registry.
        
console_readA

Drain up to max_bytes (default all) buffered console bytes.

        Consumes the drained bytes and resets the truncated flag. An
        unknown session is a tool error; an errored reader still surfaces
        any buffered data alongside the failure (registry.read()).
        
console_sendA

Write data to an open console session.

        Appends ``"\n"`` when ``newline=True``. An unknown, closed, or
        errored session is a tool error.
        
console_closeA

Close a console session: stop and join its reader, then deactivate the driver. Idempotent; an unknown session is a tool error.

forward_listA

List all live forward tunnels across every place (forwards.sessions()).

ssh_runA

Run command over SSH on an acquired place and return its result.

        Binds an ``SSHDriver`` (target.py, §11.13) and shells out via
        ``SSHDriver.run`` off the loop. Returns
        ``{"place", "stdout", "stderr", "exit_code"}`` -- labgrid returns
        stdout/stderr as line LISTS, joined here with ``"\n"``. The
        driver's own ``timeout=timeout_s`` is the sole enforcer of
        ``timeout_s``; the outer ``asyncio.wait_for`` only backstops a
        wedged thread with a small grace (``timeout_s + 5.0``) so it
        doesn't co-fire with the inner timeout. CAVEAT: a timeout does NOT
        kill the remote command -- ``subprocess`` does not reap on
        ``communicate(timeout=...)``, so the underlying ``ssh`` may linger
        until it finishes or the ControlMaster tears down. CAVEAT: labgrid
        whitespace-splits ``command`` (``cmd.split(" ")``) with no shell
        quoting, so multi-space or quoted arguments will split wrong.
        
put_fileA

Copy a local file to remote_path on an acquired place (scp).

        The local file must exist (a missing one is a pinned ToolError,
        checked before the driver is bound). Returns
        ``{"place", "put": remote_path, "bytes": <local size>}``.
        
get_fileA

Copy remote_path from an acquired place to local_path (scp).

        An existing local target is refused unless ``overwrite=True`` (pinned
        ToolError); the parent directory must already exist. Returns
        ``{"place", "got": local_path, "bytes": <resulting size>}``. NOT
        read-only (it writes a local file), but non-destructive to the DUT.
        
forward_openA

Open a local port-forward tunnel to remote_port on a place.

        ``local_port=0`` (default) auto-assigns a free local port. Returns
        ``{"forward", "place", "local_port", "remote_port"}``. Multiple
        tunnels per place are allowed (forwards.py, §11.13). The ownership
        check runs first, before any registry call.
        
forward_remote_openA

Open a REMOTE (-R) port-forward tunnel on a place.

        Unlike ``forward_open``'s local (``-L``) forward, BOTH ports are
        required -- labgrid's ``SSHDriver.forward_remote_port`` has no
        auto-assign for the local side (§11.14): a connection to
        ``remote_port`` on the DUT is forwarded to
        ``localhost:local_port`` on THIS host, so a local service must
        already be listening there. Returns ``{"forward", "place",
        "direction": "remote", "remote_port", "local_port"}``.
        ``forward_list``/``forward_close`` (and the ``labgrid://sessions``
        forwards payload) work identically regardless of direction. The
        ownership check runs first, before any registry call.
        
forward_closeA

Close a forward tunnel by id (ssh -O cancel, best-effort).

        Idempotent shape; an unknown tunnel id is a tool error.
        
add_placeA

Create a new place by name.

No ownership check -- a brand-new name can never collide with an existing acquisition.

add_place_aliasA

Add an alias to an existing place.

        Idempotent on the coordinator (adding a duplicate alias is a
        no-op, OK). Refuses if the place is acquired by a DIFFERENT
        identity unless ``force=True``. Returns the refreshed place dict
        (bounded snapshot catch-up -- the mutating RPC's completion is
        independent of the ClientStream update that lands the edit).
        
delete_place_aliasA

Remove an alias from an existing place.

        Pre-validates that ``alias`` is present in our snapshot BEFORE
        any RPC: design §11.12 trap -- the coordinator raises an
        uncaught ``KeyError`` (surfaced as gRPC ``UNKNOWN``) for a
        nonexistent alias, so this is checked here instead, giving a
        clean tool error and sending zero RPCs for a typo'd alias.
        Refuses if the place is acquired by a DIFFERENT identity unless
        ``force=True``.
        
set_place_tagsA

Set tags on an existing place.

        An empty string value for a key DELETES that key -- intentional
        labgrid semantics (design §11.12): the coordinator's own value
        validation is a no-op that lets an empty string straight
        through, so this is surfaced honestly here rather than hidden.
        Refuses if the place is acquired by a DIFFERENT identity unless
        ``force=True``.
        
set_place_commentA

Set an existing place's free-form comment (unvalidated by the coordinator).

        Refuses if the place is acquired by a DIFFERENT identity unless
        ``force=True``.
        
add_place_matchA

Add a resource match to an existing place.

        ``pattern`` is ``"exporter/group/cls"`` or
        ``"exporter/group/cls/name"`` (exactly 3 or 4 non-empty
        ``/``-separated segments) -- validated here before any RPC,
        since a different arity crashes the coordinator uncaught
        (design §11.12 trap). ``rename`` sets an alternate resource
        name; verified against labgrid 26.0 it is NOT part of a match's
        identity (a duplicate ``pattern`` is rejected regardless of
        ``rename``, and ``delete_place_match`` removes by ``pattern``
        alone -- see ``coordinator.py``). Refuses if the place is
        acquired by a DIFFERENT identity unless ``force=True``.
        

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription
placesJSON array of the places known to the coordinator (empty until synced).
resources_resourceJSON array of the resources known to the coordinator.
reservations_resourceJSON array of current reservations.
sessions_resourceJSON of live console sessions, forward tunnels, AND background flash jobs. ``{"consoles": [...], "forwards": [...], "jobs": [...]}`` (consoles.sessions(), forwards.sessions(), jobs.jobs()). Unconditional like the other resources -- registered even when Category.CONSOLE/ Category.SSH/Category.FLASH tools are gated off (readonly mode included). ``created``/``finished`` inside each job entry are MONOTONIC-clock values (process-relative, not wall-clock timestamps) -- passed through as-is, never rendered or converted here. Each job entry is normalized like ``flash_status``: canonical ``kind`` plus the additive ``loader`` field (``None`` except non-default bootstrap).

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/onurcelep/labgrid-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server