agents
Built to be called by agents
Tendril's users are mostly agents. The command line and the MCP server share one typed surface; every result is a JSON envelope with structured errors; and input is serialised, guarded and reversible, because the desktop is shared.
MCP tools
tendril mcp stdio serves the same surface over the Model Context Protocol. The tool list, as the server reports it:
| Tool | Arguments | Does |
|---|---|---|
list | all_apps, x11_display | Discover targets |
list_elements | window, display, camera, include_offscreen, x11_display | Semantic elements |
capture | window, display, camera, format, compression, max_width, max_height, timeout_ms | A screenshot or camera frame |
run | input_definition, a target, restore_focus, no_lock, lock_timeout_ms, lock_stale_ms, lock_path | Input, under the execution lock |
listen | source, duration_ms, format, output | Record audio, or probe |
clipboard_get, clipboard_set | selection, text, serve_ms | The X11 clipboard or primary selection |
permissions | request | Platform permission status |
self_update_*, feedback_* | Updates and feedback from the shared CLI stack |
The x11_display argument lets one server work against a disposable headless display without changing its environment. Through --remote, the MCP stream is passed through to a remote Tendril unchanged.
Envelopes and errors
With --json, and always over MCP, a result is {status, meta: {schema_version, command}, data} or, on failure, {status: "error", meta, error}. Errors are typed, so an agent can branch on them instead of parsing prose. The guard that refuses to type a bare key name returns:
{ "status": "error", "meta": { "schema_version": 1, "command": "run" }, "error": { "category": "validation", "code": "invalid_run_input", "message": "validation error: ambiguous run input `Return` looks like a DSL key token; refusing to type it as literal text", "details": { "action": "Return", "ambiguity": "bare_key_token", "ambiguous_token": "Return", "field": "input_definition", "stage": "parse", "hint": "Use a DSL sequence containing a comma for key taps, for example `wait(1ms),Return`. If you intended to type the literal text, use `send(\"Return\")`." } }}
| Category | Typical codes and details |
|---|---|
validation | invalid_input, invalid_run_input, with the field and a hint |
unsupported_capability | The capability, platform, reason and a suggested action |
missing_permission | Which permission, for what, and how to grant it |
target_not_found | The target kind and id |
platform_adapter_failure | The operation and platform |
timeout | platform_adapter_timeout, with the backend and timeout |
execution_failure, config_error, serialization_error | What failed, and where |
The process exits 0 on success and 1 on any error; the envelope carries the detail. Successful results carry notes for anything an agent should know, such as a fallback taken or a focus restore that was not possible.
The execution lock
Every run, including MCP run calls, writes a ticket and waits until it is first in the queue, then takes the lock atomically and keeps a heartbeat while its input runs. The lock is per user and desktop session, under the temporary directory by default ($TMPDIR/tendril-execution-lock-user-session/), so separate processes and separate MCP servers share it. A run's envelope reports its place in the queue, how long it waited and whether stale locks were reaped. --no-lock opts out for workflows that serialise themselves.
Capture, act, verify
Tendril cannot know whether an action worked; only the screen can. The working rule for agents:
Capture before acting
Find the target and its coordinates from a fresh capture, and convert them to source space.
Act in small runs
A few actions per
run, withwaits where the interface needs time.Capture again
Confirm the change before the next step. If a run reports that focus was not restored, pause and recapture before sending more input.
Prefer elements to pixels
Where an accessibility tree exists,
list-elementsandclick(id)survive layout changes that coordinates do not.
Updates and feedback
Tendril shares the same small libraries as the owner's other command-line tools: one typed surface for the CLI and MCP, tendril update for checked self-updates from tagged releases, and optional feedback that routes breakages to the owning project's board through a webhook. Feedback is off unless configured.