
Security News
Lovable’s OJ Rewrites Vite’s Dev Server in Rust as AI Lowers the Cost of Forking Open Source
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.
browsertap-mcp
Advanced tools
Real-browser MCP server with a BrowserBridge/CDP transport, background-tab page input, screenshots, and cookies
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.
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_status for the extension_path. Open chrome://extensions,
enable Developer mode, choose Load unpacked, and select that directory.
Use edge://extensions or opera://extensions for 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.
| 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.
page_click, page_type, page_press, and page_drag use trusted CDP input without moving the desktop cursor.See the usage guide for step-by-step workflows and the tool reference for parameters.
BTAP exposes three capability layers so an agent can choose the narrowest interface that matches the task:
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.inspect_native_file_dialog first, then use its short-lived ticket with
cancel_native_file_dialog; both require desktop_opt_in=true and [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.
resolve_leave_dialog fallback and the
Windows native-file-dialog tools need a usable desktop session.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
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.
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 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.
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 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.
Follow the MCP install guide and use the standard config above. An example file is included at examples/claude-desktop-config.json.
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.
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.
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.
Any MCP client that speaks stdio will work. Follow its own install guide and use the standard config above.
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.
| 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. |
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.
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.
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:
pip install -U browsertap-mcp (keep
[desktop] if you use that extra), then restart its MCP session.browsertap doctor. If it requests restart_bridge, use
browsertap bridge --restart to load the daemon's updated code.reload_extension, open chrome://extensions and 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.
browsertap bridge --stop.chrome://extensions (or the equivalent page in Edge/Opera) and remove the
unpacked BrowserTap Bridge extension.browsertap entry from each MCP client's configuration.pip uninstall browsertap-mcp in the environment where it was installed. If
you created a dedicated virtual environment, remove that specific environment after
deactivating it.~/.browsertap. This deletes the persistent bridge token and logs; the data is
retained by default so reinstalling continues to work without reconfiguration.Three layers:
tabs, cookies, debugger, and management through Chrome APIs.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_id identifies one connected browser/profile instance; session_id is its current composite clientId:tabId handle, not a permanent tab identity. Connected rows may also carry tab_identity. Several browsers and profiles coexist.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.
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.
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.
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.
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.
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.
resolve_leave_dialog's Enter fallback, lab only, 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; safe refuses to send it at all. The page_* tools carry none of this exposure.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.
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.
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=true means a bridge that is still running must be replaced with browsertap bridge --restart. reload_extension_required=true identifies the unpacked-extension platform limit and requires a manual Reload; a version number that differs on its own no longer sets it, because Chrome parses manifest.json at 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=true is 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_stamp is the stronger signal and answers the question the four version fields cannot: it is a hash of the extension sources compiled into background.js, reported by the worker actually running, so comparing it to expected_extension_build_stamp (a fresh hash of the directory) is decisive in both directions where version equality was measured wrong twice. Read the answer from extension_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 running python -m scripts.extension_stamp --write, so the comparison proves nothing either way) or unverifiable (the extension predates the stamp, or the directory could not be read -- see extension_build_error). extension_build_enforced=false means no comparison happened, so treat it as unknown rather than as a pass. Answers while another tool is still running; default_session_id is this request's snapshot of the MCP process default and default_session_settled=true because another call's temporary target is isolated. No parameters.
extension_status_available=false means the extension has not supplied runtime status: starting asks to wait_for_extension, and extension_unavailable asks to check_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_status may still create one during bridge startup or authentication initialization.lab or safe.lab|safe; the override is not persisted and does not reload the extension.
mode (string): lab or safedata.tabs, including their full session handles and browser fields. Answers while another tool is still running; default_session_id is this request's snapshot of the MCP process default and default_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.chrome-extension:// pages that list_tabs hides. Those never become sessions, so they have no session id; drive them with cdp_command(tab_id=...).
session_id (string, optional): which browser/profile to ask.url_pattern must match exactly one tab; if several match, select one with its full session_id. A browser filter matching multiple profiles also requires an explicit session_id. It does not raise the tab or focus the browser: activate defaults to false. Pass activate=true, or call activate_tab, when you need the tab in front.
session_id (string, optional), url_pattern (string, optional): substring match, browser (string, optional): chrome, edge, or opera, activate (boolean, optional): default false.on_screen in the reply: BTAP first asks Windows to restore a minimised browser, but on_screen=false means visibility still could not be confirmed and screen-coordinate input must not be sent.
session_id (string, optional)dismiss; lab automatically accepts beforeunload on configured shell/IDE hosts. If the extension's navigate route is unavailable on a heavy SPA, BTAP falls back to Page.navigate. A CDP result with isDownload=true returns {type:"download",status:"triggered"} instead of only navigation_failed; the accompanying ERR_ABORTED is normal for that download navigation.
url (string), session_id (string, optional), timeout (number, optional): default 15, beforeunload (string, optional): default dismiss, intent_leave (boolean, optional): false forces page preservationstatus="completed" plus a verified absolute path; interrupted downloads return failed, while a timeout or wait=false returns in_progress with download_id. An explicit session_id must still be live and is never replaced with another profile. Use this for attachments instead of page fetch.
url (string), filename (string, optional): relative download name, directory (string, optional): arbitrary absolute destination directory; creates parents, wait (boolean, optional): default true; directory requires true, timeout (number, optional): default 60 seconds, maximum 1800, session_id (string, optional): selects the browser profile, overwrite (boolean, optional): default false; an existing final destination raises an error unless explicitly true. If a directory download times out, directory_applied=false: the move is no longer tracked and Chrome may finish into its default download directory.session_id or client_id selects another. Creates a unique operation_id and waits a bounded time for exact session/generation registration; pass active=true for 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 exact client_id+tab_id+generation, even when ready=false; ready only reports immediate availability for session tools. Before create dispatch, registry uncertainty returns status="unknown",may_have_created=false,retry_safe=true; after dispatch, uncertainty returns may_have_created=true,retry_safe=false. With may_have_created=false,retry_safe=true, resolve the reported failure and retry without operation_id. To recover a dispatched create with retry_safe=false, pass the same operation_id, returned client_id, and owner_id: recovery reads the durable record without replaying tabs/create. A failed recovery probe preserves that uncertainty and owner capability. If the initial recovery probe finds no record, reconciliation.resume_required=false directs the caller to list_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. Keep owner_id for cleanup of registered task-owned tabs with their exact session/generation. Use this native API for reliable new tabs; page window.open() or anchor clicks may be blocked without a user gesture.
url (string), timeout (number, optional): default 15, active (boolean, optional): default false, 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)unknown. Bounded record retention also keeps a replay guard for retired operation IDs; a refused old ID does not prove non-creation. Follow reconciliation.resume_required=false with inspection of that browser, preserving unknown outcomes and exact ownership evidence.reconciliation.bridge_operation. Its wire operation_id can be queried with get_execute_js_result in the same MCP session; the outer creation operation_id still belongs to open_new_tab recovery. The probe's reservation_held=false does not prove an earlier create was absent or safe to replay.client:tabId session ids, including chrome-extension:// tabs. The default only_if_agent_owned=true requires the owner_id returned by open_new_tab and 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 returns status=already_gone, closed_by=user without reusing its native id. An actual owned close returns closed_by=agent; an explicit unowned/operator override returns closed_by=none so it is not counted as task-owned cleanup. If already_gone is returned but a page with the same work is still visible, call list_all_tabs and verify URL/title before deciding whether a new session/generation should be closed; BTAP never auto-transfers ownership by URL. A Chrome tabs.onReplaced identity mapping is safe and also migrates the ownership claim. Set only_if_agent_owned=false only 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, default true)scan_page — read the page as simplified HTML or text. Returns links mapping each #rN ref in the content to its absolute URL, and offscreen + hint when 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 explicit activate_tab. When the page can be probed, render_state/content_ready distinguish real content from a loading, hydrating, or shell-only SPA; retry or use wait_for before 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, or window globals. Optional extra_js runs caller code and can modify the page or send requests.
session_id (string, optional), text_only (boolean, optional): default false, cutlist (boolean, optional): default true; collapse repetitive lists, maxchars (integer, optional): default 35000, instruction (string, optional), extra_js (string, optional), timeout (number, optional): default 15, frame (array, optional): non-empty path of CSS strings or structured frame locators, max_targets (integer, optional): default 80, range 0–200
observation.targets adds current locators, control names, editability, rectangles and recommended_tool with a reason. Pass a returned locator unchanged as the page_click / page_type selector; re-observe after page changes. observation.frames lists uninspected child documents: pass an entry's frame back to scan_page to inspect it, including cross-origin/OOPIF frames. Open shadow roots are included; this is a bounded DOM observation, not a complete accessibility tree. max_targets caps controls and frame entries together, separately from the content budget; 0 disables enumeration and truncated=true means more targets or DOM nodes remain uninspected. Rectangles use the observed document's viewport CSS pixels and are not hit-tested. verify_coordinate_target recommends 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 report select_existing_option. Only top-document file inputs outside shadow roots recommend upload_files, using selector=locator.css; frame/shadow file uploads report unsupported. Frame scans reject extra_js and omit the parent's readiness probe; top-document extra_js preserves its execution semantics and reports target metadata unavailable.
The optional built-in readiness probe releases its tab reservation on timeout. Missing render fields mean readiness is unknown; use wait_for for 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. selector accepts legacy CSS or the structured locator object described under background page input. Caller-provided js is evaluated repeatedly and can have side effects; use a read-only predicate. A timeout with operation_id retains a pending check. Timed-out selector/text/URL probes can release the tab without discarding that receipt: reservation_held=false permits another command, and get_execute_js_result in the same MCP session can collect the delayed reply. Caller-provided js stays reserved. When reservation_held is 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; default false, timeout (number, optional): default 15, 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, unless wait_ready=false, document.readyState is complete; then returns final url, title and ready_state. Use after a click or open_url that 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 as wait_for. A pending probe with reservation_held=false no longer blocks the tab; its delayed reply remains available through get_execute_js_result in the same MCP session.
url_pattern (string): regex or substring to match against the URL, timeout (number, optional): default 15, wait_ready (boolean, optional): require readyState === 'complete', default true, session_id (string, optional)scroll_page — scroll and report the new position, so a long page can be read in passes.
to (string, optional): default bottom; also accepts top, a pixel offset, or a CSS selector to bring into view, session_id (string, optional), timeout (number, optional): default 15execute_js — run JavaScript in the page and return the result. timeout is one end-to-end deadline covering dialog-policy setup, monitor snapshots, delivery/retry, navigation inspection, and cleanup; an explicit session_id is forwarded through every one of those roundtrips instead of relying on the process default. Set wait=false for a genuinely long task: once the extension acknowledges delivery, BTAP returns status="in_progress" plus an operation_id; claim the result with get_execute_js_result instead of replaying the script. dialog_policy="manual" is intentionally unavailable in background mode. When a script navigates the page, status is navigated (not success) with landed_url; the script's return value is genuinely lost in that case and is reported as such rather than substituted. dialog_policy decides what happens if the script opens alert/confirm/prompt: dismiss (default) and accept answer it and report it under dialogs, while manual pauses a synchronous script with the native dialog still open and returns blocked_by_dialog — call handle_dialog to release it. A tab already holding a manual pause returns busy immediately. Use wait_for/wait_for_url instead of delayed setTimeout or sleep Promises when waiting for page state. When the JSON-encoded js_return exceeds the 24 KiB UTF-8 inline limit, BTAP writes the complete value to a private temporary JSON file and returns result_file, result_bytes, result_sha256, and result_format instead of a truncated inline value.
Cannot access contents of the page error must be classified before retrying: if the script attempted window.open or navigation, use open_new_tab (Chrome may block it without a user gesture); if injection into the current tab is forbidden, choose a normal scriptable http/https tab or the supported CDP route. Do not treat the message as proof that reading the current page failed.timeout does not cancel dispatched JavaScript. See Troubleshooting for timeout recovery and frame-preparation errors.script (string), session_id (string, optional), no_monitor (boolean, optional): default false, timeout (number, optional): default 15, dialog_policy (string, optional): dismiss (default), accept, or manual, wait (boolean, optional): default trueundefined and non-finite numbers become null; 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. A result_file preserves the complete converted value, including those markers.return in a complex async body, preferably (async () => { /* work */ return value; })(). Ambiguous bodies may complete with null; await(expr) can parse as a call to an ordinary function named await, so use the async IIFE when that distinction matters.result_file_scope="js-value". If writing fails, result_json preserves the complete value with result_json_scope="js-value"; see Complete JSON results for decoding and receipt semantics.get_execute_js_result — read or briefly wait for an operation_id, from the same MCP session that submitted it. Accepts handles from execute_js and 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 as late_result (success and data), with late_reply_age in seconds. The original unknown receipt and retry_safe=false remain; 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 lossless result_file metadata as execute_js; for a late value, that metadata is inside late_result and its data becomes null.
Accepts the nested reconciliation.bridge_operation.operation_id from a failed open_new_tab status probe. A known read-only wait, inventory or creation-status probe may release its reservation on timeout while retaining its receipt. Check reservation_held; it describes that probe, not the outcome of the tab creation.
operation_id (string), timeout (number, optional): default 0, range 0–120handle_dialog — inspect or answer a dialog left open on a tab. action="manual" reports it without choosing (blocked_by_dialog, or no_dialog if nothing is open); accept/dismiss answer it and release any paused execute_js or open_url. prompt_text supplies the text for an accepted prompt.
action (string), prompt_text (string, optional), session_id (string, optional), timeout (number, optional): default 3, capped at three secondsresolve_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.files is 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): default 30get_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): name is required, plus optional value/url/domain/path/expires (Unix seconds)/httpOnly/secure/sameSite. Uses CDP Network.setCookie, so HttpOnly and cross-path cookies work; falls back to document.cookie only when CDP is unavailable, and then reports which cookies could not carry HttpOnly. Cookies with neither url nor domain are scoped to the current page.
cookies (string or list or dict), session_id (string, optional), tab_id (integer, optional), timeout (number, optional): default 20delete_cookies — delete a cookie by name. Uses CDP Network.deleteCookies, falling back to expiring it via document.cookie. Scope with domain/path, or url to 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): default 20storage_get — read localStorage or sessionStorage. Omit key to page with offset/max_items/max_bytes; returns next_offset and truncated. The default timeout is 30s and a failed call does not close the MCP session.
key (string, optional), area (string, optional): local (default) or session, session_id (string, optional), timeout (number, optional): default 30, 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) or session, session_id (string, optional), timeout (number, optional): default 30Role 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.
selector or viewport coordinates. Exactly one targeting mode: either selector, or both x and y. With a selector, each omitted offset axis uses the element centre; a supplied offset_x or offset_y is 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 returns obscured with occluded_by, and an off-screen point returns outside_viewport. Top-document targets can be scrolled into view (scrolled_into_view); framed clicks do not scroll automatically. A verified click carries hit_verified: true. Screenshot coordinates are device pixels: divide by devicePixelRatio before using them as CSS coordinates. Challenge replies keep the bounded challenge_detected/attempts/challenge_stalled behavior.
selector (string/object, optional), x (number, optional), y (number, optional), offset_x (number, optional), offset_y (number, optional), button (string, optional): default left, clicks (integer, optional): default 1, session_id (string, optional), timeout (number, optional): default 15selector is 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 returns status="invalid_selector" instead of a raw SyntaxError. Successful and failed target resolution includes a redacted active_element descriptor and focus_confirmed when focus was attempted, so omitted-selector input is auditable. clear=true selects the existing value first; submit_key sends one key afterwards.
text (string), selector (string/object, optional), clear (boolean, optional): default false, submit_key (string, optional), session_id (string, optional), timeout (number, optional): default 15enter or ctrl,shift,k.
keys_csv (string), session_id (string, optional), timeout (number, optional): default 15x1 (number), y1 (number), x2 (number), y2 (number), duration (number, optional): default 0.3, button (string, optional): default left, session_id (string, optional), timeout (number, optional): default 15Temporary, 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.
notifications, geolocation (or location), camera, microphone. setting is allow, block, or ask. In safe, every allow requires approval; default lab applies it without elicitation (BROWSERTAP_LAB_NO_ELICIT=1 semantics). Declining returns requires_user_action and changes nothing. clipboard returns unsupported, because its exact prior state cannot be restored. Omit origin to use the target tab's current origin; only http/https origins are accepted. If an established lease loses restoration support, manual_recovery retains its prior setting and recovery guidance and stops automatic retries.
permission (string), setting (string): allow, block, or ask, origin (string, optional): defaults to the tab's origin, duration_seconds (integer, optional): 60–600, default 300, session_id (string, optional)manual_recovery records. Omit both origin and permission to 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)raw_cdp_blocked before 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): default 20batch_json must be a JSON object with cmd: "batch" and a commands array of cdp, tabs, or cookies objects. 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)list_tabs never shows.
session_id (string, optional)Page.printToPDF; validates PDF bytes and atomically writes save_path. save_path is relative and resolves under ~/Downloads/browsertap; an absolute path or a .. escape is rejected with ValueError. A timeout forcibly releases its debugger lease.
save_path (string), session_id (string, optional), landscape (boolean, optional): default false, print_background (boolean, optional): default true, prefer_css_page_size (boolean, optional): default true, scale (number, optional): default 1.0, range 0.1–2.0, page_ranges (string, optional), timeout (number, optional): default 30On 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.
session_id (string, optional)extension_id (string), enabled (boolean), session_id (string, optional)extension_id (string), show_confirm_dialog (boolean, optional): default true, session_id (string, optional)session_id (string, optional)title (string), url (string, optional): omit to create a folder, parent_id (string, optional), session_id (string, optional)bookmark-backups in the local state directory, then remove the bookmark or folder. Returns backup_path and backup_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): default false, session_id (string, optional)externally_connectable.
extension_id (string), message_json (string): JSON payload as text, session_id (string, optional)session_id (string, optional), include_bodies (boolean, optional): default true, max_entries (integer, optional): default 500, range 10–2000, max_body_bytes (integer, optional): default 262144, range 1024–2097152, body_timeout (number, optional): default 5, range 0.1–10 seconds, timeout (number, optional): default 10url_pattern is compiled by the browser as a JavaScript RegExp; invalid patterns return a structured error and leave the capture running for retry.
session_id (string, optional), url_pattern (string, optional): JavaScript RegExp, resource_type (string, optional), status_min/status_max (integer, optional): 100–599, include_response_bodies (boolean, optional): default true, timeout (number, optional): default 10console.* and uncaught exceptions.
session_id (string, optional), max_entries (integer, optional): default 500, range 10–5000, timeout (number, optional): default 10filter='user' retains page MAIN/default-context output and excludes isolated extension/content-script contexts; empty/all preserves the complete buffer.
session_id (string, optional), offset (integer, optional): default 0, max_items (integer, optional): default 200, clear (boolean, optional): default false, filter (string, optional): user or all, timeout (number, optional): default 10session_id (string, optional), timeout (number, optional): default 10full_page, or explicit clip modes. PNG, JPEG, and WebP are supported; quality is valid only for JPEG/WebP. Returns text metadata plus attached MCP image content; save_path only 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_height parsed from the returned bytes and pixel_space: "device" (CSS × devicePixelRatio), so a point read off the picture is not fed straight to page_click. A header it cannot parse reports null dimensions plus a dimensions_note rather than a guess — size is the byte count, not a dimension.
session_id (string, optional), tab_id (integer, optional), format (string, optional): default png, full_page (boolean, optional): default false, clip (object, optional): x,y,width,height, optional scale, quality (integer, optional): 0–100 for JPEG/WebP, save_path (string, optional), return_base64 (boolean, optional): default false, timeout (number, optional): default 20[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): default false; must be true to inspect.[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. Returns status="success", cancelled=true only after observing the original window gone. Uncertain delivery or closure returns unknown, retry_safe=false; inspect state before another action. Each opted-in attempt consumes the ticket, including refusals. Both tools report desktop, on_screen and input_quiet diagnostics.
ticket (string, required), desktop_opt_in (boolean, optional): default false; must be true to 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.
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.
Run browsertap doctor first. For connection, version, dialog,
permission, and physical-input recovery procedures, see the dedicated
troubleshooting guide.
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.
FAQs
Real-browser MCP server with a BrowserBridge/CDP transport, background-tab page input, screenshots, and cookies
We found that browsertap-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.