| 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``.
|