Drive a Web Page
Bind an exact browser window, inspect its active tab, and navigate, click, or type without foregrounding it.
Drive a Web Page
Use the browser tools when an agent must act inside Chromium or an Electron
page without raising the native window. The tools bind the (pid, window_id)
you selected to a DevTools target, then issue page input through that exact
binding.
Claude Code starts a YouTube video in Chrome without raising the browser window.
Inspect the selected browser first#
First use list_apps and list_windows as usual, then call
get_browser_state with the selected native (pid, window_id). If that browser
already exposes an owned loopback DevTools endpoint, the driver binds it without
changing the browser.
Do not pass remote-debugging flags or a user-data directory through
launch_app. Cua Driver rejects those flags because they could expose a
person's normal profile to DevTools.
Prepare an isolated browser when required#
If inspection returns browser_requires_setup, call browser_prepare as a
separate approved operation. It starts another Chromium process with a
driver-owned profile; it never copies, modifies, restarts, or terminates the
selected user profile.
In an MCP client, approve the destructive tool call through the client's normal approval UI:
browser_prepare({
"pid": 844,
"session": "research-1",
"allow_launch": true,
"profile": {"mode": "isolated_new"}
})The response includes prepared_pid. Call list_windows for that new process,
then bind its window with get_browser_state. An isolated_new profile is
removed when its owning session ends. Use
profile: {"mode":"isolated_named","name":"research"} only when the
automation profile must survive across sessions.
For direct CLI or raw-socket use, mint a five-minute, single-use token from an interactive terminal first:
cua-driver browser-approve --pid 844 --profile-mode isolated_newPass the returned token as approval_token in the otherwise identical
browser_prepare call. get_browser_state never performs this setup itself.
Attach to an existing Chrome or Edge profile#
Use this route only when the agent must work in a supported Chromium profile that is already running and authenticated. Cua Driver does not restart the browser, copy the profile, or modify profile files. The route supports Chrome and Edge on macOS and Windows, plus Chrome in the validated Linux X11 and Sway configurations. Chromium and Edge have descriptor-backed Linux routes but are not yet product-validated there. Start a named driver session and mint an approval artifact for the exact process, native window, and session:
cua-driver browser-approve \
--strategy existing_profile \
--pid 844 \
--window-id 10725 \
--session research-1The command requires an interactive terminal and asks you to type APPROVE.
Pass its five-minute, single-use token to browser_prepare:
browser_prepare({
"pid": 844,
"window_id": 10725,
"session": "research-1",
"strategy": {"kind": "existing_profile"},
"approval_token": "<token>"
})The browser may display its own remote-debugging consent prompt. Cua Driver will press only the exact browser-owned semantic allow action for this approved process and window. An absent, ambiguous, dismissed, or unrecognized prompt is refused; it is never treated as generic permission to click security dialogs.
If the approved process has no DevTools endpoint, Cua Driver opens a temporary
tab in the approved window and navigates to the product's fixed remote-debugging
page (chrome://inspect/#remote-debugging or
edge://inspect/#remote-debugging). It matches exactly one Allow remote
debugging for this browser instance checkbox, verifies that it is off, and
presses it once. It then proves that the new listener is loopback-only and
owned by the approved process before closing the temporary tab. The result's
side_effects reports whether the page was opened and closed, whether the
address field was focused, whether the setting was enabled, and whether Chrome
displayed a connection-consent prompt. If setup fails after a visible action,
the structured refusal reports detail.setup_side_effects; a checkbox changed
by that failed attempt is restored when its exact state can still be proven.
Any ambiguous control or changed process/window identity is refused.
On Linux, launch the browser with --force-renderer-accessibility unless a
screen reader already enables its complete AT-SPI tree. Native Wayland also
requires a validated compositor route that can prove the exact process,
window, geometry, and temporary focus restoration. A missing prerequisite is
reported as a refusal; Cua Driver does not fall back to an unscoped desktop
click.
After attached_existing_profile, list windows again if needed and call
get_browser_state({pid, window_id, session}). Attachment invalidates older
browser capabilities, so do not reuse a previous target_id, tab_id, or
page ref.
The grant lives only in daemon memory. It expires after inactivity, has an absolute lifetime, and is revoked when its owning session ends or the daemon restarts. A dropped browser socket may reconnect up to three times under the same live grant; a successful reconnect invalidates capabilities again and requires another bind. Browser restart requires a new approval in this first release.
Existing-profile consent turns record redacted action metadata and results but omit screenshots and accessibility snapshots, so authenticated page content is not persisted as consent evidence.
Bind the native window#
Browser capabilities are scoped to a driver session. Start one and pass the same session value to every browser call:
start_session({"session": "research-1"})
get_browser_state({
"pid": 844,
"window_id": 10725,
"session": "research-1"
})Keep the returned opaque target_id. Use the tab_id whose active field is
true only when selection is uniquely proven. active: false is proven
unselected; active: null means the native window cannot distinguish tabs,
such as two tabs with the same title. In that case, select an explicit tab by
its returned metadata and never infer selection from list order. Mutation is
available only when binding_quality is exact. A heuristic or ambiguous
window match is refused.
Snapshot the tab#
Snapshot the selected tab before using an element ref:
get_browser_state({
"target_id": "<target_id>",
"tab_id": "<tab_id>",
"session": "research-1",
"snapshot_format": "semantic_v2"
})Read outline for visible page content. Select actions only from refs, and
check that the chosen entry declares click or type in actions.
content_refs are read capabilities for scope_ref; they cannot be passed to
an unsupported mutation.
If snapshot.continuation is non-null, request the next ranked segment without
changing the page:
get_browser_state({
"target_id": "<target_id>",
"tab_id": "<tab_id>",
"session": "research-1",
"snapshot_format": "semantic_v2",
"continuation": "<opaque_continuation>"
})Use query to find matching roles, accessible names, or visible text. Use a
current scope_ref to inspect one semantic subtree when names repeat in
different regions. A continuation is single-use; a newer snapshot invalidates
it.
Refs are valid only for that session, target, tab, document, frame, and latest
snapshot. Take a fresh snapshot after navigation or whenever a call returns
browser_ref_stale. dom_refs_v1 remains available for compatibility when
snapshot_format is omitted, but new workflows should request semantic_v2.
Click and type#
The default click route requests Chromium's trusted input domain:
browser_click({
"target_id": "<target_id>",
"tab_id": "<tab_id>",
"ref": "p1:7",
"session": "research-1"
})Use viewport x and y instead of ref when the page snapshot cannot name
the rendered target. Standalone Chrome and Edge on Windows have passing
full-background evidence for this route. Standalone Chromium on macOS and Linux
returns browser_input_trust_unavailable because dispatching the trusted
pointer event activates the native browser window.
To request a synthetic full-background DOM click deliberately, pass
input_route: "dom_event" with a ref:
browser_click({
"target_id": "<target_id>",
"tab_id": "<tab_id>",
"ref": "p1:7",
"input_route": "dom_event",
"session": "research-1"
})Use this only when the page's DOM click semantics are acceptable. The driver never silently falls back from trusted input to a DOM event.
Snapshot again, then type into the fresh input ref:
browser_type({
"target_id": "<target_id>",
"tab_id": "<tab_id>",
"ref": "p2:3",
"text": "status: ready",
"session": "research-1"
})The default insert_text mode is efficient for ordinary text. Use
mode: "keystrokes" when the page depends on per-character keyboard events.
Use extended pointer actions#
browser_pointer supports hover, right_click, double_click, scroll,
and drag. Use the trusted route when genuine browser input semantics are
required and the platform reports that it can preserve posture. Use the
explicit synthetic route with current refs when DOM event semantics are
acceptable:
browser_pointer({
"target_id": "<target_id>",
"tab_id": "<tab_id>",
"ref": "p3:4",
"action": "scroll",
"input_route": "dom_event",
"delta_y": 240,
"session": "research-1"
})For drag, pass destination_ref from the same exact frame. The driver refuses
mixed-frame or stale destinations instead of translating them approximately.
Handle a page-owned dialog#
Prime and inspect the exact tab with browser_dialog({action:"inspect"}).
When present is true, pass the returned dialog_id to accept or dismiss.
Only an accepted prompt may include prompt_text. This tool does not handle
browser permission prompts, extension UI, native sheets, or file pickers.
Creating a Chromium native modal can activate its browser window; once you
restore the intended occlusion, inspecting and resolving that exact dialog do
not activate it again on Windows and macOS. Resolution defaults to
delivery_mode:"background". Linux Chromium cannot resolve the native modal
while preserving background posture, so the driver returns
browser_input_trust_unavailable before dispatch. Retry with
delivery_mode:"foreground" only when foreground activation is acceptable.
Assign files without a picker#
Take a semantic snapshot and choose a file-input ref whose actions contains
upload. Call browser_set_input_files with absolute paths to direct regular
files. Symlinks, directories, missing paths, and more than 32 files are
refused. Verify the page's uploaded-file state afterward; the tool response
contains only a count and never echoes the local paths.
Download into an approved directory#
Choose a current ref that activates the download and call browser_download
through an MCP host that presents its destructive-tool approval. Pass an
existing canonical absolute destination_root. The tool temporarily scopes
Chromium's browser-wide download behavior, correlates the exact frame and
opaque download id, restores the default on every outcome, and returns only
the final byte count and opaque id. It does not return a filename, URL, or
local path. A direct raw call without host approval is refused.
Navigate#
browser_navigate({
"target_id": "<target_id>",
"tab_id": "<tab_id>",
"url": "https://example.com",
"session": "research-1"
})Navigation invalidates every prior ref for the tab. Wait for the destination,
then call get_browser_state in snapshot mode again.
End the session#
end_session({"session": "research-1"})Ending the session revokes its target, tab, and element capabilities.
Embedded webviews#
Electron has an exact route only while one proven native window maps to one CDP
page. Adding another page or window invalidates that route. Tauri, WKWebView,
WebKitGTK, and the split-process WebView2 shape currently return
browser_route_unavailable unless the driver can independently prove the
native-host-to-engine relationship. Continue with native get_window_state
and AX/PX actions for those surfaces.
Legacy page actions#
The older page tool remains available for compatibility and for its read
actions. It preserves its first-page and URL-hint behavior, but now shares the
event-aware CDP transport used by the typed browser tools. New browser mutation
workflows should use the typed browser tools so window correlation, endpoint
ownership, capability lifetime, and refusal reasons remain explicit.