A WebView automation framework for AI agents to interact with web pages through tools like snapshot, click, type, extract, and network inspection. Provides both Koog and MCP adapters for agent integration.
Vitre demonstrates strong tool design with consistent naming (verb_noun pattern), comprehensive parameter descriptions, and well-documented schemas. All 15 tools have clear descriptions (avg ~150 chars) and input schemas with typed parameters. Tool composition is excellent, each tool has a single responsibility and outputs chain naturally (e.g., snapshot returns refs that click/type/extract consume). Error handling is present but generic. Main gaps: no output schemas documented, no error recovery guidance, no tool annotations (readOnlyHint/destructiveHint), and descriptions could be more LLM-optimized (some exceed 200 chars). Naming is consistently strong (list_sessions, acquire_lease, extract_rows). Parameter descriptions are detailed and include constraints (e.g., 'default 200', 'max 20000'). Security is sound, no credentials exposed, lease-based concurrency control is well-designed.
Takes a lease on a WebView, preventing other callers from acting on it until you release it or the lease expires. Use it when a sequence of actions must not be interleaved with another caller's — pass the lease id to every tool in the sequence.
Waits for the page to post a `{id, type, payload}` message of the given type via `window.vitre.postMessage`, and returns it. Matches one the page already sent as well as one still to come, so it is safe to call after the action that triggers it.
Report this session's enabled driver operations and platform limitations without reading the page. Individual actions still require host authorization. Screenshot/cookie management are not exposed.
Waits for a target, then clicks exactly one connected, enabled, visible element. Ambiguous, disabled, hidden, or inert targets are rejected. Success confirms a synthetic DOM click, not completion of the site's operation: inspect the result. If the outcome is unknown, inspect state before retrying because the click may already have taken effect.
Evaluates a JavaScript *expression* in the page and returns its value. The escape hatch for what the other tools cannot express; prefer them, since they resolve elements through the same guarded path. Wrap statements in an IIFE — a bare statement list will not parse.
Output schemas not documented. Tools return results but LLMs cannot see the structure of responses (e.g., what fields snapshot returns, what extract_rows yields). This forces LLMs to infer output structure and risks hallucination.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). LLMs cannot distinguish safe reads from destructive writes without reading descriptions. Annotations enable safer agent planning.
Error handling lacks recovery guidance. Descriptions mention 'fails loudly' and 'rejected' but do not tell LLMs what to do next (retry, ask user, try alternative tool). Generic error responses waste agent reasoning cycles.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 62 | 2026-07-28+ | v2 |
Reads the text, an attribute, or a live DOM property of a single element. For a field the user has typed in, use `from: "property"` with `name: "value"` — the `value` *attribute* holds the markup's original value and does not track typing. For a list of results use `extract_rows`.
Reads one record per matching row, with each column resolved inside that row, as a JSON array. Use this for search results and tables rather than many `extract` calls: a row missing a field yields an empty string in that one record instead of shifting every later record onto the wrong row.
Lists the WebViews you can drive. Call it first if you do not know whether there is more than one; with a single session every other tool's `session` argument can be omitted.
Loads a URL in the WebView and waits for the page to finish loading. Discards every `ref` from the previous page.
Reads the HTTP exchanges this WebView captured — method, URL, status, timing and the response body — newest first. Often a better source than the rendered page: a shop that draws its results from `GET /api/search` hands over typed JSON here, price as a number and stock as a boolean, where the DOM has the same price split across three spans. Narrow with `url_contains`; when you do not yet know which request you want, call it once with `max_body_chars: 0` to list the traffic cheaply, then again naming the one you want. What is visible depends on the platform, and an exchange missing here is NOT evidence of absence: on iOS only the page's own `fetch` and `XMLHttpRequest` calls are ever captured — document loads, images and stylesheets are invisible. On Android and desktop, capture depends on the interception policy and handlers: declined subresources are generally unreported, document/data policies exclude static assets, and main-frame interception may be disabled. The default policy does not intercept network requests. Read the page with `snapshot` and `extract` instead.
Releases a lease you acquired, freeing the WebView for other callers.
Delivers a string to the page as a `MessageEvent('vitre')` on `window`. Only useful for a page written to listen for it — the host app's own pages, not a third-party site.
Shows what is on the page: the interactive and text-bearing elements, each with a `ref` handle you pass to `click`, `type` and `extract`. This is how you look at a page — start here, and take a fresh one after anything that changes the page, because refs from a previous document stop resolving. Returns an indented outline, not HTML.
Replaces the value of an input or textarea with `text` and fires the input and change events a page listens for. Does not press Enter — click the form's button, which a `snapshot` will show you.
Waits until an element is present. Use it after an action that loads content asynchronously, before reading what it loaded.
read_network description is verbose (>400 chars) and buries key details. Includes platform-specific caveats that could be in error responses instead. LLM-optimized descriptions should be 50 - 200 chars.
No pagination guidance for snapshot (max_nodes=200 default). If truncated, description says 'raise it only when the outline says it was truncated' but does not explain how to iterate or handle large DOMs.