browsertap-mcp
English | 中文文档
Usage guide · Troubleshooting · Security · Privacy · Contributing · Changelog
Browser automation for the Chrome, Edge, or Opera you already use. BTAP connects your MCP client to a browser extension, reusing your open tabs and logged-in profile. Your agent can read pages, fill forms, download attachments, and inspect network activity. Page input runs in the selected tab in the background by default, without moving your desktop cursor.
BTAP controls a real browser profile, not a disposable sandbox. Give it access only to accounts and data that the connected agent may use. First-time extension installation is manual; setup and security are below.
Start in 60 seconds
Claude Code and Codex: install the plugin. It includes the MCP server and caller Skills. Install uv first; dependency downloads and the manual browser step can take longer than a minute.
-
Add BrowserTap to your agent. Choose your client:
Claude Code:
claude plugin marketplace add LinVireo/browsertap-mcp claude plugin install browsertap-mcp@browsertap
Codex:
codex plugin marketplace add LinVireo/browsertap-mcp codex plugin add browsertap-mcp@browsertap
-
Load the browser extension manually. Open a new agent session and ask it to call
get_setup_statusfor theextension_path. Openchrome://extensions, enable Developer mode, choose Load unpacked, and select that directory. Useedge://extensionsoropera://extensionsfor those browsers. -
Start a browser task: "List my open tabs, then summarize the page I select without navigating or closing it."
Plugin setup and updates
also covers browsertap doctor through the plugin's environment.
For Cursor, Claude Desktop and other MCP clients, use the
standard MCP installation below. Existing MCP configurations
continue to work.
Documentation by audience
| Reader | Start here |
|---|---|
| Installing or using BTAP | This README, then the usage guide for workflows and boundaries. |
| Installing the Claude Code or Codex plugin | Plugin guide. |
| Diagnosing a local setup | Troubleshooting, with the output of browsertap doctor. |
| An agent calling BTAP tools | The client's live tool schemas and the optional caller skills. |
| A human or agent changing BTAP | Contributing; coding agents also read AGENTS.md. |
The Tools section is the complete parameter reference for this source tree. For an installed release, use documentation from its matching tag; development checkouts can contain unreleased changes. Runtime capabilities come from the connected server, not from a different version of the README.
Key features
- Read and inspect pages: simplified HTML/text, JavaScript, screenshots, and bounded network/console captures.
- Interact in a background tab:
page_click,page_type,page_press, andpage_draguse trusted CDP input without moving the desktop cursor. - Fill embedded forms: locate, wait for, click, and type into controls in nested same-origin and cross-origin iframes.
- Use the existing profile: authenticated downloads, cookies, storage, bookmarks, extensions, and temporary site-permission leases.
- Control interruptions: explicit dialog policies, condition-based waiting, and operation handles for collecting delayed results without replaying an action.
- Separate concurrent tasks: explicit browser/tab targets and owner-aware cleanup. Different tabs can run concurrently; shared-profile state is not isolated.
- Connect multiple browsers: one bridge can serve Chrome, Edge, Opera, and multiple profiles. Browser-level operations can work without a page tab.
Common workflows
- Read authenticated sites: summarize a selected page, extract a table, or follow links using your existing login.
- Complete web forms: search, filter, and fill controls in ordinary pages, SPAs, and embedded frames.
- Collect files and inspect pages: download reports, capture screenshots, and inspect network requests or console messages.
See the usage guide for step-by-step workflows and the tool reference for parameters.
Capability model and tool results
BTAP exposes three capability layers so an agent can choose the narrowest interface that matches the task:
- Page capability — DOM inspection, JavaScript, waiting, scrolling, screenshots, and
page_*CDP input inside a named tab. This is the default path for ordinary web workflows and does not use the operating system mouse or keyboard. - Browser capability — the real Chromium profile and its browser-native surface: tabs, downloads, cookies, storage, permissions, bookmarks, extensions, service-worker messaging, and raw CDP. These operations can work without a foreground tab.
- Desktop capability — explicit inspection and cancellation attempts for a foreground
Windows standard file dialog owned by registered Chrome or Edge. Call
inspect_native_file_dialogfirst, then use its short-lived ticket withcancel_native_file_dialog; both requiredesktop_opt_in=trueand[desktop]. Other browser UI and unsupported native layouts remain outside this surface. The seven global OS input/screenshot tools removed in 0.5.0 remain removed.
resolve_leave_dialog remains a page-scoped, lab-only recovery workflow; its
final Enter fallback is a restricted exception, not a general desktop surface.
get_setup_status returns tool counts and capability groups in
data.capability_registry. MCP tools/list supplies the actual tool schemas and
explicit readOnlyHint, destructiveHint, idempotentHint, and openWorldHint
for every tool. These describe all supported parameter paths: tools with optional
script execution, buffer clearing, or file writes are not classified as read-only.
get_setup_status can tighten an existing Windows token file's ACL and is also
classified as state-changing.
Annotations help hosts plan calls; they do not grant permission or replace
BTAP's ownership and concurrency checks.
Every public tool now returns the
btap.result.v1 envelope without renaming tools: successful operation data is
in data, explicit legacy failure payloads remain in legacy, and
error/error_code, retryable, target, and diagnostics provide stable
machine-readable status. Failures also set MCP isError=true. Arrays, objects,
HTML, and other large bodies are read from data or legacy; only small scalar
operation fields are projected at the top level for compatibility. Use the
envelope's retry verdict: an explicit retry_safe=false or possible execution
overrides a connection error's usual retry hint.
Requirements
- Python 3.10+
- Chrome, Edge, or Opera
- Linux, macOS, or Windows. The ordinary page, browser, and CDP tools do not
require OS-level input. The lab-only
resolve_leave_dialogfallback and the Windows native-file-dialog tools need a usable desktop session. - A running Chromium user session with the BrowserTap Bridge extension loaded.
- Claude Code, or any other MCP client
Getting started
1. Install
Create a virtual environment and install the package. The optional desktop
extra is needed for the lab-only physical fallback or explicit native-file-dialog tools:
Windows PowerShell
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install browsertap-mcp
.\.venv\Scripts\browsertap.exe extension-path
Linux or macOS
python -m venv .venv
./.venv/bin/python -m pip install browsertap-mcp
./.venv/bin/browsertap extension-path
The core install (pip install browsertap-mcp) is sufficient for page, browser,
and CDP tools. It omits pyautogui, mss, and pillow, which are used by the
lab-only resolve_leave_dialog Enter fallback and its screen/input checks.
The explicit Windows native-file-dialog tools also require [desktop].
To work on the project rather than only use it, install the checkout as editable instead. Same extras; the extension directory and the skills are then read straight out of the tree:
git clone https://github.com/LinVireo/browsertap-mcp.git
cd browsertap-mcp
python -m venv .venv
./.venv/bin/python -m pip install -e ".[dev,desktop]"
./.venv/bin/browsertap extension-path
2. Load the Chrome extension
This project ships an unpacked extension that has to be loaded once by hand.
browsertap extension-path
Open chrome://extensions, turn on Developer mode, click Load unpacked, and pick the directory that command printed.
The loaded extension is listed as BrowserTap Bridge.
If you also use Edge or Opera, repeat the same steps at edge://extensions or opera://extensions with the same directory. The bridge tells the browsers apart automatically.
Then open a normal http:// or https:// page. A blank tab is not enough — content scripts cannot run on about:blank, so no session is established.
Connection status badge
The extension may show a small BTAP: checking, BTAP: connected, or
BTAP: disconnected badge on pages. The badge is presentation-only: it reports
the bridge connection state and does not display page content, cookies, tokens,
or URLs. Open the extension popup and clear Show connection status on pages
to hide it. Hiding the badge does not stop the bridge, keepalive, or automatic
reconnect behavior.
The extension popup
The popup holds the badge toggle above and two buttons, both of which act on the tab you are looking at:
| Control | What it does |
|---|---|
| Refresh | Lists that tab's cookies including their values, and including HttpOnly ones the page's own JavaScript cannot read. It is a debugging aid, so it deliberately shows what a document.cookie dump would hide. |
| Copy | Writes every listed cookie to the system clipboard as name=value lines. |
Treat both as handling live credentials: anything that later reads your clipboard receives session cookies, and a screenshot of the popup captures them. See SECURITY.md for where this sits in the threat model.
3. Add the server to your client
Standard config works in most tools:
{
"mcpServers": {
"browsertap": {
"type": "stdio",
"command": "browsertap"
}
}
}
If you installed into a virtualenv, point command at the executable's absolute path instead — relying on PATH is the most common reason a client fails to start the server.
Claude Code
claude mcp add browsertap -- browsertap
Add --scope user to make it available across all projects. For a virtualenv install:
claude mcp add browsertap -- /absolute/path/to/.venv/bin/browsertap
On Windows PowerShell, use the absolute path to
.venv\Scripts\browsertap.exe instead.
Verify with /mcp.
Claude Desktop
Follow the MCP install guide and use the standard config above. An example file is included at examples/claude-desktop-config.json.
Cursor
Put the standard config in .cursor/mcp.json for one project, or ~/.cursor/mcp.json globally. An example file is included at examples/cursor-mcp.json.
VS Code
code --add-mcp '{"name":"browsertap-mcp","command":"browsertap"}'
Or write it into .vscode/mcp.json by hand — note that VS Code's key is servers, not mcpServers.
Hermes
Add to ~/.hermes/config.yaml:
mcp_servers:
browsertap:
command: browsertap
timeout: 120
connect_timeout: 60
browsertap print-hermes-config prints this snippet. An example file is included at examples/hermes-config.yaml. Verify with hermes mcp list.
Other clients
Any MCP client that speaks stdio will work. Follow its own install guide and use the standard config above.
Your first prompt
Once the extension is loaded and a normal page is open, try:
What tabs do I have open? Read the current page and summarise it.
If tabs come back empty, run browsertap doctor.
For the least disruptive workflow, start with docs/USAGE.md: it explains which operations stay in a background tab, when foreground activation is needed, how the remaining physical fallback is gated, and when an image-capable model is useful.
Configuration
Environment variables
| Variable | Default | Purpose |
|---|---|---|
BROWSERTAP_BRIDGE_HOST |
127.0.0.1 |
Bridge bind address. |
BROWSERTAP_BRIDGE_PORT |
18765 |
Integer from 1 through 65533. WebSocket uses this base, HTTP uses PORT+1, and the host lock uses PORT+2. Invalid values fail before network or spawn actions. The separate spawn.lock file prevents concurrent daemon starts. For a custom port, also configure the extension — see docs/TROUBLESHOOTING.md. |
BROWSERTAP_STATE_DIR |
~/.browsertap |
Override the state directory. A nonempty relative path is anchored to the launching process's working directory before daemon spawn. Existing legacy-directory fallback remains available when no override is set. |
BROWSERTAP_NO_SPAWN |
unset | Set to 1 to stop the MCP server from auto-starting the bridge. Use it when you run the bridge yourself. |
BROWSERTAP_BRIDGE_AUTH |
enabled | Set to off only for an explicitly trusted local compatibility setup. By default BTAP authenticates /link with a persistent per-user token. |
BROWSERTAP_BRIDGE_TOKEN_FILE |
~/.browsertap/bridge-token |
Override the shared token file location; relative paths use the launching process's working directory. Without this override, the token lives in the selected state directory. Editors do not need individual tokens. |
BROWSERTAP_BRIDGE_TOKEN |
unset | Legacy one-time migration source. If the token file does not exist, BTAP imports this value once; the file wins thereafter. |
BROWSERTAP_PREFERRED_BROWSER |
unset | chrome, edge, or opera. Which browser wins when several are connected and no tab is specified. |
BROWSERTAP_MODE |
lab |
lab prioritizes uninterrupted automation and skips physical-input/site-allow elicitation; safe prompts for every such action. set_automation_profile changes only the current MCP process. |
BROWSERTAP_ALLOW_UNSAFE_CDP |
unset | Raw CDP blocks the high-risk methods listed in SECURITY.md. 1 permits them only in lab; safe retains the guard. Other allowed methods can still change page or profile state. |
BROWSERTAP_LAB_NO_ELICIT |
enabled | Lab skips elicitation by default. Set this to 0/false only when you want session-level lab approval prompts; the cross-process lock, quiet-input gate, foreground confirmation, and ownership checks always apply. |
BROWSERTAP_AUTO_BEFOREUNLOAD_HOSTS |
shell.,ttyd,code-server,jupyter,vscode-web |
In lab, ordinary open_url accepts beforeunload on matching current hosts. intent_leave=false always preserves the page. |
BROWSERTAP_WS_ALLOWED_ORIGINS |
unset | Comma-separated exact extra origins for WebSocket and HTTP. The packaged extension ID is pinned by default, from its manifest key or unpacked path; separate copies need their own explicit Origin. HTTP token authentication still applies, including without Origin. |
BROWSERTAP_WS_ALLOW_NO_ORIGIN |
unset | Set to 1 only for a trusted non-browser local WebSocket client that cannot send Origin. The default rejects origin-less clients. |
CLI
browsertap # run the MCP server (stdio)
browsertap --version # print the installed package version
browsertap extension-path # print the unpacked extension directory
browsertap skill-path # print the directory holding the shipped agent skills
browsertap doctor # diagnose the local setup, as JSON
browsertap bridge # run the bridge in the foreground
browsertap bridge --restart # restart the managed bridge; does not touch the browser
browsertap bridge --stop # stop the exact managed bridge process
browsertap print-hermes-config # print a Hermes config snippet
doctor reports the extension path, port state, and connected tab count. It also
returns a structured verdict: cause is one of healthy, starting,
ext_never_registered, sw_slept_or_dropped, registering, or
bridge_unreachable, and advice is the matching one-line fix. starting
means the bridge has just started and is waiting for the extension handshake;
its action is wait_for_extension, so wait a few seconds and run doctor
again. registering means the extension is connected but no normal http(s)
content tab is ready.
When no extension runtime status is available after startup, the setup status is
extension_unavailable with action: check_extension_connection. Check that
BrowserTap Bridge is enabled in the intended browser and retry doctor.
extension_status_available=false means compatibility is still unknown;
missing runtime data alone does not request an extension Reload. Confirmed old
bridge or MCP versions retain their own restart action, including during startup.
doctor prints JSON on stdout even for invalid configuration. An invalid base
port reports status: "initialization_failed", action: "check_config",
error and error_type before network or spawn actions. Imports, help/version
and package-path commands remain usable. DNS/socket failures during later port
probes preserve the available setup diagnosis and add port_probe_errors;
an affected port's state is null. Exit status is 0 only for healthy or
starting without probe errors. A malformed bridge diagnosis reports
cause: "bridge_unreachable", ok: false and error_code: "malformed_diagnosis";
it does not establish that the bridge is an old build.
state_paths.token_file_status distinguishes missing, empty, ready,
unreadable and invalid_encoding. token_file_error gives a safe reason;
only ready content has a fingerprint. Existence fields are null when metadata
cannot be read. Unknown default-directory metadata keeps the canonical path;
legacy selection requires the default directory to be confirmed absent.
Existing empty or unreadable files are not overwritten.
mcp_build_verdict and bridge_build_verdict compare each process's package-import
source snapshot with disk: matches_tree, stale_process, or unverifiable.
The snapshot includes package Python files and import-cached JavaScript used
for result conversion, guarded execution, scoped dialogs and page inspection.
Same-version edits still require the corresponding MCP or bridge restart.
Missing assets or older identity schemas cannot prove a match. This source
identity does not attest runtime monkeypatches, cached bytecode or arbitrary
other assets; *_build_enforced=false is unknown.
BTAP creates ~/.browsertap/bridge-token on first use and every bridge/MCP
process reads that same file. Closing browsers or editors does not rotate it. Removing
the browser extension or reinstalling the Python package deliberately leaves the token
file in place, so a reinstall continues to work. A full user-data purge may delete the
whole ~/.browsertap directory only after all BTAP bridge processes have stopped;
the next start then creates a new token.
Agent skills (optional)
The Claude Code and Codex plugins load these Skills automatically. This section is for clients configured through the standard MCP installation.
BTAP includes the primary workflow and recovery rules in MCP initialization
instructions and tool descriptions. The same two packaged Markdown guides are
also discoverable through MCP resources/list and readable with resources/read:
browsertap://agent/workflow and browsertap://agent/recovery. A client can read
them without installing Skills. Hosts decide when to load resources; the critical
target, ownership and retry rules remain in the instructions and tool results.
browsertap skill-path # e.g. .../site-packages/browsertap_mcp/skills
That directory contains:
| Skill | What it is for |
|---|---|
browsertap-default/SKILL.md |
The calling contract: pick a target before acting, open your own tab for anything that mutates a page, close it in cleanup, and how to react to no_response / switched_session / bridge_error. |
browsertap-bridge-recovery/SKILL.md |
Recovery when the transport itself is down: which of the three components is stale, and the one restart or reload that fixes it. |
These are instructions for an agent using BTAP, not for an agent editing BTAP's source. Coding rules and test commands belong in AGENTS.md and Contributing.
Point your client's skill manager at that directory rather than copying the
files. A copy looks correct for as long as the contents happen to agree, then
silently stops receiving updates when you upgrade the package. If you keep copies
anyway, python -m scripts.check_tool_docs --check-installed-skills --skill-mirror DIR compares them against the shipped originals and names whichever one drifted.
Upgrade
Plugin users: follow plugin updates. The steps below apply to a Python package installation.
The marker below is maintained with this source tree. It is not proof that a development checkout has been published; compare the installed package with its release tag before using new tool signatures or the 0.5.0 migration notes.
Current release: unified Python package, bridge, and unpacked Chrome extension 0.5.4.
The three components load updates separately:
- Update the installed package with
pip install -U browsertap-mcp(keep[desktop]if you use that extra), then restart its MCP session. - Run
browsertap doctor. If it requestsrestart_bridge, usebrowsertap bridge --restartto load the daemon's updated code. - If it requests
reload_extension, openchrome://extensionsand press Reload on BrowserTap Bridge. Extension source changes require that manual step.
browsertap doctor reports which part is stale and names the one action
that fixes it: reload_extension, restart_bridge, or restart_mcp_session.
The other two will not help, so read the field rather than doing all three.
Uninstall
- Stop the managed daemon with
browsertap bridge --stop. - Open
chrome://extensions(or the equivalent page in Edge/Opera) and remove the unpacked BrowserTap Bridge extension. - Remove the
browsertapentry from each MCP client's configuration. - Run
pip uninstall browsertap-mcpin the environment where it was installed. If you created a dedicated virtual environment, remove that specific environment after deactivating it. - Optional full cleanup: after confirming every BTAP bridge is stopped, remove
~/.browsertap. This deletes the persistent bridge token and logs; the data is retained by default so reinstalling continues to work without reconfiguration.
How it works
Three layers:
- Chrome extension (MV3) — injected into real pages, reaches
tabs,cookies,debugger, andmanagementthrough Chrome APIs. - BrowserBridge — a local daemon on
127.0.0.1:18765(WebSocket) and:18766(HTTP). It owns the extension connections, tracks sessions, and relays results. It runs detached from any MCP instance, and the MCP server starts it on demand with no console window.client_ididentifies one connected browser/profile instance;session_idis its current compositeclientId:tabIdhandle, not a permanent tab identity. Connected rows may also carrytab_identity. Several browsers and profiles coexist. - MCP server — exposes the whole thing as MCP tools. Each agent's MCP process keeps its own selected target and tab ownership registry.
Two channels reach the browser: a per-tab session channel, and a direct channel to the extension's service worker. The second one is why some tools keep working when every tab is closed.
Behaviour you should know before driving it
Selecting a tab does not raise it. switch_tab defaults to activate=false: it only changes which tab later calls target. Nothing moves on screen until you call activate_tab, pass switch_tab(activate=true), or approve a physical-input action. Page reading, JS, and the page_* input tools all work on a background tab.
One coordinate space, inside the tab. page_click/page_drag take viewport coordinates inside one tab and are dispatched through CDP — no cursor movement, no window focus, foreground_changed: false in the reply. There is no desktop-coordinate tool to confuse them with any more: the ones that took physical screen pixels were removed in 0.5.0.
Two pixel units, and the screenshot does not use the one you click with. Viewport coordinates are CSS pixels — the space getBoundingClientRect reports. A page screenshot comes back in device pixels, which is CSS × devicePixelRatio, so at 125% display scaling a point read off the picture is 25% too large for page_click; capture_page_screenshot reports image_width/image_height and pixel_space: "device" so the factor is visible instead of assumed. Reading a point off a picture is the one path with no hit test — prefer a scan_page selector, which is checked against the page before anything is dispatched.
Automation profiles. With BROWSERTAP_MODE unset, BTAP defaults to lab with BROWSERTAP_LAB_NO_ELICIT=1 semantics. Lab permits site allow and the restricted leave-dialog fallback without elicitation; safe prompts for each site allow and refuses the physical Enter fallback. Neither profile is a confirmation prompt for every browser action. Ownership and target checks still apply, and the physical path keeps its OS lock, quiet-input gate and on_screen check. input_quiet.enforced says whether comparable input markers were available.
Raw CDP. get_automation_profile.raw_cdp_policy reports guarded or
allow_unsafe. Single commands and entire batches are checked before dispatch;
use dedicated tools for tab closure, cookies, permissions and user-agent changes.
The guard covers common destructive methods, while allowed JavaScript/CDP can
still change a page. The complete policy and explicit lab override are in
SECURITY.md.
Dialogs are explicit. execute_js(dialog_policy=...), open_url(beforeunload=...), and handle_dialog(action=...) take dismiss (default), accept, or manual. The global default still preserves the page; only an explicit accept or lab's configured shell/IDE host heuristic leaves automatically. handle_dialog answers within three seconds or reports no_dialog/an explicit error. resolve_leave_dialog tries protocol accept twice and uses physical Enter only as a final, lab-approved fallback.
The extension route for execute_js prepares the current injectable frames for
accept/dismiss before running the caller and restores temporary helpers on
completion or expiry. The Python CDP fallback for an older command router covers
only its current evaluation context. New documents do not inherit either scope.
Preparation shares the command deadline; a caller's CSP-like error or an
uncertain result never justifies replaying it.
Permissions use temporary leases. set_site_permission covers one origin for 60–600 seconds, records the prior setting, and attempts restoration on expiry/reset/service-worker restart. safe prompts for every allow; default lab applies it without elicitation. Unsupported restoration is retained as manual_recovery with the prior setting and recovery guidance, and automatic retries stop. Correct the cause before an explicit reset_site_permissions retry. Unsupported initial grants return unsupported or requires_user_action.
Challenges stay in your browser. A Cloudflare Turnstile or similar widget is handled in the same connected tab, by page_click, with a bounded number of attempts. When the challenge has not moved, the result is challenge_stalled and BTAP stops so you can finish it yourself in that same tab. BTAP never launches Playwright, a headless browser, or a separate automation profile as a fallback — the whole point is your real, logged-in session.
Changed tools need a reload. Tool schemas and descriptions are read once when your client starts the MCP server; after upgrading, restart the MCP session or your client, or you will keep calling the old signatures. Extension changes need a manual reload at chrome://extensions — chrome.runtime.reload() restarts the service worker without re-reading the files from disk.
Tab ownership in concurrent tasks
Classify every tab before using it. A U (user) tab existed in the first list_tabs snapshot; do not close it or navigate it by default. An A (agent) tab is created by this task's open_new_tab; save its session_id, generation, and owner_id, pass that explicit session to every operation, and call close_tabs(..., owner_id=...) in cleanup. A B (borrowed) tab is a temporarily used U tab; record its original_url and never close it. Restore a URL changed by this task only after confirming the same tab lifecycle still exists and the user has not since navigated it elsewhere.
Decision order: run list_tabs; borrow an existing match only for read-only/light work; open an A tab for searches, filters, sorting, pagination, scrolling, expand/collapse, navigation, forms, or other actions that change the page view or state; open an A tab when no match exists; finally close only A tabs. Never register the initial tab snapshot as owned, close a U/B tab, omit the explicit target for state changes, reuse an old native tab id, omit generation-aware cleanup, or leak an A tab.
For parallel agents, use a separate A tab per agent and explicit session_id
on each call. Different tabs can run concurrently both within one MCP process
and across independent MCP processes. Each process has its own default tab;
agents sharing a process also share that default, so switch_tab is not an
agent identity. Each call snapshots its target, and an explicitly targeted call
does not change another call's target or the process default.
A cooperative target lock covers each complete MCP call and its internal browser
roundtrips. A competing call to the same tab returns target_busy. The bridge
also records reservations for dispatched commands: wait=false and some timeout
paths retain the reservation pending a definitive browser result, a confirmed
tab lifecycle end, or the bounded retention expiry described below.
Claim an execute_js result with get_execute_js_result from the
same MCP session; do not replay a script whose result is pending. Direct /link
and Python driver calls receive the bridge's per-command reservation, but need
an MCP command scope for the lock across multiple browser roundtrips.
A script deadline, debugger timeout or detach can leave JavaScript running. Dispatched
exec_timeout replies retain the same bounded reservation as other uncertain outcomes:
status=in_progress, operation_status=outcome_unknown, and
reservation_held=true. After that retention window, the receipt stays unknown
and retry_safe=false. Neither a failed receipt
nor an expired reservation proves execution stopped; avoid replay or conflicting
work in that tab while the outcome is uncertain. Manual dialogs keep their
reservation, and only the originating MCP session can call handle_dialog.
Handling a dialog does not prove the script finished: when the extension cannot
return its final result, the operation remains outcome_unknown. The caller can
close its own tab with close_tabs(..., owner_id=...) to end that lifecycle.
Console and network capture mutations belong to the MCP session that started
them. Another session cannot restart, stop, or clear that capture (capture_busy);
ordinary page operations and non-clearing console reads remain available. After
the owning MCP process has exited, another session can reclaim its capture.
Process exit alone does not cancel page JavaScript or release a pending target;
the tab owner can close its own tab to recover. Agents
sharing one MCP process share this ownership too. These guards do not make a
multi-step workflow atomic or isolate cookies/storage shared by a profile.
Structured statuses and recovery fields
Read ok and error_code first; operation statuses remain in data/legacy
and as small top-level compatibility fields. Failures set MCP isError=true:
status / error_code |
Meaning |
|---|---|
ok / success |
Completed and verified as far as the protocol allows. |
redirected |
Navigation landed on a different URL than requested (login wall, SSO, canonical rewrite). |
navigated |
An execute_js script navigated the page, so its return value is genuinely gone; landed_url says where it went. |
blocked_by_dialog |
A JavaScript dialog is open and waiting for handle_dialog. |
blocked_by_beforeunload |
Navigation was cancelled to keep the page; re-issue with beforeunload="accept" to leave. |
dialog_handle_failed |
A dialog was seen but answering it failed; the tab may still be blocked. |
navigation_failed / navigation_timeout |
open_url did not complete within its timeout, or the browser reported an error. |
triggered with type="download" |
open_url was replaced by a browser download. ERR_ABORTED can be normal only when CDP also reports isDownload=true; use download_file for completion and the local path. |
requires_user_action |
The action needs user intervention; approval failures include reason (elicitation_unsupported, declined, timeout, cancelled, or error) and do not execute the action. |
raw_cdp_blocked |
A raw method bypasses a protected state/ownership path. No command was dispatched; use a dedicated tool or resolve the operator configuration instead of retrying unchanged. |
busy |
Another BTAP process holds the physical-input lock, or the tab already has a pending manual execution. Returned immediately, never queued. |
target_busy |
This tab is reserved by another call or a still-pending browser command. Check delivery_state and retry_safe; a call involving multiple tabs may have completed earlier steps. |
capture_busy |
Another MCP session owns this console/network capture. Its owner must stop it before another session can restart, stop, or clear it. |
ambiguous_browser |
Several browser/profile instances match and no unique browser was selected. Use list_tabs, then pass the chosen full session_id (or client_id where supported). |
input_activity_detected |
You used the mouse or keyboard during the post-approval quiet window, so no physical input was sent. |
activation_failed |
The target tab could not be confirmed on screen, so no physical input was sent. |
unsupported |
The browser or extension API cannot provide this (e.g. clipboard permission leases). |
challenge_stalled |
A browser challenge made no progress within the attempt bound; hand the tab back to the user. |
no_response |
The script did not reach the tab or timed out — do not blindly retry anything with side effects. |
not_found |
The selector matched nothing; no input was dispatched. |
bridge_error |
A bridge call failed. It may appear as error_code or a diagnostic field rather than the top-level status; run list_tabs/doctor before retrying. |
switched_session |
Supplemental field indicating that only an implicit dead default was replaced with another live tab. Verify the new target before continuing; explicitly directed dead sessions are never substituted. |
For delivery failures, only delivery_state="undelivered" proves the operation
was not sent; retry_safe=false still prevents replay. sent_unconfirmed means its ACK or HTTP response is missing, so it
must not trigger an automatic replay. Treat delivered_no_result, navigated,
and unknown delivery as potentially executed; recover by operation ID when one
is available and inspect the page before deciding the next action.
Complete JSON results
Large JS values and strings containing unpaired UTF-16 code units are returned
through result_file. When result_file_encoding="json", first parse the
path field once as JSON; otherwise use the path directly. Then read that file
as UTF-8 JSON and verify result_bytes and result_sha256 against its bytes.
The path encoding is separate from the file contents. result_file_scope
identifies those contents:
| Scope | JSON contents |
|---|---|
js-value |
The complete converted JavaScript value; its inline value is null. |
envelope |
The complete original v1 tool envelope. |
mcp-call-result |
The complete native MCP result after normal envelope adaptation, including original content and metadata. |
JS descriptors are in data, or in legacy.late_result for a late reply.
Unrepresentable strings elsewhere in a tool result use the latter two scopes
and retain a valid structured decision header with the original ok, isError
and retry verdicts. result_content_externalized / result_meta_externalized
identify native content or metadata moved into the archive.
If the file cannot be written, parse result_json once as JSON;
result_json_scope uses the same scope names. This explicit inline fallback
can exceed the usual size limit and keeps the original operation receipt.
For envelope/MCP scopes it is at the header root, with a result_json_ref in
data/legacy. result_file_error.message_json is a JSON-encoded I/O message.
An error field marked message_encoding="json" or code_encoding="json"
(also error_code_encoding) must likewise be parsed once. These representations
preserve the original UTF-16 values; export failure never permits replay.
Disclaimers
This server exposes your real browser profile to the connected MCP client, including logged-in sessions. Its scope is browser automation, with only the restricted physical leave-dialog fallback described below.
- One physical-input path is left after 0.5.0:
resolve_leave_dialog's Enter fallback,labonly, and only after two protocol-level attempts fail. It is real OS-level input rather than a synthetic page event, so it lands on whatever is on screen;saferefuses to send it at all. Thepage_*tools carry none of this exposure. - Page content is untrusted input. A page your agent reads can attempt prompt injection, and the tools available make that consequential.
- This is not a security boundary. See MCP Security Best Practices.
- Avoid pointing it at sensitive accounts you would not want an MCP client to see, and prefer not to run it on shared or production machines.
The extension requests broad permissions because the feature set requires them:
cookies, tabs, debugger, scripting, alarms, storage,
contentSettings, declarativeNetRequest, management, bookmarks,
downloads, and <all_urls>. declarativeNetRequest temporarily removes CSP
response headers only from the tab executing an eval-based command. The rule is
session-scoped, reference-counted, and removed in cleanup; it is not a
browser-wide persistent CSP override. See Security for the full
permission and loopback threat model.
Tools
Most tools accept an optional session_id to target one specific tab; omitting it
uses this MCP process's current target. Pass it explicitly for state changes.
client_id distinguishes connected browser/profile instances; session_id is a
composite handle such as chrome_a1b2c3:456. Pass returned handles verbatim.
With multiple instances connected and no browser selected, routing returns
ambiguous_browser. Select a full session_id from list_tabs with switch_tab,
or supply it to the operation; open_new_tab also accepts client_id.
browser="chrome" alone is insufficient when multiple Chrome profiles match.
If Chrome reports an evidence-backed replacement for the same tab, BTAP returns
rebound_from, replacement_session_id, and tab_identity; otherwise an
explicit stale handle is refused. Tools marked no tab needed use the
extension's service worker and work with zero tabs, but still need a unique
browser/profile selection.
Tabs and navigation
- get_setup_status — report
package_version,bridge_version,extension_version,protocol_version, connection state, ports, tabs, and the required recovery action. A missing bridge listener is started automatically when spawning is enabled;restart_bridge_required=truemeans a bridge that is still running must be replaced withbrowsertap bridge --restart.reload_extension_required=trueidentifies the unpacked-extension platform limit and requires a manual Reload; a version number that differs on its own no longer sets it, because Chrome parsesmanifest.jsonat load time and never re-parses it without a Reload, so a release bump would otherwise demand a click whose only effect is on that number.restart_mcp_session_required=trueis the opposite direction: a component is newer than the running server, so the stale build is this process and only restarting the MCP session or client clears it — the other two flags stay false, because a restart or reload would report the same mismatch again.extension_build_stampis the stronger signal and answers the question the four version fields cannot: it is a hash of the extension sources compiled intobackground.js, reported by the worker actually running, so comparing it toexpected_extension_build_stamp(a fresh hash of the directory) is decisive in both directions where version equality was measured wrong twice. Read the answer fromextension_build_verdict:matches_tree(the worker is running this code),stale_worker(it is not -- Reload),stamp_not_regenerated(an extension file was edited without runningpython -m scripts.extension_stamp --write, so the comparison proves nothing either way) orunverifiable(the extension predates the stamp, or the directory could not be read -- seeextension_build_error).extension_build_enforced=falsemeans no comparison happened, so treat it as unknown rather than as a pass. Answers while another tool is still running;default_session_idis this request's snapshot of the MCP process default anddefault_session_settled=truebecause another call's temporary target is isolated. No parameters.extension_status_available=falsemeans the extension has not supplied runtime status:startingasks towait_for_extension, andextension_unavailableasks tocheck_extension_connection. Missing status alone does not request a Reload. Compatibility checks, including legacy replies missing required fields, apply once runtime status is available. On Windows, inspecting an existing token file can tighten its ACL to the current user. The token-file state check itself does not create a missing file;get_setup_statusmay still create one during bridge startup or authentication initialization. - get_automation_profile — inspect whether the current MCP process uses
laborsafe. - set_automation_profile — switch the current MCP process between
lab|safe; the override is not persisted and does not reload the extension.mode(string):laborsafe
- list_tabs — list connected tabs under
data.tabs, including their full session handles andbrowserfields. Answers while another tool is still running;default_session_idis this request's snapshot of the MCP process default anddefault_session_settled=true. Parallel agents should still pass an explicit target. No parameters. A timed-out inventory releases that read's bridge reservation. Pending mutations keep their reservations; a release does not establish whether another operation happened. - list_all_tabs — (no tab needed) list every open tab, including
chrome-extension://pages thatlist_tabshides. Those never become sessions, so they have no session id; drive them withcdp_command(tab_id=...).session_id(string, optional): which browser/profile to ask.
- switch_tab — set this MCP process's target tab for later calls. A
url_patternmust match exactly one tab; if several match, select one with its fullsession_id. Abrowserfilter matching multiple profiles also requires an explicitsession_id. It does not raise the tab or focus the browser:activatedefaults tofalse. Passactivate=true, or callactivate_tab, when you need the tab in front.session_id(string, optional),url_pattern(string, optional): substring match,browser(string, optional):chrome,edge, oropera,activate(boolean, optional): defaultfalse.
- activate_tab — bring a tab to the foreground and focus its window. This is the explicit way to raise a tab, and the only one that does not involve approving physical input. Check
on_screenin the reply: BTAP first asks Windows to restore a minimised browser, buton_screen=falsemeans visibility still could not be confirmed and screen-coordinate input must not be sent.session_id(string, optional)
- open_url — navigate the current tab. Global behavior remains
dismiss; lab automatically accepts beforeunload on configured shell/IDE hosts. If the extension'snavigateroute is unavailable on a heavy SPA, BTAP falls back toPage.navigate. A CDP result withisDownload=truereturns{type:"download",status:"triggered"}instead of onlynavigation_failed; the accompanyingERR_ABORTEDis normal for that download navigation.url(string),session_id(string, optional),timeout(number, optional): default15,beforeunload(string, optional): defaultdismiss,intent_leave(boolean, optional):falseforces page preservation
- download_file — download an HTTP(S) URL through Chrome's native download manager, using that browser profile's cookies and authenticated session. It waits by default and returns
status="completed"plus a verified absolutepath; interrupted downloads returnfailed, while a timeout orwait=falsereturnsin_progresswithdownload_id. An explicitsession_idmust still be live and is never replaced with another profile. Use this for attachments instead of pagefetch.url(string),filename(string, optional): relative download name,directory(string, optional): arbitrary absolute destination directory; creates parents,wait(boolean, optional): defaulttrue;directoryrequirestrue,timeout(number, optional): default 60 seconds, maximum 1800,session_id(string, optional): selects the browser profile,overwrite(boolean, optional): defaultfalse; an existing final destination raises an error unless explicitlytrue. If a directory download times out,directory_applied=false: the move is no longer tracked and Chrome may finish into its default download directory.
- open_new_tab — open a background tab in this MCP process's selected browser/profile, unless
session_idorclient_idselects another. Creates a uniqueoperation_idand waits a bounded time for exact session/generation registration; passactive=truefor foreground work. Returns{operation_id,tab_id,session_id,generation,ready,owned,opener,owner_id,load_status}. The extension deduplicates by operation id. Ownership requires a completed record with exactclient_id+tab_id+generation, even whenready=false;readyonly reports immediate availability for session tools. Before create dispatch, registry uncertainty returnsstatus="unknown",may_have_created=false,retry_safe=true; after dispatch, uncertainty returnsmay_have_created=true,retry_safe=false. Withmay_have_created=false,retry_safe=true, resolve the reported failure and retry withoutoperation_id. To recover a dispatched create withretry_safe=false, pass the sameoperation_id, returnedclient_id, andowner_id: recovery reads the durable record without replayingtabs/create. A failed recovery probe preserves that uncertainty and owner capability. If the initial recovery probe finds no record,reconciliation.resume_required=falsedirects the caller tolist_tabs()and inspection of that browser instead of another recovery call. Missing records, matching URLs, or unchanged tab counts cannot prove non-creation or ownership; keep the outcome unknown unless exact identity and task ownership resolve it. Keepowner_idfor cleanup of registered task-owned tabs with their exact session/generation. Use this native API for reliable new tabs; pagewindow.open()or anchor clicks may be blocked without a user gesture.url(string),timeout(number, optional): default15,active(boolean, optional): defaultfalse,session_id(optional browser/profile selector),owner_id(optional capability to group several tabs under one task owner),operation_id(optional recovery handle),client_id(optional browser/profile client selector for creation or recovery)- A pending create left by a worker restart becomes terminal
unknown. Bounded record retention also keeps a replay guard for retired operation IDs; a refused old ID does not prove non-creation. Followreconciliation.resume_required=falsewith inspection of that browser, preserving unknown outcomes and exact ownership evidence. - A failed status probe may return
reconciliation.bridge_operation. Its wireoperation_idcan be queried withget_execute_js_resultin the same MCP session; the outer creationoperation_idstill belongs toopen_new_tabrecovery. The probe'sreservation_held=falsedoes not prove an earlier create was absent or safe to replay.
- close_tabs — (no tab needed) accept native numeric tab ids or full
client:tabIdsession ids, includingchrome-extension://tabs. The defaultonly_if_agent_owned=truerequires theowner_idreturned byopen_new_taband verifies the current lifecycle generation before closing, so pre-existing user tabs and another agent's tabs are refused. If the user already closed an owned tab, cleanup returnsstatus=already_gone, closed_by=userwithout reusing its native id. An actual owned close returnsclosed_by=agent; an explicit unowned/operator override returnsclosed_by=noneso it is not counted as task-owned cleanup. Ifalready_goneis returned but a page with the same work is still visible, calllist_all_tabsand verify URL/title before deciding whether a new session/generation should be closed; BTAP never auto-transfers ownership by URL. A Chrometabs.onReplacedidentity mapping is safe and also migrates the ownership claim. Setonly_if_agent_owned=falseonly when the operator explicitly asked to close an unowned/user tab.tab_id,session_id(optional browser constraint),owner_id(required by the safe default),only_if_agent_owned(boolean, defaulttrue)
Page reading and execution
-
scan_page — read the page as simplified HTML or text. Returns
linksmapping each#rNref in the content to its absolute URL, andoffscreen+hintwhen content was left outside the viewport. A background tab may report viewport height zero; ordinary DOM/text/API work still continues there, and only visual/layout fidelity requires explicitactivate_tab. When the page can be probed,render_state/content_readydistinguish real content from a loading, hydrating, or shell-only SPA; retry or usewait_forbefore treating an empty shell as final content.cutlist(on by default) collapses long repeated lists and reports a CSS selector for each container it collapsed, derived from that container's own structure. The built-in scan does not write page attributes, ids, orwindowglobals. Optionalextra_jsruns caller code and can modify the page or send requests.session_id(string, optional),text_only(boolean, optional): defaultfalse,cutlist(boolean, optional): defaulttrue; collapse repetitive lists,maxchars(integer, optional): default35000,instruction(string, optional),extra_js(string, optional),timeout(number, optional): default15,frame(array, optional): non-empty path of CSS strings or structured frame locators,max_targets(integer, optional): default80, range0–200observation.targetsadds current locators, control names, editability, rectangles andrecommended_toolwith a reason. Pass a returnedlocatorunchanged as thepage_click/page_typeselector; re-observe after page changes.observation.frameslists uninspected child documents: pass an entry'sframeback toscan_pageto inspect it, including cross-origin/OOPIF frames. Open shadow roots are included; this is a bounded DOM observation, not a complete accessibility tree.max_targetscaps controls and frame entries together, separately from the content budget;0disables enumeration andtruncated=truemeans more targets or DOM nodes remain uninspected. Rectangles use the observed document's viewport CSS pixels and are not hit-tested.verify_coordinate_targetrecommends a screenshot to verify a canvas or zero-size target, not an automatic coordinate click. Disabled, inert and readonly controls have no recommended input tool; native selects reportselect_existing_option. Only top-document file inputs outside shadow roots recommendupload_files, usingselector=locator.css; frame/shadow file uploads report unsupported. Frame scans rejectextra_jsand omit the parent's readiness probe; top-documentextra_jspreserves its execution semantics and reports target metadata unavailable. The optional built-in readiness probe releases its tab reservation on timeout. Missingrenderfields mean readiness is unknown; usewait_forfor the intended control.
-
wait_for — wait until a condition holds, then return. Use this instead of polling
scan_page, which re-serializes the whole DOM each time. The server schedules short synchronous page checks under one deadline, avoiding background-page timer throttling. Exactly one condition is required.selectoraccepts legacy CSS or the structured locator object described under background page input. Caller-providedjsis evaluated repeatedly and can have side effects; use a read-only predicate. A timeout withoperation_idretains a pending check. Timed-out selector/text/URL probes can release the tab without discarding that receipt:reservation_held=falsepermits another command, andget_execute_js_resultin the same MCP session can collect the delayed reply. Caller-providedjsstays reserved. Whenreservation_heldis true or unknown, keep querying the original operation until it settles or releases its reservation. A pending probe is never replayed.selector(string/object, optional): CSS or structured locator,text(string, optional): substring of body text,url_pattern(string, optional): regex on the URL,js(string, optional): expression to become truthy,gone(boolean, optional): wait for the condition to stop holding; defaultfalse,timeout(number, optional): default15,session_id(string, optional)
-
wait_for_url — wait for navigation to settle: blocks until the tab URL matches
url_pattern(regex, or plain substring — both are tried) and, unlesswait_ready=false,document.readyStateiscomplete; then returns finalurl,titleandready_state. Use after a click oropen_urlthat navigates;wait_for(url_pattern=...)only checks the URL and can return while the new document is still blank. Uses the same bounded synchronous checks and receipt recovery aswait_for. A pending probe withreservation_held=falseno longer blocks the tab; its delayed reply remains available throughget_execute_js_resultin the same MCP session.url_pattern(string): regex or substring to match against the URL,timeout(number, optional): default 15,wait_ready(boolean, optional): requirereadyState === 'complete', defaulttrue,session_id(string, optional)
-
scroll_page — scroll and report the new position, so a long page can be read in passes.
to(string, optional): defaultbottom; also acceptstop, a pixel offset, or a CSS selector to bring into view,session_id(string, optional),timeout(number, optional): default15
-
execute_js — run JavaScript in the page and return the result.
timeoutis one end-to-end deadline covering dialog-policy setup, monitor snapshots, delivery/retry, navigation inspection, and cleanup; an explicitsession_idis forwarded through every one of those roundtrips instead of relying on the process default. Setwait=falsefor a genuinely long task: once the extension acknowledges delivery, BTAP returnsstatus="in_progress"plus anoperation_id; claim the result withget_execute_js_resultinstead of replaying the script.dialog_policy="manual"is intentionally unavailable in background mode. When a script navigates the page,statusisnavigated(notsuccess) withlanded_url; the script's return value is genuinely lost in that case and is reported as such rather than substituted.dialog_policydecides what happens if the script opensalert/confirm/prompt:dismiss(default) andacceptanswer it and report it underdialogs, whilemanualpauses a synchronous script with the native dialog still open and returnsblocked_by_dialog— callhandle_dialogto release it. A tab already holding a manual pause returnsbusyimmediately. Usewait_for/wait_for_urlinstead of delayedsetTimeoutor sleep Promises when waiting for page state. When the JSON-encodedjs_returnexceeds the 24 KiB UTF-8 inline limit, BTAP writes the complete value to a private temporary JSON file and returnsresult_file,result_bytes,result_sha256, andresult_formatinstead of a truncated inline value.- A
Cannot access contents of the pageerror must be classified before retrying: if the script attemptedwindow.openor navigation, useopen_new_tab(Chrome may block it without a user gesture); if injection into the current tab is forbidden, choose a normal scriptablehttp/httpstab or the supported CDP route. Do not treat the message as proof that reading the current page failed. timeoutdoes not cancel dispatched JavaScript. See Troubleshooting for timeout recovery and frame-preparation errors.script(string),session_id(string, optional),no_monitor(boolean, optional): defaultfalse,timeout(number, optional): default15,dialog_policy(string, optional):dismiss(default),accept, ormanual,wait(boolean, optional): defaulttrue- All routes use the same result conversion:
undefinedand non-finite numbers becomenull; BigInt/symbol become strings; DOM, Error and function values become readable representations. Cycles and depth 6 have markers; iterables keep up to 200 items plus a truncation marker. Aresult_filepreserves the complete converted value, including those markers. - Once user code starts, a script error does not trigger a second execution. Use an explicit
returnin a complex async body, preferably(async () => { /* work */ return value; })(). Ambiguous bodies may complete withnull;await(expr)can parse as a call to an ordinary function namedawait, so use the async IIFE when that distinction matters. - Unpaired UTF-16 values or keys use the same file export at any size, with
result_file_scope="js-value". If writing fails,result_jsonpreserves the complete value withresult_json_scope="js-value"; see Complete JSON results for decoding and receipt semantics.
- A
-
get_execute_js_result — read or briefly wait for an
operation_id, from the same MCP session that submitted it. Accepts handles fromexecute_jsand other timed-out bridge commands. Querying never replays the operation. A completed result can be read repeatedly, including after a lost query response; pending, unknown/expired, and foreign handles return explicit statuses or errors. After reservation expiry, the first valid late terminal reply is retained aslate_result(successanddata), withlate_reply_agein seconds. The originalunknownreceipt andretry_safe=falseremain; the late reply neither restores the reservation nor renews retention. Results are retained for up to 10 minutes, with at most 512 completed operation records; capacity pressure can evict them earlier. A missing result does not prove the operation was never executed. Large successful values use the same losslessresult_filemetadata asexecute_js; for a late value, that metadata is insidelate_resultand itsdatabecomes null. Accepts the nestedreconciliation.bridge_operation.operation_idfrom a failedopen_new_tabstatus probe. A known read-only wait, inventory or creation-status probe may release its reservation on timeout while retaining its receipt. Checkreservation_held; it describes that probe, not the outcome of the tab creation.operation_id(string),timeout(number, optional): default0, range0–120
-
handle_dialog — inspect or answer a dialog left open on a tab.
action="manual"reports it without choosing (blocked_by_dialog, orno_dialogif nothing is open);accept/dismissanswer it and release any pausedexecute_jsoropen_url.prompt_textsupplies the text for an acceptedprompt.action(string),prompt_text(string, optional),session_id(string, optional),timeout(number, optional): default3, capped at three seconds
-
resolve_leave_dialog — for an already-open shell/ttyd/IDE leave prompt: two protocol accepts, then physical Enter only when lab permits it.
session_id(string, optional)
-
upload_files — set files on a file input, which JavaScript cannot do (
input.filesis read-only). Runs as one CDP batch so the DOM node ids stay valid across the sequence.selector(string): the<input type=file>,paths(string or array of strings): absolute local paths,session_id(string, optional),timeout(number, optional): default30
-
get_cookies — read cookies for a page.
session_id(string, optional),tab_id(integer, optional)
-
set_cookies — write cookies into the real browser profile. Takes one cookie object or a list (JSON text is accepted):
nameis required, plus optionalvalue/url/domain/path/expires(Unix seconds)/httpOnly/secure/sameSite. Uses CDPNetwork.setCookie, so HttpOnly and cross-path cookies work; falls back todocument.cookieonly when CDP is unavailable, and then reports which cookies could not carry HttpOnly. Cookies with neitherurlnordomainare scoped to the current page.cookies(string or list or dict),session_id(string, optional),tab_id(integer, optional),timeout(number, optional): default20
-
delete_cookies — delete a cookie by name. Uses CDP
Network.deleteCookies, falling back to expiring it viadocument.cookie. Scope withdomain/path, orurlto target one site.name(string),domain(string, optional),path(string, optional),url(string, optional),session_id(string, optional),tab_id(integer, optional),timeout(number, optional): default20
-
storage_get — read localStorage or sessionStorage. Omit
keyto page withoffset/max_items/max_bytes; returnsnext_offsetandtruncated. The default timeout is 30s and a failed call does not close the MCP session.key(string, optional),area(string, optional):local(default) orsession,session_id(string, optional),timeout(number, optional): default30,offset(integer, optional),max_items(integer, optional),max_bytes(integer, optional)
-
storage_set — write one localStorage/sessionStorage value (non-string values are JSON-encoded first). Verifies by read-back, so a quota-full or privacy-mode failure is reported instead of silently lost.
key(string),value(string),area(string, optional):local(default) orsession,session_id(string, optional),timeout(number, optional): default30
Background page input
Role locators exclude hidden, aria-hidden, and inert controls, including inactive forms retained by SPAs. A visible disabled control can satisfy a query; CSS queries retain DOM-presence semantics. Multiple visible role matches still return ambiguous.
page_click, page_type, and wait_for accept nested same-origin and cross-origin iframe paths, including frames in a separate renderer (OOPIF):
control = {"frame": ["#outer", "#inner"], "css": "#control"}
wait_for(selector=control, session_id=session_id)
page_type("example", selector=control, clear=True, session_id=session_id)
page_click(selector={"frame": ["#outer", "#inner"], "role": "button", "name": "Search"}, session_id=session_id)
Each call binds the actual frame documents and elements. Navigation or replacement after binding returns stale_frame; a later, independent call can locate the new document. Check input_dispatched before recovery: a partial sequence or unknown result must not be replayed. An iframe wait with an outstanding operation_id can retain its reservation; collect that result first. Old extension builds return stale_extension with next_action=reload_extension.
Trusted CDP input events delivered to one named tab. They do not activate the tab, focus its window, or move the desktop cursor — every reply carries foreground_changed: false and input_mode: "cdp". All coordinates are viewport CSS pixels (relative to the top-left of the page area, the space getBoundingClientRect reports), never desktop pixels and never the device pixels capture_page_screenshot returns.
Pass session_id explicitly: the call holds that target in its own context without changing this MCP process's default. A stale handle without evidence of a same-tab replacement is refused. Another MCP call using the same tab can return target_busy; see the concurrent-task boundaries above.
selector remains backward-compatible with CSS strings and also accepts a locator object with exactly one primary key: css, role (optional name), text, or label. Inside a locator object, selector is a compatibility alias for css. exact applies to role/name or text matching; frame walks iframe locators across origin and renderer boundaries; shadow walks open Shadow DOM hosts. A click-only frame-relative point can use {"frame": [...], "x": 20, "y": 30}; its CSS coordinates in the final frame's viewport are converted to top-document coordinates before dispatch. Zero matches return not_found, multiple matches return ambiguous, and closed shadow roots remain inaccessible. Framed clicks refuse non-identity transforms, zoom or perspective on the frame or its ancestors with unsupported_frame_transform; query/type paths remain available.
- page_click — click a CSS/structured
selectoror viewport coordinates. Exactly one targeting mode: eitherselector, or bothxandy. With a selector, each omitted offset axis uses the element centre; a suppliedoffset_xoroffset_yis measured from the element's top-left corner.{"frame": [...], "x": 20, "y": 30}names a point in the final iframe, converted to top-document CSS coordinates. Point mode is not hit-tested. Missing, ambiguous, non-interactable, closed-shadow, and unsupported transformed-frame targets dispatch no input. Selector mode checks the element and every parent frame for obstruction before dispatch: an overlay returnsobscuredwithoccluded_by, and an off-screen point returnsoutside_viewport. Top-document targets can be scrolled into view (scrolled_into_view); framed clicks do not scroll automatically. A verified click carrieshit_verified: true. Screenshot coordinates are device pixels: divide bydevicePixelRatiobefore using them as CSS coordinates. Challenge replies keep the boundedchallenge_detected/attempts/challenge_stalledbehavior.selector(string/object, optional),x(number, optional),y(number, optional),offset_x(number, optional),offset_y(number, optional),button(string, optional): defaultleft,clicks(integer, optional): default1,session_id(string, optional),timeout(number, optional): default15
- page_type — insert text into a CSS/structured-locator field, or into whatever already has focus when
selectoris omitted. Xterm.js containers/descendants retarget to.xterm-helper-textarea. Missing, ambiguous, read-only, or otherwise unusable targets return a structured status without dispatching text or keys; invalid legacy CSS returnsstatus="invalid_selector"instead of a rawSyntaxError. Successful and failed target resolution includes a redactedactive_elementdescriptor andfocus_confirmedwhen focus was attempted, so omitted-selector input is auditable.clear=trueselects the existing value first;submit_keysends one key afterwards.text(string),selector(string/object, optional),clear(boolean, optional): defaultfalse,submit_key(string, optional),session_id(string, optional),timeout(number, optional): default15
- page_press — press a key or a comma-separated modifier chord in the tab, e.g.
enterorctrl,shift,k.keys_csv(string),session_id(string, optional),timeout(number, optional): default15
- page_drag — drag between two viewport points as one uninterrupted event sequence.
x1(number),y1(number),x2(number),y2(number),duration(number, optional): default0.3,button(string, optional): defaultleft,session_id(string, optional),timeout(number, optional): default15
Site permissions
Temporary, origin-scoped permission leases backed by chrome.contentSettings. Every lease records the prior setting and attempts restoration on expiry, explicit reset, and a service-worker or browser restart. Unsupported restoration retains manual_recovery with the prior setting and stops automatic retries.
- set_site_permission — set one permission for one origin, for 60–600 seconds. Supported:
notifications,geolocation(orlocation),camera,microphone.settingisallow,block, orask. Insafe, everyallowrequires approval; defaultlabapplies it without elicitation (BROWSERTAP_LAB_NO_ELICIT=1semantics). Declining returnsrequires_user_actionand changes nothing.clipboardreturnsunsupported, because its exact prior state cannot be restored. Omitoriginto use the target tab's current origin; onlyhttp/httpsorigins are accepted. If an established lease loses restoration support,manual_recoveryretains its prior setting and recovery guidance and stops automatic retries.permission(string),setting(string):allow,block, orask,origin(string, optional): defaults to the tab's origin,duration_seconds(integer, optional): 60–600, default300,session_id(string, optional)
- reset_site_permissions — attempt to restore matching leases now, including
manual_recoveryrecords. Omit bothoriginandpermissionto reset every lease on that browser. Unsupported restoration preserves the prior setting and recovery guidance and stops automatic retries; resolve the cause before another explicit reset.origin(string, optional),permission(string, optional),session_id(string, optional)
CDP
- cdp_command — send one CDP command to the selected tab or explicit debuggee. Listed high-risk methods return
raw_cdp_blockedbefore dispatch; params must be a JSON object. Other allowed methods can still change page or profile state. See the raw CDP policy above.method(string): e.g.Page.navigate,params_json(string, optional): JSON object as text,session_id(string, optional),tab_id(integer/string, optional),extension_id(string, optional),target_id(string, optional),timeout(number, optional): default20
- cdp_batch — send a batch;
batch_jsonmust be a JSON object withcmd: "batch"and acommandsarray ofcdp,tabs, orcookiesobjects. The whole batch passes the raw CDP policy before its first member runs; nested/unknown commands are rejected.batch_json(string),session_id(string, optional)
- debugger_targets — (no tab needed) list every CDP-attachable target, including service workers and extension background pages that
list_tabsnever shows.session_id(string, optional)
- save_pdf — bounded
Page.printToPDF; validates PDF bytes and atomically writessave_path.save_pathis relative and resolves under~/Downloads/browsertap; an absolute path or a..escape is rejected withValueError. A timeout forcibly releases its debugger lease.save_path(string),session_id(string, optional),landscape(boolean, optional): defaultfalse,print_background(boolean, optional): defaulttrue,prefer_css_page_size(boolean, optional): defaulttrue,scale(number, optional): default1.0, range0.1–2.0,page_ranges(string, optional),timeout(number, optional): default30
On driving other extensions: Chrome refuses cross-extension debugging at attach time, and all three addressing forms (
tab_id,extension_id,target_id) are rejected alike unless Chrome was started with--silent-debugger-extension-api. These parameters are for this extension's own targets and for diagnosis.
Extension management
- extension_path — absolute path of the unpacked extension, for manual install. No parameters.
- list_extensions — (no tab needed) installed extensions with id, name, enabled state, type, and version.
session_id(string, optional)
- set_extension_enabled — (no tab needed) enable or disable an installed extension. Chrome exposes no API to install one, so this only toggles what is already there.
extension_id(string),enabled(boolean),session_id(string, optional)
- uninstall_extension — (no tab needed) request removal of another extension. Confirmation defaults on; set it off only for an explicitly selected disposable/test extension. Chrome can refuse either setting because a user gesture is required; changing the flag does not supply that gesture. BTAP cannot uninstall itself through its active response channel.
extension_id(string),show_confirm_dialog(boolean, optional): defaulttrue,session_id(string, optional)
- get_bookmarks — (no tab needed) read the bookmark tree.
session_id(string, optional)
- create_bookmark — (no tab needed) create a bookmark or folder.
title(string),url(string, optional): omit to create a folder,parent_id(string, optional),session_id(string, optional)
- remove_bookmark — (no tab needed) atomically save the target subtree under
bookmark-backupsin the local state directory, then remove the bookmark or folder. Returnsbackup_pathandbackup_sha256; backup failure prevents deletion. The managed backup subdirectory must be an ordinary directory, not a symlink or reparse point. Limits: 16 MiB per file, 100 files and 64 MiB total; backups older than 30 days or over capacity are removed on the next backup. A lost deletion receipt keeps the backup evidence but requires inspecting the bookmark tree before retrying.bookmark_id(string),recursive(boolean, optional): defaultfalse,session_id(string, optional)
- call_extension — (no tab needed) send JSON to another enabled extension; the target must allow BTAP via
externally_connectable.extension_id(string),message_json(string): JSON payload as text,session_id(string, optional)
Network and console capture
- network_capture_start — start collecting bounded request/response records and optional bodies. Defaults: 500-entry ring and 256 KiB per body.
session_id(string, optional),include_bodies(boolean, optional): defaulttrue,max_entries(integer, optional): default500, range 10–2000,max_body_bytes(integer, optional): default262144, range 1024–2097152,body_timeout(number, optional): default5, range 0.1–10 seconds,timeout(number, optional): default10
- network_capture_stop — return the current capture and release its debugger lease; always call it in cleanup. Returned records can be filtered without changing capture bounds or cleanup.
url_patternis compiled by the browser as a JavaScriptRegExp; invalid patterns return a structured error and leave the capture running for retry.session_id(string, optional),url_pattern(string, optional): JavaScriptRegExp,resource_type(string, optional),status_min/status_max(integer, optional): 100–599,include_response_bodies(boolean, optional): defaulttrue,timeout(number, optional): default10
- console_capture_start — start collecting
console.*and uncaught exceptions.session_id(string, optional),max_entries(integer, optional): default500, range 10–5000,timeout(number, optional): default10
- get_console_messages — page through or clear the current console buffer.
filter='user'retains page MAIN/default-context output and excludes isolated extension/content-script contexts; empty/allpreserves the complete buffer.session_id(string, optional),offset(integer, optional): default0,max_items(integer, optional): default200,clear(boolean, optional): defaultfalse,filter(string, optional):userorall,timeout(number, optional): default10
- console_capture_stop — return the remaining console messages and release its debugger lease.
session_id(string, optional),timeout(number, optional): default10
Screenshots
- capture_page_screenshot — page capture via CDP with viewport,
full_page, or explicitclipmodes. PNG, JPEG, and WebP are supported;qualityis valid only for JPEG/WebP. Returns text metadata plus attached MCP image content;save_pathonly adds a disk copy, and it is relative — it resolves under~/Downloads/browsertap, with absolute paths and..escapes rejected. Base64 is omitted unless explicitly requested. The metadata names its own units:image_width/image_heightparsed from the returned bytes andpixel_space: "device"(CSS ×devicePixelRatio), so a point read off the picture is not fed straight topage_click. A header it cannot parse reportsnulldimensions plus adimensions_noterather than a guess —sizeis the byte count, not a dimension.session_id(string, optional),tab_id(integer, optional),format(string, optional): defaultpng,full_page(boolean, optional): defaultfalse,clip(object, optional):x,y,width,height, optionalscale,quality(integer, optional): 0–100 for JPEG/WebP,save_path(string, optional),return_base64(boolean, optional): defaultfalse,timeout(number, optional): default20
Native file dialogs: explicit desktop opt-in
- inspect_native_file_dialog — inspect the current foreground Windows standard Shell file dialog owned by a registered Chrome or Edge process. Requires
[desktop]. Checks owner/process identity, native controls, visibility and Cancel hit targets, then installs a temporary lifetime marker and returns a 15-second ticket. This has a temporary marker side effect and does not activate a window. Unsupported platforms, portable/unregistered browsers, cross-process owners and unrecognized layouts are refused.desktop_opt_in(boolean, optional): defaultfalse; must betrueto inspect.
- cancel_native_file_dialog — consume a fresh inspection ticket and send one bounded message to that dialog's Cancel button. Requires
[desktop]and the current safe/lab physical-approval policy. Rechecks the cross-process lease, observed Windows quiet-input state, held keys/buttons, identity, foreground and hit targets. Returnsstatus="success", cancelled=trueonly after observing the original window gone. Uncertain delivery or closure returnsunknown,retry_safe=false; inspect state before another action. Each opted-in attempt consumes the ticket, including refusals. Both tools reportdesktop,on_screenandinput_quietdiagnostics.ticket(string, required),desktop_opt_in(boolean, optional): defaultfalse; must betrueto cancel.
For uploads, use upload_files with the page's file input. Native cancellation
is an explicit recovery attempt for an already-open supported dialog. Live
verification did not obtain a valid ticket for a real Chrome dialog with a
cross-process owner, so successful automatic cancellation remains unverified.
Do not open a chooser solely to test recovery. Closing its tab may leave the
dialog open, and a user's manual closure is not an automated success. safe
requires approval; default lab skips elicitation but still requires opt-in.
Tickets expire, are limited to eight per MCP process, and clean up their own
markers on consumption, expiry, eviction or orderly shutdown. These checks are
not an atomic desktop transaction. See native-dialog design.
Removed in 0.5.0: OS-level input and desktop capture
mouse_move, mouse_click, mouse_drag, type_text, hotkey, pointer_info
and capture_desktop_screenshot no longer exist. They drove the whole desktop
rather than one tab, so they acted on whatever happened to be on screen. What to
call instead:
| Removed | Use |
|---|---|
mouse_click |
page_click |
mouse_move |
not needed — page_click positions itself |
mouse_drag |
page_drag |
type_text |
page_type |
hotkey |
page_press |
pointer_info |
execute_js to read element geometry |
capture_desktop_screenshot |
capture_page_screenshot |
A failing page_* call is a targeting problem, not a reason to look for a
screen-coordinate fallback — re-read the page with scan_page and fix the
locator. Browser chrome, extension popups and OS dialogs are outside page-level
input. The explicit native-file-dialog tools above cover only the documented
Windows inspection/cancellation boundary.
One global-key fallback survives: resolve_leave_dialog may send Enter after its two
protocol attempts fail, in lab only; pure probe timeouts do not trigger it.
safe returns requires_user_action without sending Enter. Lab skips elicitation
by default; when lab approval prompts are enabled, a declined, cancelled or
unavailable prompt also prevents input. The physical gate keeps its cross-process lock (contended → busy, returned
immediately, never queued), a short quiet window (you touched the mouse or
keyboard → input_activity_detected, nothing sent), then raise the target tab,
then act. What that window can detect depends on the OS: only Windows exposes a
last-input timestamp, and the pointer position is unavailable under Wayland, in a
headless container, and on macOS without the accessibility permission. With no
signal at all the window still elapses but has nothing to compare, so the result
carries an input_quiet block naming the markers it sampled, with
enforced: false when there were none — read a pass on such a machine as
unverified rather than as an idle desktop. If the tab cannot be confirmed on
screen the result is activation_failed and nothing is sent, so a minimised
window produces an error rather than an Enter into the wrong place.
Troubleshooting
Run browsertap doctor first. For connection, version, dialog,
permission, and physical-input recovery procedures, see the dedicated
troubleshooting guide.
License
MIT — see LICENSE, which ships inside both the wheel and the sdist. Keep it if you fork or redistribute this.
BTAP is maintained by LinVireo, and the canonical public repository for this
distribution is LinVireo/browsertap-mcp.
Metadata
Release files for browsertap-mcp 0.5.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| browsertap_mcp-0.5.4.tar.gz | 1.3 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| browsertap_mcp-0.5.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.7 MB
Release files / browsertap_mcp-0.5.4.tar.gz
| Download URL | browsertap_mcp-0.5.4.tar.gz |
|---|---|
| Size | 1.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c453244cab620b85e29efb292e8e48576544e71fb659c2054399c99e5fa68655
|
|
BLAKE2b-256 checksum How to use checksums |
71df47c0503a5a5e1c637f042033fa5d2cc2db454f70aa093469a7b0b039e1a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / browsertap_mcp-0.5.4-py3-none-any.whl
| Download URL | browsertap_mcp-0.5.4-py3-none-any.whl |
|---|---|
| Size | 420.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1c1f60160c0bafec454332f0752ea63137420e667edc3e096f4acc9f103f0853
|
|
BLAKE2b-256 checksum How to use checksums |
a32584e79787d47801f9a64d53dc7274c4fd5bd2db3009e073961d36ae416c98
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency log