vm-relay
An MCP server for acquiring and driving fresh, dedicated VMs through recorded scripts, with snapshot evidence, output extraction, console viewing, and lifecycle management.
Search installed applications from image inventories and probe host/guest readiness before choosing an image.
Acquire one or more fresh VMs per session, with TTLs, credential packs, declared outputs, capacity waiting, and optional console-sharing preparation.
Stage the guest runtime, support files, and an optional workspace onto a guest.
Run recorded shell, JavaScript, TypeScript, or Python scripts in the guest; commands are shell scripts.
Capture before/after display snapshots when the image supports cua-driver, and retrieve saved PNG/JPEG/WebP evidence images.
Extract declared files or directories with checksum verification.
Finish a guest by delivering a verified evidence package and destroying its VM, or release/abandon it while retaining available evidence.
Read console-viewing capabilities, resolve console status, open explicitly user-requested viewing, and cancel viewing attempts without releasing the VM.
Show session status for all held guests, leases, staging state, evidence paths, service origin, and selected environment.
Handles multiple guests per session and reconciles restarted sessions; failures retain VMs for repair rather than replaying uncertain input.
Does not perform local computer-use, spawn subagents, target physical machines, record video, or attach to existing browsers.
Provides browser-use automation inside a dedicated VM, controlling a fresh persistent Google Chrome (Chromium) instance to navigate, click, type, press keys, read page content, and capture snapshots with settle-waiting.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@vm-relayUse a fresh VM to open our staging site and capture evidence of any interruptions during checkout."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-relay
The Model Context Protocol front end of the VM relay, packaged as a Claude Code
plugin and as a pi package. pi-vm-relay,
which gave pi a native relay tool directly, is retired; mcp-relay replaces
it for both Claude Code and pi, the latter loaded through pi-mcp-adapter. It
is an MCP server: fourteen relay_* tools, each with its own schema, title
and annotations, and the server's instructions. The user command, status,
ships as one small command file per host (see "Commands"). There is no skill and no hook. Together they offer recorded,
snapshot-evidenced scripts, commands included as shell scripts, in a fresh, dedicated VM, for any work
the agent chooses to run there; a script drives the desktop or a browser
through the software the image lists, such as cua-driver or Playwright. Nothing here executes local computer-use, spawns
subagents, targets physical machines, records video, or attaches to existing
browsers.
The project was named mcp-vm-relay before v0.12.0. The npm package is now
@wezzard/mcp-relay, the plugin and its marketplace are mcp-relay, and every
MCP_VM_RELAY_* variable is now MCP_RELAY_*; the old names are not read. The
default state directory moved with the name, so finish or release a lease the
old relay owns before upgrading.
From v0.16.0 the relay's backend is machine-service, vm-service under its new
name. The relay asks for /machines paths, which vm-service never served, and
refuses a service whose /health does not answer machine-service. A selected
environment profile must be at schemaVersion 2 with machctlPath; a profile
at version 1 or with vmctlPath is refused with its fix in the error (DC-27).
The relay's own names do not change: MCP_RELAY_*, VM_ENVIRONMENT_FILE,
VM_RELAY_* and the state directories. Release every lease before switching,
as machine-service's installer requires.
How it is built
The relay implementation lives in this repository: the manager
(src/manager.ts), the machine-service client, the registry with its OS-level lock,
the guest transfer and transport, the evidence package and its state merge, the
selected-environment profile, the console and saved-image contracts, the strict
tool contract (src/schema.ts), the action dispatch and bounded result
rendering (src/surface.ts), and the guest program, the receiver
(src/guest/receiver.ts). It was inherited from
pi-vm-relay at commit 8990123,
synchronized with its implementation through commit 0d69fc7 before that
project's retirement, and is maintained here as mcp-relay's own
implementation from then on. src/server.ts binds the core to MCP over
standard input and output.
The committed dist/ holds server.mjs (the MCP server with the core bundled
in), the guest bundle the manager stages (receiver.mjs) and the doctor. Compiled relay-driver
code, the recorded-execution and evidence substrate, is bundled with hashes and
provenance in dist/build-info.json. Consumers load dist/; no build, SDK
checkout or network fetch happens at install time.
The Claude Code plugin and pi both reach this same server as plain MCP: pi
through pi-mcp-adapter (see "Use with pi" below), Claude Code by starting it
directly. Every tool's identity, schema and contract are shared; the plugin
adds nothing Claude-Code-specific beyond the marketplace packaging and the
vm-relay-operator agent definition.
In pi-vm-relay (retired, native pi extension) | Here (MCP server) |
a registered | fourteen |
doctrine injected before each agent turn | the server's MCP |
|
|
prompt-composed guest operator | the |
typed image blocks in a run result, with pi's | MCP image content blocks in the tool result, with the MCP |
session shutdown pauses lease renewal | the same when the server's stdio closes or it is signalled: renewal pauses after one final renewal for the rest of the lease's TTL, the recording detaches, the VM is retained until that TTL for an explicit |
the settled agent pauses renewal | none: MCP has no such hook, so the instructions and each run tool's description say "finish or release before you return" |
Related MCP server: claw-server
Install
Use with Claude Code
Try it from a checkout:
claude --plugin-dir /path/to/mcp-relayOr add this repository as a marketplace and install the plugin from it:
/plugin marketplace add WeZZard/mcp-relay
/plugin install mcp-relay@mcp-relayOnce loaded, the model sees the fourteen tools as
mcp__plugin_mcp-relay_relay__relay_search,
mcp__plugin_mcp-relay_relay__relay_probe, and so on through
mcp__plugin_mcp-relay_relay__relay_status (see "The tools" below for
the full table). The vm-relay-operator agent definition allows them by those
names. The server can also be configured directly in a project's .mcp.json
with node /path/to/mcp-relay/dist/server.mjs, in which case the tools are
mcp__relay__relay_search and so on.
Use with pi
pi install npm:@wezzard/mcp-relayThis needs pi-mcp-adapter installed. The tools appear as relay_search,
relay_probe, and so on through relay_status, with no host-specific
prefix (pi-mcp.json sets toolPrefix: "none"). The package also
ships a pi prompt template for the command, /mcp-relay-status.
Use with npx
npx -y @wezzard/mcp-relayThis runs the MCP server directly on stdio, for a client that speaks MCP without a Claude Code plugin or pi package wrapper.
Prerequisites
Node 22+, Python 3 with fcntl for the registry lock, and for VM execution an
Apple-silicon Mac with Tart, a running
machine-service (formerly vm-service) and prepared guest images.
Check readiness with node dist/doctor.mjs. It does not acquire a VM or
establish guest UI readiness. Staging and runs assume no software in the guest
but Node.js, which runs the relay's own guest code; relay_stage refuses an
image without it. A run takes before and after snapshots only when the image
lists capture software, cua-driver (catalog ids bundle:com.trycua.driver and
dpkg:cua-driver); otherwise its record has no snapshots, and that is not a
gap. Snapshots need cua-driver with capture permission; on Linux, native X11
and serve --no-overlay. relay_script runs JavaScript with the guest's
Node.js, TypeScript with tsx and Python with python3, each from the guest's
PATH; the relay installs none of them, nor the software a script drives, such
as cua-driver, Playwright or a browser. Console viewing needs the machine-service guest-sharing backend and its
guest preparation; see docs/console.md here.
The tools
Each tool's inputSchema is a plain object with no root anyOf/oneOf/allOf
and no action field, derived directly from the strict per-action contract in
src/schema.ts (nested unions inside a property, such as relay_image's
target, are unaffected). A call is mapped back to that contract's shape and
dispatched exactly as the retired single relay tool was, so behaviour,
results, image blocks and isError semantics are unchanged from before this
split. One tool runs anything in a guest: relay_script runs a shell,
JavaScript, TypeScript or Python script, given inline or as a project file. A
command is a shell script. It takes the optional reason, step, snapshots,
timeoutMs and, for a shell script, diagnostic, and its evidence is
automatic: see "Runs" below.
Tool | Title | Replaces ( | Purpose |
| Search installed applications |
| Find installed applications from image inventories by name and optional OS. |
| Probe host and guest readiness |
| Default |
| Read acquisition capabilities |
| Read versioned acquisition options (VNC backends) without allocation or ownership recovery. |
| Acquire a VM |
| Register the task, acquire a fresh VM, a guest, on the network |
| Stage the guest runtime |
| Push and hash-check the runtime, support files and an opt-in workspace, all from the project folder. Needs only Node.js in the guest, and reports whether runs take snapshots (only when the image lists cua-driver). Failed staging can be retried; a staged runtime accepts corrected executable paths; |
| Run a guest script |
| One recorded guest script in |
| Retrieve a saved image |
| Retrieve one saved display image, declared application image or immutable image reference without input, capture, directory export or acquisition. |
| Extract declared outputs |
| Pull only declared files or directories with source and host checksum verification. |
| Finish and deliver evidence |
| Extract the guest's declared outputs, deliver and verify its snapshot package, destroy its VM and unregister it; the session's other guests keep running. |
| Release the VM |
| Retain the guest's available evidence and abandon or destroy its VM; the other guests keep running. When this session holds no guest, the result is an error that says nothing was released. |
| Resolve console status |
| Resolve non-secret console status for the guest's lease and environment. |
| Open console viewing |
| Open explicitly user-requested viewing on the service host, with |
| Cancel console viewing |
| Cancel the identified viewing attempt without releasing the VM. |
| Show relay status | (unchanged) | Every guest this session holds, by name, each with its lease: backend binding, guest state, renewal state, console observation, last error, staging state and evidence path; plus |
readOnlyHint/idempotentHint are true for relay_search, relay_probe,
relay_acquisition_capabilities, relay_console_resolve, relay_image and
relay_status; destructiveHint is true only for relay_finish and
relay_release, which destroy a guest's VM; openWorldHint is true only for
relay_script, whose guest code may reach the network.
relay_command, relay_exec, relay_code, relay_run, relay_gate and
relay_tools are gone (DC-23, DC-26, docs/guest-code-runs.md §10). A
command is a shell script: {"argv":["ls","-al"]} becomes
{"language":"shell","inline":true,"code":"ls -al"}. relay_code is
relay_script with inline: true. A run carries no kind. In place of
relay_run's forwarded MCP tool calls, a script drives cua-driver, Playwright
or Chrome DevTools through their own libraries or command lines, as the image
lists them; the relay no longer stages MCP servers in the guest.
A run's result carries the execution identity and outcome, the derived step record, and, when the snapshot plan captured the after phase and delivery succeeded, the saved after-image as an MCP image content block. A run's result also carries the guest's bounded standard output and error. The full receipt stays in the evidence package.
Commands
The command asks the assistant to call one relay tool and report the answer. It changes nothing itself.
Command in pi | Command in Claude Code | Arguments | Purpose |
|
| none | Calls |
It is not an MCP prompt. pi's MCP adapter can only name a prompt
/mcp__<package>__<server>__<prompt>, so each host gets its own command file
instead: pi prompt templates in pi-prompts/ (listed in package.json under
pi.prompts) and Claude Code plugin commands in commands/. Both are
generated by npm run build from scripts/host-commands.mjs; do not edit them
by hand. CI and the release workflow check the packed tarball with
scripts/verify-package.mjs: the generated files must match their source, pi's
glob must select exactly the one template, and the packed server must start
without the prompts capability. The release publishes and attaches the same
tarball it checked.
Ownership, failure and recovery
One server session holds several guests at once, up to
MCP_RELAY_MAX_GUESTS(4 by default).relay_acquirenames each guest, such aslogin-test-1, and every tool that acts on a guest takes that name asguest. Each guest keeps its own lease and heartbeat, its own evidence folder underrelay-evidence/, its own background downloads, owner lock and registry row, and its own finish or release; finishing or releasing one leaves the others running. Calls on different guests run at the same time; calls on one guest keep their order. An acquisition beyond the limit is refused with the names of the guests held; the backend's own limits, such as two macOS VMs per host, still hold. An acquisition the host refuses for capacity waits for it, up toMCP_RELAY_CAPACITY_WAIT, then refuses with the backend's reason and the time waited; a waiting guest counts against the limit, and the backend's other refusals pass through at once.An operation failure retains the VM so the agent can inspect, repair and submit a new operation. Failed and uncertain operations are never automatically replayed.
Use
finishto deliver evidence and release, orreleaseto abandon explicitly. A declared output that was never produced is recorded as incomplete (incompleteExtractions) rather than failingfinish. A failed delivery retains the VM; a release failure retains ownership until destruction is verified.When the session ends (the client closes the server's standard input, a write to its standard output fails, it is signalled with SIGTERM, SIGINT or SIGHUP, or the process that started it goes away),
MCP_RELAY_ON_EXITsays what happens to the guests. Withrelease, the relay releases every guest the session holds through its own release path, keeping the evidence the host can still pull, then exits; an operation already in flight completes first, all within the shutdown grace period, after which a guest still held is released directly in one bounded attempt. A guest whose release fails keeps its record and stops renewing, so its lease ends within its last 15-minute window. Withpause, the default, the relay renews each guest's lease once for the rest of its TTL, then renewal pauses and the recording detaches; no VM is destroyed and is retained until its TTL for an explicit finish or release. Afinishorreleasealready in flight completes first, for up to the shutdown grace period; after that the operation is cancelled and renewal pauses without it. The backend TTL and grace period handle abandoned leases. Status reports the last confirmed expiration time.While the session is live, the relay tracks the lease's own deadline (its start plus
ttlHours) and renews machine-service only 15 minutes ahead at a time, never past that deadline, and stops renewing at it. A client that crashes without ending its session therefore leaves its VM leased for at most about 15 minutes, plus the backend's grace period, rather than its whole TTL.A restarted server with the same
MCP_RELAY_SESSIONreconciles each guest's durable ownership and reattaches its recording session without replaying prior work; every guest keeps its name. A session state written by a relay before several guests is restored as one guest named from its task. Context compaction does not reset VM state;probereports the owned state without relying on earlier messages.A run tool's
timeoutMsdefaults to 120,000 ms and accepts integers up to 3,600,000 ms. It bounds command execution, not the snapshot delay or the lease lifetime. A timeout reports confirmed termination or uncertainty and keeps the VM available.A shell
relay_scriptwithdiagnostic: truerecords diagnosis or repair without screenshots, for explicitly requested diagnosis when capture is unavailable. It runs through machine-service as/bin/sh -c <script>, a script file read on the host first: before staging in the guest's default directory, after staging in the recording workspace. Only a shell script may be diagnostic, because no receiver or Node.js is involved. It cannot join a snapshot group and is not visual verification.After diagnosing damaged recording state,
relay_stagewithresetRecording: truearchives the old recording and starts a new recording session in the same VM. An existing receiver lock refuses the reset until its operation is reconciled. Prior evidence paths remain in the owned status.
Runs
relay_script runs one script as one recorded operation; a command is a
shell script:
{"guest":"login-test-1","language":"shell","inline":true,"code":"/usr/bin/xdg-open https://example.com"}
{"guest":"login-test-1","language":"python","inline":true,"code":"import subprocess\nprint(subprocess.run(['cua-driver','call','list_apps'],capture_output=True,text=True).stdout)"}
{"guest":"login-test-1","language":"typescript","inline":false,"localPath":"checks/login.ts"}Evidence is automatic. Every run is admitted by the guest receiver, journaled, and, when the image lists cua-driver, bracketed by a before and an after display snapshot. The step record is derived from the call: the title is a shell script's first line (
shell <line>),code (<language>)orscript <file>, and the expected result is "Exits with status 0".reason,stepandsnapshotsare optional overrides; the default wait before the after-snapshot is 500 ms.Scripts. A
relay_scriptwithinline: truestreams itscodeto the guest with its hash; withinline: falseitslocalPath, a file in the project folder, is pushed hash-checked. The script runs in the staged workspace: shell with/bin/sh, JavaScript with the guest's Node.js, TypeScript withtsxor Node.js type stripping, Python withpython3. The script's exit status says whether it succeeded: a script that calls cua-driver reads the driver's answer and exits nonzero on an error. A call that givescodeandlocalPathother than asinlinenames is refused before anything reaches the guest.One interaction per run. A run is one recorded operation; consecutive keystrokes may form an explicit text group with
snapshots.group. A script that drives several interactions records them as one step.Outcomes. A refused, uncertain or nonzero run is an error result and keeps the VM. Nothing is ever replayed.
Saved images
A run tool names its saved after-image whenever the snapshot plan captures that
phase: a standalone event or a text group's last event. The result does not
wait for the image. Its imageDelivery is pending with the image identity
(imageId, originalPath, hash and size). In the background, one download at
a time, the relay brings each step's evidence to the host as one archive: its
before- and after-images and its receiver and action records. relay_finish and relay_release wait only for the download in
flight. They put the steps still queued, and every other file the host lacks,
into one archive of their own, and copy the files the step downloads already
brought instead of pulling them again. relay_status counts the steps whose
download a lifecycle operation took over as coalesced. A first or intermediate
group event and a diagnostic script do not invent an image. Execution and
image delivery are independent outcomes: a pending image does not turn a
failed run into a successful one. The delivery identity leads the result
text so it survives truncation. The design is docs/screenshot-delivery.md
§10.
relay_image shows a saved image. Asked for an image whose download is still
queued or running, it waits for that download within its own deadline. It
retrieves the same saved image through one closed selector; it never repeats input, creates a capture, exports the
consumer directory or acquires a VM:
{"target":{"source":"display","sessionId":"<recording-session>","executionId":"<saved-execution>","phase":"after"}}
{"target":{"source":"application","name":"screens","path":"login/step-1.png"}}
{"target":{"source":"reference","imageId":"image-<64 lowercase hex digits>"}}Display selection needs
beforeorafter; a phase the plan did not request returnsnot-requested. Application selection needs an acquisition-time declaration: a directory declaration takes one relative file path, a file declaration omitspath. A reference resolves to the same original bytes or an explicit failure.Originals are limited to 64 MiB, 40,000,000 decoded pixels and 32,768 pixels per dimension; PNG, JPEG and WebP are accepted. The preview is at most 2,000 × 2,000 pixels and 4 MiB of base64. Presentation differs from pi here: pi resizes through its own image helper, while this core has no codec dependency. A PNG original is decoded and, when it exceeds the preview bounds, resampled in-process (area averaging,
node:zlib); a JPEG or WebP original has its container validated and passes through unchanged when within bounds, and is reportedpresentation-unavailableotherwise. No EXIF orientation is applied; dimensions are those stored. The presentation receipt records the policy, the original and preview dimensions, the MIME type, hash, size and whether bytes were transformed, beside the untouched original.Each delivery has a 90-second deadline and a shared budget of three byte-transfer attempts. Only transient transfer failures are retried; authorization, unsafe-path, capture, format and integrity failures are not. Recommend at most two explicit recovery calls for the same reference; if a required image still cannot be inspected, stop exploratory input and finish or release.
Immutable catalog entries bind each reference to the owner, enclosure, backend and original identity. A closed or sealed guest returns
stale-reference; delivered originals remain readable with the host's file tools. Finalization materializes verified originals at canonical snapshot paths and preserves prior state when merging guest evidence.Attachment means the block was included in the result; it does not prove the model inspected it or that a human reviewed it.
Live console viewing
Acquisition never opens a viewer. relay_acquisition_capabilities reads the
backend's VNC options; relay_acquire with vnc: true checks OS availability
and requires a ready console and lease identity in the response.
relay_console_open is permitted only following an explicit user request,
requires userRequested: true, console_id, attempt_id, reason and
expected, and opens on the declared service host, not on a remote client.
relay_console_resolve refreshes the non-secret observation;
relay_console_cancel closes managed viewing resources and never releases the
VM. Console status ready is guest preflight, not launch
success; transport connection, authentication, displayed pixels and human
confirmation are separate observations that console actions never establish.
On macOS the human must choose Standard sharing of the existing console, not a
new Log In session or a High Performance display; the relay never confirms
that selection, and server_enforced_view_only: false with session_binding: viewer-selection-unverified remain limitations after transport connects.
Failed or uncertain launches retain ownership; resolve or cancel the attempt
rather than replaying it. Console tests use mocked backends; no live viewer,
installation or platform acceptance is claimed here.
Selected environments
Set VM_ENVIRONMENT_FILE to a profile, at schemaVersion 2 with
machctlPath, that binds a loopback machine-service endpoint, an image repository, a Tart store and separate service, image and
relay state directories together. The profile is validated before use, is
authoritative over the individual MCP_RELAY_* variables, and an invalid
selection fails rather than falling back. Leases are bound to their backend and
store: an owned lease restored under a different environment fails before any
backend operation, and destruction is verified with the selected Tart binary
and TART_HOME. docs/selected-environments.md documents the schema. The
bundle the profile exports to subprocesses is the canonical one machine-service
defines, with MACHCTL and the MACHINE_SERVICE_* names, so its relay entries
keep the VM_RELAY_STATE_DIR and VM_RELAY_URL names and the three runtimes
agree; this server reads its own
settings from the profile.
Evidence and cleanup
Default output is relay-evidence/<unique-task>/ under the project, with
state/ (the guest journal, action records, receipts and snapshot PNGs),
host/ (reasons, submissions, transfer facts, receipts, diagnostics, image
deliveries and lifecycle events), extractions/ (declared files, including the
page captures), manifest.json, which checksums everything, and OPENING.txt.
The package is the raw evidence only; pi-secretary's computer-use extension
shows its steps and verdicts beside the agent's session. The steps and verdicts
are derived from the evidence when you review it, so an earlier package is reviewed with today's rules.
A program that reads relay packages gets the same derivation from
@wezzard/mcp-relay/review-data: await reviewData(<directory>) returns the
steps (with each snapshot's package path), the verdicts and their reasons. No network, VM or external assets are
needed. Diagnostic commands appear in the review data
as command-only steps without screenshots. finish verifies the package before
destroying the VM; a failed delivery removes only the derived files it created
and retains the VM for a corrected attempt. Cleanup checks the read-only host
Tart inventory (the selected one, when an environment is selected) before
claiming destruction. Leases default to 4 hours, renewed 15 minutes at a
time up to that TTL; the machine-service reaper is the final backstop for process death.
Relay evidence has no size limit (owner decision PS-D12): screenshots are
delivered as captured, never scaled, compressed, deduplicated or budgeted, and
the relay state is delivered whatever its total size or file count. Only what a
client stages into the guest is bounded (512 MiB and 10,000 files per staging
request), and a single image original must be at most 64 MiB to be valid. Before
finish pulls the relay state it checks that the evidence volume has room for
it; when it does not, finish fails before pulling, names the shortfall and
keeps the VM for a retry after space is freed.
Configuration
MCP_RELAY_PROJECT: the project directory (the plugin passes Claude Code's). Evidence lands underrelay-evidence/<task>/there. Every host file the relay reads,relay_stage'sworkspaceandfiles[].localandrelay_script'slocalPath, lies in this folder: a relative path resolves against it, an absolute path must already lie in it, and a path through a symlink is refused.MCP_RELAY_SESSION: an explicit session identity. By default each server process takes a fresh random one; a stable identity lets a restarted server reconcile and reattach the guests of the same identity.MCP_RELAY_URL: the loopback machine-service origin, defaulthttp://localhost:6240. Non-loopback servers, redirects and physical targets are refused.MCP_RELAY_GUEST_NETWORK: the network every guest is acquired on,nat(the default) orinternet. Withinternet, machine-service gives a guest that reaches the internet and reaches neither this host nor any private network; an application that hosts agents sets it. The agent cannot choose it:relay_acquiretakes no network. Any other value stops the server at startup and refuses every acquisition, and a machine-service that cannot giveinternetrefuses the acquisition with its own reason, never falling back tonat.MCP_RELAY_ON_EXIT: what the end of a session does to its guests,pause(the default: renewal pauses and the VMs are kept for an explicit finish or release) orrelease(every guest is released, then the server exits). An application that owns its tasks' machines, such as The Desk, setsrelease. Any other value stops the server at startup.MCP_RELAY_CAPACITY_WAIT: how many secondsrelay_acquirewaits when the host is at capacity, such as at machine-service's limit of two running macOS VMs, asking again every 15 s; default 1800, and 0 for not at all. Any other value stops the server at startup.MCP_RELAY_MAX_GUESTS: the most guests one session holds at once, a positive integer, default 4. Any other value stops the server at startup.MCP_RELAY_STATE_DIR: host state, otherwise$XDG_STATE_HOME/mcp-relayor~/.local/state/mcp-relay, one subdirectory per session, with oneguests/<guest>/folder per guest holding its ownership record and owner lock.MCP_RELAY_REGISTRY: an explicit task registry file; otherwise an existing compatible~/AGENTS.mdVM table, or a private managedregistry.mdin the state directory.MCP_RELAY_SHUTDOWN_GRACE_MS: how long a session end waits for an in-flight operation, and withMCP_RELAY_ON_EXIT=releasefor the guests' releases, before cancelling it; default 120000.MCP_RELAY_PYTHON: the Python 3 used for the registry lock; defaultpython3on PATH.VM_ENVIRONMENT_FILE: a selected environment profile; it overrides the URL, state directory and registry above and binds the Tart store.Guest files go to
/var/tmp/<UTC-timestamp>-mcp-relay-<task>/, with the runtime inreceiver.mjs, support insupport/and work inworkspace/.
Development
Build relay-driver using its own instructions, then in this checkout:
npm ci
npm run setup:dev -- /absolute/path/to/built/relay-driver
npm run check # build, typecheck, testsThree tests need something outside this repository and skip with a message
when it is absent: the console fixture regeneration needs a historical
../vm-service checkout with the Python backend (or
VM_SERVICE_PYTHON_BACKEND); the environment parity test needs
machine-service's machctl (MACHCTL, PATH, or a release build in
../machine-service/rust) and ../pilot-images; and the shared catalog text
corpus needs ../pilot-images. The JPEG and WebP presentation fixtures in
tests/fixtures/ were encoded once from the test's spatial PNG with a codec
outside this repository and are committed, because the core carries none.
End-to-end with headless Claude Code
npm run test:e2eThis runs claude --print with the plugin loaded from this checkout
(--plugin-dir), the model limited to the relay's tools, and the server pointed
at a loopback stand-in for machine-service (tests/e2e/fixture-service.ts), which
keeps leases as records, runs the relay's own Node and transfer commands
locally, and fakes the desktop capture driver. Four cases run, each a separate
headless session: the status call (which also proves the server is pointed at
the stand-in before anything is acquired), an invalid call refused by the
contract, a full acquire, stage, exec, finish lifecycle with a verified package,
and a lease abandoned when the session ends, which is retained with its renewal
paused rather than released. What is checked is what happened on disk and at
the service, not what the model said. The suite needs claude on PATH with an
account and spends a few model turns per case, so it is not part of npm test.
The relay-driver links are development-only. The tests run the manager, the
guest programs and the shipped server against fixture HTTP services, fixture
MCP servers built with the MCP SDK and a real MCP client over stdio; no VM, desktop or user
registry is touched. Commit the regenerated dist/ with source changes.
Releasing
Cut a release with:
node scripts/bump.mjs X.Y.Z && git push --follow-tagsscripts/bump.mjs sets X.Y.Z as the version in package.json,
.claude-plugin/plugin.json and the pinned pi-mcp.json arg, refreshes
package-lock.json, rebuilds dist/, commits Release vX.Y.Z and creates the
annotated tag vX.Y.Z. It refuses to run against a dirty working tree or a
malformed version, and it never pushes — git push --follow-tags is a
separate, explicit step.
Pushing the tag triggers
.github/workflows/release.yml, which
rebuilds machctl, checks the working build against a fresh one, and publishes
to npm using trusted publishing
(OIDC — no NPM_TOKEN secret involved), then creates the matching GitHub
release.
Trusted publishing has to be configured on npmjs.com, and npm requires the
package to already exist before you can do that. The first publish must
therefore be done by hand (npm publish --access public from a maintainer's
machine) before its trusted publisher can be configured for this workflow.
License
MIT — see LICENSE.
Available Tools
14 toolsrelay_acquireAcquire a VMA
Acquire one fresh VM, a guest, and start its ownership heartbeat. The result's guest is the guest's name in this session, such as login-test-1; every later call on this guest names it with guest. Requires task (a short slug), image (a key from relay_probe or relay_search) and extractions: every output you intend to bring home, declared before any work ([] is allowed; nothing is extracted automatically). Optional ttlHours (default 4), env (a credential pack, never baked into images), fullWorkspace (opt in to deliver the whole workspace) and vnc (default false; only prepares console-sharing capacity, never opens a viewer). Acquire one guest for each machine the work needs: several guests may run at once, each with its own lease, evidence and finish, up to the session's limit (relay_status reports maxGuests, MCP_RELAY_MAX_GUESTS, default 4); an acquisition beyond it is refused with the names of the guests held. When the host is at capacity, such as at its limit on running macOS VMs, the acquisition waits for it, asking again every 15 s for up to MCP_RELAY_CAPACITY_WAIT (30 minutes by default), and the result's waitedForCapacityMs says how long it waited; once the wait runs out it is refused with the backend's reason and the time waited. The backend's other refusals pass through at once. Continue with relay_stage, relay_script, relay_image/relay_extract, then relay_finish or relay_release explicitly before you return; a failed step keeps the VM for repair rather than replaying uncertain input.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | Credential pack, default none; never baked into images. | |
| vnc | No | Prepare guest console sharing, default false. Never opens a viewer automatically. | |
| task | Yes | ||
| image | Yes | Image key from relay action=probe, e.g. ubuntu2404 or macos26. | |
| ttlHours | No | ||
| extractions | Yes | ||
| fullWorkspace | No | Explicit opt-in to deliver the entire workspace. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=false, destructiveHint=false, idempotentHint=false and openWorldHint=false, but the description adds substantial behavioral context: ownership heartbeat, lease/TTL behavior, capacity waiting and refusal semantics, credential packs never baked into images, VNC never opening a viewer, and extraction declarations being required up front. No annotation contradiction is present.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core acquisition action and then proceeds through parameters, lifecycle and failure behavior. It is dense and occasionally repeats schema-level details, but the length is largely justified by the tool's complexity and the absence of an output schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a 7-parameter, multi-step tool with no output schema, the description is complete enough: it explains the result's `guest` naming and `waitedForCapacityMs`, the session guest limit, capacity-wait timing, refusal behavior, and the required follow-up lifecycle. An agent should be able to invoke and sequence this tool correctly without additional documentation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 57%, so the description must compensate and does so well. It explains `task` as a short slug, `image` as a key from relay_probe or relay_search, `extractions` as all intended outputs declared before work, `ttlHours` defaulting to 4, `env` as a credential pack never baked into images, `fullWorkspace` as an explicit opt-in, and `vnc` as merely preparing console-sharing capacity.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence gives a specific verb and resource: 'Acquire one fresh VM, a guest, and start its ownership heartbeat.' It also distinguishes this tool from sibling relay actions by naming the subsequent relay_stage, relay_script, relay_image/relay_extract, relay_finish and relay_release workflow.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states when to use the tool (one guest per machine, multiple concurrent guests), what alternatives follow (relay_stage, relay_script, relay_image/relay_extract, relay_finish or relay_release), and what happens at capacity or refusal. It also gives explicit failure guidance: a failed step keeps the VM for repair rather than replaying uncertain input.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_acquisition_capabilitiesRead acquisition capabilitiesARead-onlyIdempotent
Read the backend's versioned VNC console options for the images it serves. Read-only: it neither acquires a VM nor recovers ownership of one.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false. The description adds valuable context by explicitly stating it 'neither acquires a VM nor recovers ownership of one,' clarifying behaviors beyond the generic annotation hints. This is useful behavioral disclosure about side-effect boundaries without contradicting the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two concise sentences with no filler. The core purpose is front-loaded, and the clarifying non-behavior sentence adds meaningful distinction without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a parameterless, read-only capability inspection tool with strong annotations, the description covers purpose and side-effect boundaries sufficiently. It does not describe the return format, but this is a minor gap given the simple nature of the tool and the absence of an output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters and schema description coverage is 100%, so the schema fully documents that no inputs are needed. The description correctly focuses on the tool's purpose rather than parameter details. The baseline of 4 for zero-parameter tools applies here.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Read the backend's versioned VNC console options for the images it serves.' It also explicitly differentiates the tool from acquisition and ownership-recovery actions by saying it 'neither acquires a VM nor recovers ownership of one,' which distinguishes it from sibling tools like relay_acquire and relay_console_resolve.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies this tool is for inspecting capabilities without side effects, and the read-only clarification helps an agent understand not to expect acquisition. However, it does not explicitly state when to prefer this tool over alternatives such as relay_acquire or relay_console_resolve, nor when not to use it. Usage context is present but indirect.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_console_cancelCancel console viewingA
Cancel an open or uncertain console-viewing attempt by console_id and attempt_id. Closes managed viewing resources only; it never releases or destroys the VM.
| Name | Required | Description | Default |
|---|---|---|---|
| guest | Yes | The guest this call acts on: the name relay_acquire answered. | |
| attempt_id | Yes | ||
| console_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With annotations declaring non-readonly, non-idempotent, non-destructive, the description adds real value by scoping what is torn down: managed viewing resources only, never the VM. That is exactly the blast-radius context an agent needs before invoking a cancel. It doesn't discuss retry semantics or failure states, keeping it out of 5 territory.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no filler, and the key scoping caveat (never releases or destroys the VM) is placed second as a guardrail. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema and thin parameter documentation, so the description must stand in for more. It covers purpose, scope of effect, and identifiers well, but leaves the post-cancel state and whether the attempt can be retried unstated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 33% (only guest is documented in the schema), so the description carries extra burden here. It names console_id and attempt_id as the identifiers but adds no format, provenance, or validity semantics beyond that, so it only marginally compensates for the gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb (cancel) + resource (console-viewing attempt) scoped by console_id and attempt_id, and it explicitly distinguishes itself from the release/destroy path. An agent can pick this over relay_console_open/resolve/release without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'open or uncertain console-viewing attempt' gives a clear trigger condition for using this tool, implying the counterpart open/resolve flows. It stops short of naming sibling alternatives or stating when not to use it, so it falls just below the explicit when/when-not bar.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_console_openOpen console viewingA
Open console viewing for the lease of the guest that guest names, only following an explicit user request to watch. Requires console_id and attempt_id from relay_console_resolve, userRequested: true, reason (intent) and expected (what the human should expect to see); it opens on the declared service host, never a remote client, and is never triggered automatically by relay_acquire's vnc option. See relay_console_resolve for what a ready status does and does not prove. A failed or uncertain open keeps the attempt: resolve or cancel it rather than opening again with a new attempt.
| Name | Required | Description | Default |
|---|---|---|---|
| guest | Yes | The guest this call acts on: the name relay_acquire answered. | |
| reason | Yes | Intent of this execution. Retained as evidence, never identity, authorization or a retry key. | |
| expected | Yes | ||
| attempt_id | Yes | ||
| console_id | Yes | ||
| userRequested | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare a non-read-only, non-idempotent, non-destructive operation, and the description adds real context beyond them: the console opens on the declared service host and never a remote client, `reason` is retained as evidence and is never an authorization or retry key, and a failed/uncertain open must not be retried with a new attempt. It stops short of describing permissions or the return payload, so 4 rather than 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and precondition are front-loaded, and each clause carries new information (host placement, no auto-trigger, retry semantics). The middle sentence is semicolon-heavy and slightly run-on, which costs a point, but there is no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 6-parameter, all-required mutation tool with no output schema, the description covers invocation conditions, parameter provenance and failure recovery, and it delegates the meaning of a `ready` status to relay_console_resolve. What the call itself returns is unaddressed, which is the remaining gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 33%, so the description must compensate and largely does: it explains the provenance of `console_id` and `attempt_id`, that `userRequested` must be true, that `reason` is intent evidence, and defines `expected` as what the human should anticipate seeing. Only `guest` is left lightly covered.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb and resource ('Open console viewing'), constrains it to the lease of the named guest, and distinguishes itself from siblings by naming relay_console_resolve, relay_console_cancel and relay_acquire explicitly. An agent can tell this apart from the other relay_* tools without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit precondition ('only following an explicit user request to watch'), an explicit exclusion ('never triggered automatically by relay_acquire's `vnc` option'), and required upstream inputs ('Requires `console_id` and `attempt_id` from relay_console_resolve'). It even states what to do after failure, routing the agent to resolve or cancel instead of reopening.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_console_resolveResolve console statusARead-onlyIdempotent
Refresh this session's non-secret console-viewing status for the lease of the guest that guest names. Read-only, and it never recovers ownership. A ready status means guest preflight only, not that a viewer launched: console operations carry no screenshots and never establish authentication, displayed pixels or a human's confirmation. On macOS, viewing requires the human to choose Standard sharing of the existing console, not a new Log In session or a High Performance display, and the relay never auto-confirms that choice; server_enforced_view_only: false and an unverified viewer selection remain limitations even once transport connects. Never replay an uncertain attempt: resolve it here, or close it with relay_console_cancel.
| Name | Required | Description | Default |
|---|---|---|---|
| guest | Yes | The guest this call acts on: the name relay_acquire answered. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnly, idempotent, non-destructive), and the description goes well beyond them: it explains that 'ready' means guest preflight only, that no screenshots/authentication/pixels are delivered, that ownership is never recovered, and details macOS sharing-mode constraints plus the server_enforced_view_only caveat. This is exactly the operational context an agent needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded and each following sentence adds a distinct caveat rather than padding. The paragraph is dense and long, but the content is substantive rather than repetitive.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with subtle console semantics, no output schema and a single parameter, the description supplies the behavioral and platform nuances needed to interpret status correctly. It is thorough, with only the meaning of non-'ready' status values left implicit.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is one parameter with 100% schema description coverage, so the schema already carries the meaning ('the name relay_acquire answered'). The description adds only that the guest names the lease, a marginal gain, which matches the baseline 3 for high-coverage schemas.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence states a specific verb and resource: refresh this session's non-secret console-viewing status for the lease of the named guest. It is clearly distinguishable from relay_console_cancel, which it names as the contrasting operation for closing an attempt.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit condition for use and an alternative: 'Never replay an uncertain attempt: resolve it here, or close it with relay_console_cancel.' That is clear routing between two siblings, though it does not compare against relay_status or relay_console_open.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_extractExtract declared outputsA
Pull one or more already-declared extraction outputs home early, by names. Only outputs declared on relay_acquire may be pulled; each pull verifies source and host hashes and rejects path traversal, symlinks, or a source that changed underneath it.
| Name | Required | Description | Default |
|---|---|---|---|
| guest | Yes | The guest this call acts on: the name relay_acquire answered. | |
| names | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With annotations already declaring readOnly=false, destructive=false, idempotent=false, openWorld=false, the description adds real behavioral context: each pull verifies source and host hashes and rejects path traversal, symlinks, or a source that changed underneath it. It does not say where outputs land ('home' is vague) or what failure/success looks like, but the validation and security behavior is genuinely beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, action front-loaded, followed by the prerequisite and the safety/rejection behavior. No sentence is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description should explain the return surface, but it never says what the agent gets back (paths, files, status) or what 'home' concretely means. For a 2-param retrieval tool this is a noticeable gap, though the safety and prerequisite coverage offsets part of it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 50%: `guest` is documented in the schema, but `names` has no schema description. The tool description partially compensates by establishing that `names` refers to outputs declared on relay_acquire and that multiple may be pulled at once, but it adds no format or constraint detail for the array. Baseline 3 for the partial coverage gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (pull), resource (already-declared extraction outputs), and scoping mechanism (by `names`). It distinguishes itself from the sibling that declares outputs by noting 'Only outputs declared on relay_acquire may be pulled', so an agent can tell it apart from relay_acquire without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear usage context ('home early', i.e., retrieving outputs before the normal completion flow) and a hard prerequisite tying it to relay_acquire. It stops short of explicitly naming alternatives (relay_finish/relay_release) or stating when NOT to use it, so it is context-rich but not fully exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_finishFinish and deliver evidenceADestructive
Complete one guest's work: extract every declared output, deliver and verify a portable evidence package, then destroy the guest's VM and unregister it; the session's other guests keep running. The result reports delivery, snapshot completeness, execution outcome and human review as separate facts; a verified package is not a passing test, and a delivered package is not itself human approval. Call this, or relay_release, explicitly for every guest before you return, since ending the session does not do it for you. A declared output that was never produced is listed in incompleteExtractions and does not stop the delivery; any other failed extraction or delivery keeps the VM for a corrected attempt.
| Name | Required | Description | Default |
|---|---|---|---|
| guest | Yes | The guest this call acts on: the name relay_acquire answered. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the destructiveHint=true annotation: it names exactly what is destroyed (the guest's VM), what is unregistered, that other guests survive, that an unproduced declared output lands in `incompleteExtractions` without blocking delivery, and that other failures preserve the VM. It also warns that a verified package is not a passing test and delivery is not human approval, which is meaningful behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the main action and sequences the steps compactly despite being a dense paragraph. Most sentences carry distinct, load-bearing meaning about outcomes and error behavior, though a couple of clauses could be tightened.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive, multi-step, no-output-schema tool, the description covers the important ground: the sequence of actions, what the result reports as separate facts (delivery, snapshot completeness, execution outcome, human review), and the failure semantics. Nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one parameter (guest) and schema description coverage is 100%, so the schema already documents its role. The description adds nothing about format or constraints beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific multi-step verb chain (extract outputs, deliver/verify package, destroy VM, unregister) on a named resource (one guest). It clearly separates itself from siblings by noting other guests keep running, and it names relay_release as the related alternative.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly instructs to 'call this, or relay_release, explicitly for every guest before you return' and warns that ending the session does not do it. It also gives the error-path condition (failed extraction keeps the VM). It stops short of stating when relay_release is preferred over relay_finish, leaving that choice slightly ambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_imageRetrieve a saved imageARead-onlyIdempotent
Retrieve one already-saved image; never a new capture, input or directory export, and it never acquires a VM. target selects a display phase (sessionId/executionId/phase: before/after), a declared application file (name, plus a relative path for a directory declaration), or an immutable reference (imageId). PNG, JPEG and WebP only; originals up to 64 MiB and 40,000,000 decoded pixels; the delivered preview is at most 2000x2000 px and 4 MiB of base64 (PNG originals are resampled in-process; JPEG/WebP pass through only within bounds). Each delivery has a 90-second deadline and up to three transfer attempts. A run's after-snapshot is pending in its result; retrieve it here with its imageId or display selector, which waits for its background download within the same deadline. If an image did not arrive, recover it here the same way, never by repeating the input or capturing again, and stop after at most two such recovery calls if it still cannot be inspected. A closed guest returns stale-reference for a display or reference target; its delivered originals stay readable with host file tools. An attached image proves only that the block was included, not that anyone inspected or reviewed it.
| Name | Required | Description | Default |
|---|---|---|---|
| guest | Yes | The guest this call acts on: the name relay_acquire answered. | |
| target | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnly/idempotent/non-destructive), and the description adds the traits that matter operationally: 90-second deadline, three transfer attempts, 64 MiB/40M-pixel originals vs. a 2000x2000/4 MiB delivered preview, in-process PNG resampling, and `stale-reference` behavior for a closed guest. This is far beyond what the annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose and exclusions, then limits, deadlines, and recovery rules — nearly every clause carries information. It is a dense single paragraph, though, and the closing caveat about attachment-vs-inspection is commentary rather than actionable guidance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description still characterizes what comes back (preview dimensions/base64 budget, `pending` snapshots, `stale-reference`), and covers failure recovery and the closed-guest case. An agent has everything needed to call and interpret this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 50% and the nested target variants carry no field descriptions, so the description does real work: it explains the three selector shapes (display phase via sessionId/executionId, declared application file by name plus relative path, immutable imageId reference). It does not add much about `guest` beyond the schema's own note, keeping it just short of 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Retrieve one already-saved image') and immediately bounds it: never a new capture, input or directory export, and never acquires a VM. This lets an agent separate it from relay_acquire/relay_extract without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use (fetching an already-saved image, including a run's `pending` after-snapshot) and when-not (never repeat the input or capture again; recover here instead). It even gives a stop condition — at most two recovery calls — and names the failure mode that selects the recovery path.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_probeProbe host and guest readinessARead-onlyIdempotent
Read-only host and service facts, used to pick an image; it reports current owned state on its own, so call it again after context compaction rather than trusting earlier messages. Default scope: "host" reports host permissions and activity, VM service availability, and every guest this session holds with its state, not guest readiness. scope: "guest" with guest (an already-acquired guest) reports whether Node.js, which staging requires, is available, and, as facts, whether each relay_script language's runtime runs (/bin/sh, node, tsx, python3), the software the image lists in the catalog, and whether this lease's runs take snapshots, without installing or starting anything, and never claims capture readiness. Acquire one guest for each machine the work needs; several may run at once. Continue each guest in order: relay_acquire, relay_stage, relay_script, relay_image/relay_extract, then relay_finish or relay_release, called explicitly for every guest before you return.
| Name | Required | Description | Default |
|---|---|---|---|
| guest | No | The guest this call acts on: the name relay_acquire answered. | |
| scope | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnly/idempotent annotations, it discloses that the tool reports state on its own and must be re-invoked after compaction, that it works 'without installing or starting anything', and that it 'never claims capture readiness'. These are exactly the behavioral limits an agent needs and cannot infer from the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and scope behavior, and nearly every clause carries information. It is a single dense block, though, and the workflow enumeration at the end could be broken out for faster scanning, so it falls just short of ideal structure.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and only two parameters, the description fully specifies what each scope returns and the constraints around guest acquisition and sequencing. An agent has everything needed to invoke it correctly in the relay pipeline.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 50%, but the description compensates by explaining the default ('scope: "host"') and what each scope reports, plus that `guest` must be 'an already-acquired guest' whose name relay_acquire returned. It does not restate the string pattern for `guest`, but the semantics of both parameters are otherwise well covered.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Read-only host and service facts') and immediately gives the concrete reason ('used to pick an image'). It then distinguishes the two scopes precisely, including the caveat that host scope is 'not guest readiness', so an agent can separate this from relay_status and other siblings without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells the agent to call it again after context compaction instead of trusting prior messages. It also lays out the required sequencing ('Continue each guest in order: relay_acquire, relay_stage, relay_script, relay_image/relay_extract, then relay_finish or relay_release') and the cardinality rule ('Acquire one guest for each machine the work needs').
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_releaseRelease the VMADestructive
Abandon one guest's work: destroy the VM of the guest that guest names and unregister it, retaining whatever evidence already exists, without claiming the task succeeded; the session's other guests keep running. Use this instead of relay_finish when the task is not being completed. Safe to retry if a previous release attempt failed; a failed release keeps ownership until destruction is verified. When this session holds no guest, the result is an error saying that nothing was released; a lease acquired under another session identity is never guessed at.
| Name | Required | Description | Default |
|---|---|---|---|
| guest | Yes | The guest this call acts on: the name relay_acquire answered. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructive/non-idempotent, and the description adds real context: evidence is retained, success is not claimed, sibling guests keep running, a failed release keeps ownership until destruction is verified, and another session's lease is never guessed at. The 'safe to retry after failure' note is consistent with idempotentHint=false, since a retry after success errors instead of repeating silently.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense paragraph, front-loaded with the core action and consequence. Nearly every clause carries distinct information (scope, alternative, retry, errors), though the run-on sentence structure is heavier than necessary.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-param destructive tool with no output schema, the description covers the mutation's effects, evidence retention, retry semantics, and failure/error modes. Nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already explains the guest identifier ('the name relay_acquire answered'). The description contextualizes that this name is the target of destruction, but adds no format or resolution detail beyond what the schema provides, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific action chain (destroy the named guest's VM and unregister it) with the scope 'one guest', and explicitly differentiates itself from relay_finish. An agent can distinguish this from relay_acquire/relay_finish without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names the alternative ('Use this instead of relay_finish when the task is not being completed') and gives the selecting condition, plus retry guidance and the no-guest error case. When-to-use and when-not-to-use are both explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_scriptRun a guest scriptA
Run one guest script as one recorded operation; never hide several interactions in one call. A command is a shell script. The script drives whatever the image lists, such as cua-driver, Playwright or Chrome DevTools, through their own libraries or command lines, and its exit status says whether it succeeded, so a script that calls cua-driver checks the driver's answer itself. Requires language (shell, javascript, typescript or python) and inline: with inline: true give the script itself as code (up to 1 MiB); with inline: false give localPath, a host file in the project folder (relative to it or absolute inside it, with no symlinks). Give exactly the one inline names; any other call is refused. It runs in the staged workspace: shell with /bin/sh, JavaScript with the guest's Node.js, TypeScript with tsx from the guest's PATH or else the guest Node.js's own type stripping (22.6 or later), and Python with python3 from the guest's PATH (relay_probe scope=guest reports which run); a missing runtime refuses the run before it starts. Evidence is automatic: the relay records a step derived from the call and, when the image lists cua-driver, snapshots the display before and after. Optional reason (intent, never authorization), step (id, title, expected, inputMode) and snapshots.afterIntervalMs (default 500 ms) enrich or tune the record; consecutive keystrokes may form an explicit text group with snapshots.group (first/member/last). timeoutMs bounds execution (default 120000, up to 3600000). The result carries the outcome and bounded stdout/stderr; it names the after-snapshot as pending with its imageId while the image downloads in the background, so call relay_image with that imageId to see it. A refused, uncertain or nonzero result keeps the VM; never replay input whose effect is uncertain. diagnostic: true, for shell scripts only, records a diagnosis or repair without snapshots, even before relay_stage, running the script as /bin/sh -c; it is never visual verification, and its result has one output field with stdout and stderr combined.
| Name | Required | Description | Default |
|---|---|---|---|
| code | No | With inline: true, the script itself, up to 1 MiB. | |
| step | No | Optional step record overrides; any field left out is derived from the call. | |
| guest | Yes | The guest this call acts on: the name relay_acquire answered. | |
| inline | Yes | true: the script is `code`, given inline. false: the script is the host file `localPath`. Give exactly the one that inline names. | |
| reason | No | Optional intent, retained as evidence. Default: derived from the call. | |
| language | Yes | ||
| localPath | No | With inline: false, a host script file in the project folder: relative to it, or absolute inside it; no symlinks. | |
| snapshots | No | Optional. Consecutive keystrokes may form an explicit text group (first/member/last); only first gets a before-snapshot and only last an after-snapshot. | |
| timeoutMs | No | Execution timeout in milliseconds, default 120000, maximum 3600000. Independent of snapshot delay and lease TTL. | |
| diagnostic | No | Shell scripts only: explicit diagnosis or repair without screenshot evidence, including before staging. The script and its outcome remain recorded. Never claim visual verification. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover mutability/open-world, but the description adds substantial context beyond them: exit-status semantics, VM retention on refused/uncertain/nonzero results, automatic before/after snapshots, the pending imageId handoff, pre-flight refusal when a runtime is missing, and timeout bounds. This is exactly the extra behavioral detail the safety hints cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and cardinality rule are front-loaded, and the density is justified by the tool's complexity. It is a long single block, however, and the runtime/evidence/diagnostic material could be split for faster scanning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description still explains the return shape: outcome plus bounded stdout/stderr, the pending after-snapshot imageId, and the single combined `output` field for diagnostic runs. Nothing essential to calling or interpreting the result is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Though schema coverage is already 90%, the description adds material meaning: which runtimes back each `language`, the exclusive contract between `inline` and `code` vs `localPath` (including symlink and project-folder constraints), what `reason` vs `step` control, and how snapshots.group/afterIntervalMs tune the record. This goes beyond restating schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource and scope: 'Run one guest script as one recorded operation', and immediately adds the cardinality constraint ('never hide several interactions in one call'). This clearly separates it from relay_probe (probe before running) and relay_stage (submit source files).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete routing: use relay_image with the returned imageId to view the after-snapshot, and use diagnostic:true only for shell diagnosis/repair that is 'never visual verification'. It also warns against replaying uncertain input. No explicit statement of when a sibling like relay_stage would be preferable for non-script work, so it falls just short of 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_searchSearch installed applicationsARead-onlyIdempotent
Find an installed application in the image inventories by name, then choose an image yourself; optional before relay_acquire. Matches canonical names and aliases by exact, prefix and substring comparison, with an optional hard os filter (linux/macos). Returns each match's installed versions (which may be null), image keys, OS and architecture; a truncated result means whole installations were omitted, and an empty result is distinct from a catalog error. Never allocates, boots, installs, reserves capacity or chooses an image for you.
| Name | Required | Description | Default |
|---|---|---|---|
| os | No | ||
| name | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint, but the description adds behavioral details: matching logic (exact/prefix/substring), optional os filter, return shape (installed versions possibly null, image keys, OS, architecture), and the distinction between truncated results and empty results versus catalog errors. It also clarifies it never allocates, which adds safety context beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is comprehensive yet efficient, leading with the core purpose, then matching behavior, then return semantics, and ending with what it does not do. Every sentence adds distinct value with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no output schema, the description explains return values (installed versions, image keys, OS, architecture) and handles edge cases (null versions, truncated results, empty vs error). It also specifies the optional filter. For a search tool, this covers everything an agent needs to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema description coverage, the description fully compensates by explaining the 'name' parameter as matching by exact, prefix, and substring, and the 'os' parameter as an optional hard filter with enum values linux/macos. This gives meaning beyond the schema's bare property definitions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a clear verb 'Find' and resource 'installed application in the image inventories', and it explicitly differentiates from relay_acquire by stating it is optional before acquiring and does not choose an image. This distinguishes it from sibling tools like relay_acquire and relay_probe.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states it is optional before relay_acquire, and clarifies what it never does (allocates, boots, installs, reserves capacity, chooses an image), guiding when to use it. It does not explicitly name alternatives, but the context of being a search step is clear. The 'optional before relay_acquire' provides a clear usage slot.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_stageStage the guest runtimeA
Push and hash-check the guest runtime, plus an optional workspace and support files (files, landing under support/), onto the acquired guest that guest names. workspace and each files[].local lie in the project folder, relative to it or absolute inside it, with no symlinks. Staging needs only Node.js in the guest (nodePath, default node); an image without it is refused, because the relay pushes no runtime. Runs take before and after snapshots only when the image lists capture software (cua-driver); otherwise their records have none, and the result's snapshots says which. When the image lists cua-driver, staging waits up to 120 s for it to take a screenshot, since the image starts its daemon at login, and refuses if it never does. cuaDriver names the guest cua-driver the snapshots use. A failed stage can be retried; once staged, only corrected executable paths may be resubmitted, and staging does not prove capture readiness. resetRecording: true archives the current recording's evidence and starts a fresh recording on the same VM; it refuses while a receiver lock is held.
| Name | Required | Description | Default |
|---|---|---|---|
| files | No | ||
| guest | Yes | The guest this call acts on: the name relay_acquire answered. | |
| nodePath | No | Guest Node executable, default node; use image nvm path if needed. | |
| cuaDriver | No | Guest CUA executable; OS default when omitted. | |
| workspace | No | Host directory copied to the guest workspace, opt-in. It lies in the project folder: relative to it, or absolute inside it; no symlinks. | |
| resetRecording | No | Explicitly archive the current recording and start a new session on the same VM. Retains prior evidence; refuses while the receiver lock exists. Use after diagnosing recording damage. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare the generic safety profile (readOnlyHint=false, destructiveHint=false), and the description goes far beyond them: hash-checking, refusal conditions, the 120 s cua-driver capture wait, before/after snapshot dependence on image software, retry limits ('only corrected executable paths may be resubmitted'), and lock behavior. This is exactly the behavioral context an agent cannot infer from the schema or annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The main action is front-loaded, but the body is a dense wall of run-on sentences with nested parentheticals that mix staging, snapshot, and recording semantics. Each clause carries information, yet resetRecording guidance is stranded at the very end and the whole block would benefit from separation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 6-parameter mutation tool with no output schema, the description covers refusals, retry semantics, snapshot dependence, and even return semantics ('the result's snapshots says which'). It stops short of 5 only because the relationship to sibling staging/scripting tools is never established.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is already 83%, so the baseline is 3, but the description adds genuine meaning: `files[].path` destinations land under `support/`, workspace/files locals must be inside the project folder with no symlinks, `nodePath` defaults to `node`, and `cuaDriver` names the executable whose snapshots result. Some of this duplicates the schema, keeping it from a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource chain: 'Push and hash-check the guest runtime, plus an optional workspace and support files... onto the acquired guest.' An agent knows exactly what the tool does. It does not, however, differentiate itself from siblings like relay_script or relay_finish, so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives real when/when-not conditions: an image without Node is refused, staging refuses when cua-driver never captures, resetRecording 'refuses while a receiver lock is held', and 'Use after diagnosing recording damage' is explicit guidance. No alternative sibling (e.g. relay_script) is ever named, so 5 is not warranted.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
relay_statusShow relay statusARead-onlyIdempotent
List every guest this session holds, by the name relay_acquire answered, each with its VM lease (backend binding, guest state, renewal, console observation, last error), staging state and evidence path, plus the most guests the session may hold (maxGuests), the project directory, the VM service origin and the selected environment in use; active:false when no guest's lease is being renewed. Read-only; it does not touch the VMs.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered upstream. The description reinforces this ("Read-only; it does not touch the VMs") — mildly redundant but reassuring — and adds the active:false field semantics, but discloses no pagination, cost, or auth constraints beyond that.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The content is substantive rather than padded, but it is delivered as one very long, clause-heavy sentence dense with internal jargon (backend binding, renewal, console observation, evidence path). It is not front-loaded around a single takeaway, so an agent must parse the whole run-on to extract the essentials.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the full burden of describing the return shape, and it does so thoroughly — naming lease fields, staging state, maxGuests, project directory, VM service origin, and selected environment. An agent knows what it will get back and can call the tool correctly; only the absence of usage/exclusion guidance keeps it from a 5.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so per the rubric the baseline is 4. The description correctly adds no parameter detail because there is nothing to specify.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific resource — every guest the session holds, keyed by the name relay_acquire answered — and enumerates the lease, staging, and session-level fields returned. It is easy to tell this is the introspection/status sibling of relay_acquire and relay_release, though it never names a sibling to make that distinction explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use, when-not-to-use, or alternative routing. Usage is only implied: an agent infers this is the call to make when it wants the current session's guest/lease state. Nothing warns about when another sibling would be more appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
14 tool updates
v0.15.0- Removed
relay_code - Changed
relay_console_cancel2 fields changed- added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "console_id", - "attempt_id" -]New value: +[ + "guest", + "console_id", + "attempt_id" +]
- Changed
relay_console_open2 fields changed- added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "console_id", - "attempt_id", - "userRequested", - "reason", - "expected" -]New value: +[ + "guest", + "console_id", + "attempt_id", + "userRequested", + "reason", + "expected" +]
- Changed
relay_console_resolve2 fields changed- added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +} - added
Input schema / requiredAdded value: +[ + "guest" +]
- Removed
relay_exec - Changed
relay_extract2 fields changed- added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "names" -]New value: +[ + "guest", + "names" +]
- Changed
relay_finish2 fields changed- added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +} - added
Input schema / requiredAdded value: +[ + "guest" +]
- Changed
relay_image2 fields changed- added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "target" -]New value: +[ + "guest", + "target" +]
- Changed
relay_probe1 field changed- added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +}
- Changed
relay_release2 fields changed- added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +} - added
Input schema / requiredAdded value: +[ + "guest" +]
- Removed
relay_run - Changed
relay_script7 fields changed- added
Input schema / properties / codeAdded value: +{ + "description": "With inline: true, the script itself, up to 1 MiB.", + "maxLength": 1048576, + "type": "string" +} - added
Input schema / properties / diagnosticAdded value: +{ + "description": "Shell scripts only: explicit diagnosis or repair without screenshot evidence, including before staging. The script and its outcome remain recorded. Never claim visual verification.", + "type": "boolean" +} - added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +} - added
Input schema / properties / inlineAdded value: +{ + "description": "true: the script is `code`, given inline. false: the script is the host file `localPath`. Give exactly the one that inline names.", + "type": "boolean" +} - changed
Input schema / properties / language / enumPrevious value: -[ - "javascript", - "typescript", - "python" -]New value: +[ + "shell", + "javascript", + "typescript", + "python" +] - added
Input schema / properties / localPath / descriptionAdded value: +"With inline: false, a host script file in the project folder: relative to it, or absolute inside it; no symlinks." - changed
Input schema / requiredPrevious value: -[ - "localPath", - "language" -]New value: +[ + "guest", + "language", + "inline" +]
- Changed
relay_stage5 fields changed- removed
Input schema / properties / browserExecutableRemoved value: -{ - "description": "Guest browser executable for the playwright and chrome-devtools targets; default is installed Google Chrome.", - "type": "string" -} - added
Input schema / properties / files / items / properties / local / descriptionAdded value: +"Host file in the project folder: relative to it, or absolute inside it; no symlinks." - added
Input schema / properties / guestAdded value: +{ + "description": "The guest this call acts on: the name relay_acquire answered.", + "pattern": "^[a-z0-9][a-z0-9-]{0,47}$", + "type": "string" +} - changed
Input schema / properties / workspace / descriptionPrevious value: -"Host directory copied to the guest workspace, opt-in."New value: +"Host directory copied to the guest workspace, opt-in. It lies in the project folder: relative to it, or absolute inside it; no symlinks." - added
Input schema / requiredAdded value: +[ + "guest" +]
- Removed
relay_tools
1 tool update
v0.8.0- Removed
relay_trajectory
20 tool updates
v0.5.1- Removed
relay - Added
relay_acquire - Added
relay_acquisition_capabilities - Added
relay_code - Added
relay_console_cancel - Added
relay_console_open - Added
relay_console_resolve - Added
relay_exec - Added
relay_extract - Added
relay_finish - Added
relay_image - Added
relay_probe - Added
relay_release - Removed
relay_review - Added
relay_run - Added
relay_script - Added
relay_search - Added
relay_stage - Added
relay_tools - Added
relay_trajectory
3 tool updates
v0.3.1- First observed
relay - First observed
relay_review - First observed
relay_status
TDQS
Scored across 14 tools
Each tool maps to a distinct lifecycle step (acquire, stage, script, image, extract, finish/release), and the finish-vs-release vs extract boundary is spelled out in the descriptions. The only real overlap is among the read-only trio (relay_probe, relay_status, relay_acquisition_capabilities) and between relay_image and relay_extract, where an agent must read carefully to pick correctly.
Every tool uses the same relay_ snake_case prefix with a clear verb/noun tail (relay_acquire, relay_stage, relay_script, relay_finish). The three-part console names (relay_console_open/resolve/cancel) are grouped consistently by sub-domain, so the pattern is fully predictable.
14 tools sit squarely in the well-scoped range for a VM relay covering lifecycle, staging, execution, evidence, console viewing and discovery. Each tool corresponds to a real distinct operation and none appear redundant filler.
The surface covers the full lifecycle: discover (search/probe), acquire, stage, run, retrieve evidence (image/extract), and terminate (finish/release), plus console and status. Minor gaps exist, such as no explicit lease-extension or recording-inspection tool, but agents can work around these.
Maintenance
Related MCP Connectors
- TabfleetOAuthcom.tabfleet
Launch, inspect, control, and share isolated cloud browsers for your agents.
Cloud Linux VMs for your agent: run code, Docker, files and HTTPS previews in a Sandboxer microVM.
Run in-product voice interviews with AI agents and analyze source-linked evidence.
Run AI agent tasks in isolated sandboxes with a cost cap, saved workflows and scoped connections.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables autonomous desktop automation by delegating tasks to vision-based agents operating within cloud-based virtual machine sandboxes. It allows users to manage VMs, execute complex computer tasks, and receive text-based screen summaries across Linux, Windows, and macOS environments.2-
- AlicenseNot gradedqualityCmaintenanceEnables a Copilot Studio agent to operate a dedicated, disposable Windows VM by running commands, PowerShell scripts, file operations, and background jobs over authenticated HTTPS.MIT
- AlicenseNot gradedqualityFmaintenanceEnables AI agents to record desktop workflows from human demonstrations, save them as validated semantic skills, and replay them in an isolated Linux sandbox with deterministic verification and evidence-driven self-healing.1MIT
- AlicenseBqualityCmaintenanceEnables AI coding agents to work inside a disposable Windows guest desktop, where they can run shell commands, launch applications, inspect and operate native GUI controls, perform browser DOM actions, and transfer files. Fresh observations, PNG screenshots, and hashed artifacts let agents verify results without touching the host desktop.18MIT