Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

BrowserTap MCP

БесплатноНе проверен

MCP server that drives the real Chrome you already use, through an extension and CDP.

GitHubEmbed

Описание

MCP server that drives the real Chrome you already use, through an extension and CDP.

README

browsertap-mcp

English | 中文文档

Offline CI Python 3.10+ License: MIT

Usage guide · Troubleshooting · Security · Contributing · Changelog

A Model Context Protocol (MCP) server that drives the real Chrome you are already using, through a Chrome extension and the Chrome DevTools Protocol. Your agent works inside your existing browser session, so logins, cookies, and open tabs are all already there — no separate sandbox browser to authenticate again.

Current release: unified Python package, bridge, and unpacked Chrome extension 0.4.12.

It also reaches past the page: five direct tools provide real mouse and keyboard input at the OS level when page-level input is not enough. resolve_leave_dialog is one additional, narrowly scoped path that can send Enter after two protocol attempts fail. safe asks before physical input, while the default lab profile runs without elicitation and still enforces the cross-process lock, quiet-input gate, target activation, and on-screen confirmation.

Start in 60 seconds

Three steps. Each one is spelled out in full under Getting started below, with the Windows PowerShell paths and the config for every supported client.

# 1. Install from source. There is no PyPI release yet.
git clone https://github.com/LinVireo/browsertap-mcp.git && cd browsertap-mcp
python -m venv .venv && ./.venv/bin/python -m pip install -e ".[desktop]"
./.venv/bin/browsertap extension-path   # prints the directory step 2 needs

# 3. Point your MCP client at that same executable (Claude Code shown).
claude mcp add browsertap -- "$PWD/.venv/bin/browsertap"

On Windows the same three commands use .\.venv\Scripts\python.exe and .\.venv\Scripts\browsertap.exe.

Step 2 is manual, and it is the slow one. There is no Chrome Web Store listing yet, so the extension is loaded by hand: open chrome://extensions, turn on Developer mode, click Load unpacked, and pick the directory extension-path printed. Then open an ordinary http:// or https:// page -- about:blank runs no content script, so no session is established.

Then ask your agent what tabs do I have open? If the list comes back empty, run browsertap doctor: it names one cause and the one matching advice.

Key features

  • Real browser, real session — attaches to your running Chrome/Edge/Opera. Logged-in sites, cookies, and page context are preserved.
  • Background by default — a selected tab is not a foreground tab. switch_tab retargets without raising anything, and page work runs in the tab you named while you keep using the screen.
  • Page reading — scan any page into simplified HTML or text, sized for a model's context. Long links are shortened to #r1 refs and the real URLs come back alongside, so a results page stays both small and navigable.
  • JavaScript execution — run arbitrary JS in the page.
  • Background page inputpage_click, page_type, page_press, and page_drag dispatch trusted CDP input events at viewport coordinates inside one named tab, without moving your cursor or changing which tab is visible.
  • Waiting and scrolling — wait for a selector, text, URL, or JS condition; scroll and re-scan long pages. scan_page reports how much it left outside the viewport instead of dropping it silently.
  • Explicit dialog policiesalert, confirm, prompt, and beforeunload each get a per-call dismiss/accept/manual policy and are reported truthfully; handle_dialog resolves one that is left open.
  • Temporary site permissions — grant notifications, geolocation, camera, or microphone to one origin for 60–600 seconds; the prior setting is restored automatically.
  • Native CDP access — single commands or batches. Addressable by tab, extension id, or target id.
  • Authenticated native downloads — download attachments through Chrome's download manager with the active browser profile's cookies, wait for completion, and receive the verified local path.
  • Tab-less operation — extension management, CDP target listing, and tab listing/closing go straight to the extension's service worker, so they work even with zero tabs open.
  • Page screenshots — page capture via CDP is returned as MCP image content and can also be saved to disk; full desktop capture is available for physical-input checks. A model without image support must use scan_page, page APIs, or OCR to inspect content.
  • Guarded real physical input — OS-level mouse move/click/drag, typing, and hotkeys are the last-resort path. lab can run without elicitation; safe prompts per call. Both profiles keep the lock, quiet-input gate, ownership checks, target activation, and on-screen confirmation.
  • Multi-browser — Chrome, Edge, and Opera can all connect to one bridge at the same time without clobbering each other's sessions.

Requirements

  • Python 3.10+
  • Chrome, Edge, or Opera
  • Linux, macOS, or Windows. OS-level input on Linux requires an X11 desktop.
  • A desktop session, not a container. There is no Docker image on purpose: the server attaches to the Chrome you are signed into, through an extension a human loads once, so an isolated container has no browser to drive.
  • Claude Code, or any other MCP client

Getting started

1. Install

Clone the repository, create a virtual environment, and install the recommended desktop feature set:

Windows PowerShell

git clone https://github.com/LinVireo/browsertap-mcp.git
Set-Location browsertap-mcp
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[desktop]"
.\.venv\Scripts\browsertap.exe extension-path

Linux or macOS

git clone https://github.com/LinVireo/browsertap-mcp.git
cd browsertap-mcp
python -m venv .venv
./.venv/bin/python -m pip install -e ".[desktop]"
./.venv/bin/browsertap extension-path

The core install (pip install -e .) omits OS-level mouse/keyboard and desktop capture dependencies. Use it only when those tools are intentionally disabled. After the first PyPI release, pip install "browsertap-mcp[desktop]" will be the non-editable install path; until then, the source install above is the supported 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.

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 a desktop screenshot really means the monitor, 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 WebSocket port. HTTP uses PORT+1, and PORT+2 is a lock socket that keeps exactly one bridge hosting those two (a second bridge stays up and works through the first). Not to be confused with the separate spawn.lock file, which is what stops several MCP sessions from starting several daemons at once. For a custom port, also tell the extension once — see docs/TROUBLESHOOTING.md.
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. Editors do not need individual token configuration.
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_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 allowed to open the bridge WebSocket. Extension origins are allowed automatically; do not add broad or untrusted origins.
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 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 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, ext_never_registered, sw_slept_or_dropped, registering, or bridge_unreachable, and advice is the matching one-line fix. registering means the extension is connected but no normal http(s) content tab is ready.

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)

BTAP ships two skills that tell a calling agent how to drive it. They are ordinary Markdown and completely optional — every tool works without them. What they add is the judgement the tool descriptions cannot carry: which tool to reach for first, when session_id is mandatory, and which tabs belong to you and must be left alone.

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.

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

An upgrade is three steps, not one: the three parts do not become current at the same moment, and step 3 fails silently if you skip it.

  1. Update the package — pip install -U browsertap-mcp once it is on PyPI, or git pull in a source checkout. A new MCP session picks this up immediately.
  2. browsertap bridge --restart. The daemon is long-lived and outlives every MCP session, so until it restarts it keeps serving the old code.
  3. Open chrome://extensions and press Reload on the extension. Its files were replaced on disk, but Chrome keeps running the build it already loaded, and no command can make it re-read them.

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

  1. Stop the managed daemon with browsertap bridge --stop.
  2. Open chrome://extensions (or the equivalent page in Edge/Opera) and remove the unpacked BrowserTap Bridge extension.
  3. Remove the browsertap entry from each MCP client's configuration.
  4. Run 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.
  5. 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:

  1. Chrome extension (MV3) — injected into real pages, reaches tabs, cookies, debugger, and management through Chrome APIs.
  2. 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. Sessions are keyed clientId:tabId, so several browsers and profiles coexist.
  3. MCP server — exposes the whole thing as MCP tools.

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.

Two kinds of coordinates, two kinds of authority. 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. mouse_move/mouse_click/mouse_drag take desktop screen coordinates and drive your real cursor. The two are not interchangeable, and a viewport coordinate pasted into mouse_click will land somewhere else entirely.

Three pixel units, and screenshots do not use the one you click with. Viewport coordinates are CSS pixels — the space getBoundingClientRect reports. Desktop coordinates are physical screen pixels. 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. A desktop screenshot is not resized, so pixel_space: "physical" and its pixels are already mouse_click's coordinates. 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: physical input and site allow proceed without elicitation. safe prompts for every action. Both profiles keep the cross-process lock, quiet-input gate, target activation, ownership protection, and on_screen check, so higher authority never means stale or misdirected input. The quiet gate's reach is bounded by what the OS exposes rather than by the profile; input_quiet.enforced in the result says whether it could observe this machine at all.

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.

Permissions are leases, not grants. set_site_permission covers one origin for 60–600 seconds, records the prior setting, and restores it on expiry/reset/service-worker restart. safe prompts for every allow; default lab applies it without elicitation. Browser capabilities that cannot be restored 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://extensionschrome.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, restore that URL when the tab still exists, and never close it.

Decision order: run list_tabs; borrow an existing match only for read-only/light work; open an A tab for navigation, forms, or other state changes; 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, depend on the shared default session, reuse an old native tab id, omit generation-aware cleanup, or leak an A tab. Separate concurrent tasks should use separate A tabs instead of competing for the same U tab.

Structured statuses and recovery fields

Expected interruptions come back as a status field, not an exception:

status 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 Approval was declined, cancelled, or unavailable — nothing was done.
busy Another BTAP process holds the physical-input lock, or the tab already has a pending manual execution. Returned immediately, never queued.
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.

Disclaimers

This server drives your real browser and your real desktop. Anything it can do, you can do — and it inherits every session you are logged into.

  • Mouse moves, clicks, typing, and hotkeys are real OS-level input, not synthetic page events. safe prompts per call; lab can reuse or disable prompts. Once allowed, it drives your actual desktop.
  • 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 the current target. Pass it explicitly for anything that changes state — the shared default is a single value every task on this bridge sees, and another task retargeting it is exactly how a click lands on the wrong page. Session ids look like chrome_a1b2c3:456; pass them verbatim and never split them. Tools marked no tab needed talk to the extension's service worker and work with zero tabs open.

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=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. 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. No parameters.
  • get_automation_profile — inspect whether the current MCP process uses lab or safe.
  • set_automation_profile — switch the current MCP process between lab|safe; the override is not persisted and does not reload the extension.
    • mode (string): lab or safe
  • list_tabs — list connected tabs. Each carries a browser field. No parameters.
  • list_all_tabs(no tab needed) list every open tab, including 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 to ask.
  • switch_tab — set the target tab for later calls. A url_pattern must match exactly one tab; if several match, select one with its full session_id. It does not raise the tab or focus the browser: activate defaults to false, so retargeting never disturbs what you are looking at. Pass activate=true, or call activate_tab, when you actually 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.
  • 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_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)
  • open_url — navigate the current tab. Global behavior remains 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 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 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.
  • open_new_tab — open a background tab by default with a unique operation_id and wait a bounded time for exact session/generation registration; pass active=true only when foreground work is genuinely required. Returns {operation_id,tab_id,session_id,generation,ready,owned,opener,owner_id,load_status}. The extension deduplicates repeated requests with the same operation id. Ownership is registered only from a completed record containing the exact client_id+tab_id+generation, even when ready=false; ready only says whether session-scoped tools can be used immediately. A pre-create registry uncertainty returns status="unknown",may_have_created=false,retry_safe=true; after create dispatch, an unresolved ACK/reconciliation returns status="unknown",may_have_created=true,retry_safe=false. Keep its random owner_id capability and use it only for that task's cleanup. For an unresolved dispatched create, do not call open_new_tab again for the same request; retain operation_id as diagnostic/support evidence.
    • 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)
  • close_tabs(no tab needed) accept native numeric tab ids or full 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. 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)
Page reading and execution
  • 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.
    • 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
  • wait_for — wait until a condition holds, then return. Use this instead of polling scan_page, which re-serializes the whole DOM each time. Polling happens inside the page, so a 30s wait still costs one bridge roundtrip. Exactly one condition is required. selector accepts legacy CSS or the structured locator object described under background page input.
    • 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. Polls in-page across navigation chunks, so a long wait is still cheap.
    • 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 15
  • execute_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 shared default. 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 the 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; no_response reports delivery_state and retry_safe, and BTAP never replays an acknowledged script whose side effects may already have run.
    • 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
  • handle_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 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.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 30
  • 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): 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 20
  • delete_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 20
  • storage_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 30
Background page input

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 binds the driver to that tab for its duration and restores the shared default afterwards, so a directed call cannot leave another task's target moved. A session_id naming a dead tab is refused rather than redirected to a live one.

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. exact applies to role/name or text matching; frame walks one or more same-origin iframe locators; shadow walks open Shadow DOM hosts. Zero matches return not_found, multiple matches return ambiguous, and cross-origin or closed roots are reported without dispatching input.

  • page_click — click a CSS/structured 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 on that axis. Missing, ambiguous, non-interactable, cross-origin-frame, and closed-shadow targets return structured status without input dispatch. In selector mode the point is also hit-tested in the page before dispatch: a target below the fold is scrolled into view (scrolled_into_view), one whose pixel belongs to something else returns obscured with occluded_by naming the overlay, and one still off screen returns outside_viewport — in both cases nothing is clicked, because a dispatched click would have landed on the other element and reported success. A verified click carries hit_verified: true. Coordinate mode is not hit-tested: coordinates name a pixel, not an element — and a pixel read off capture_page_screenshot is a device pixel, so it needs dividing by devicePixelRatio first. 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 15
  • page_type — insert text into a CSS/structured-locator field, or into whatever already has focus when selector 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. 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 15
  • page_press — press a key or a comma-separated modifier chord in the tab, e.g. enter or ctrl,shift,k.
    • keys_csv (string), session_id (string, optional), timeout (number, optional): default 15
  • page_drag — drag between two viewport points as one uninterrupted event sequence.
    • x1 (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 15
Site permissions

Temporary, origin-scoped permission leases backed by chrome.contentSettings. Every lease records the prior setting and restores it — on expiry, on explicit reset, and after a service-worker restart or browser restart.

  • set_site_permission — set one permission for one origin, for 60–600 seconds. Supported: 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.
    • 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)
  • reset_site_permissions — restore matching leases now instead of waiting for expiry. Omit both origin and permission to restore every lease on that browser.
    • origin (string, optional), permission (string, optional), session_id (string, optional)
CDP
  • cdp_command — send one CDP command.
    • 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 20
  • cdp_batch — send a batch; batch_json must be a JSON object with cmd: "batch".
    • 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_tabs never shows.
    • session_id (string, optional)
  • save_pdf — bounded Page.printToPDF; validates PDF bytes and atomically writes save_path. 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.12.0, page_ranges (string, optional), timeout (number, optional): default 30

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) uninstall another extension. Confirmation defaults on; set it off only for an explicitly selected disposable/test extension. BTAP cannot uninstall itself through its active response channel.
    • extension_id (string), show_confirm_dialog (boolean, optional): default true, 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) remove a bookmark or folder subtree.
    • bookmark_id (string), recursive (boolean, optional): default false, 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): 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 10
  • 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_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 10
  • console_capture_start — start collecting console.* and uncaught exceptions.
    • session_id (string, optional), max_entries (integer, optional): default 500, range 10–5000, timeout (number, optional): default 10
  • 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/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 10
  • console_capture_stop — return the remaining console messages and release its debugger lease.
    • session_id (string, optional), timeout (number, optional): default 10
Screenshots
  • capture_page_screenshot — page capture via CDP with viewport, full_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. 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
  • capture_desktop_screenshot — captures the currently visible OS virtual desktop across all displays and returns metadata plus MCP image content. This is not a selected/background-tab capture; it may include other applications. save_path only adds a disk copy. width/height/left/top and the image itself are physical screen pixels (pixel_space: "physical"), unscaled, so they are the same space mouse_click takes.
    • save_path (string, optional), return_base64 (boolean, optional): default false
Physical input

Real OS-level input at desktop screen coordinates, in physical pixels on the virtual desktop (not CSS pixels, not device pixels). It moves your actual cursor and types into whatever has focus. Prefer the page_* tools: they are precise, do not interrupt you, and work on a background tab. Reach for these only when page input genuinely cannot work — browser chrome, native file pickers, extension popups, OS dialogs.

In safe, each of these five direct tools asks through MCP elicitation. Default lab uses BROWSERTAP_LAB_NO_ELICIT=1 semantics and does not prompt; setting it false restores session-level lab approval. Decline, cancel, or unavailable elicitation returns requires_user_action; every profile still enforces the lock, quiet window, ownership, activation, and foreground check. resolve_leave_dialog is a sixth physical-input path, limited to a final Enter fallback after two protocol-level attempts and subject to the same gate.

After approval the sequence is fixed: take the cross-process lock (contended → busy, returned immediately, never queued), check the coordinates against the real display geometry, wait out 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 actually 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 every result carries an input_quiet block naming the markers it sampled, with enforced: false when there were none — on such a machine read a pass as unverified rather than as an idle desktop. All five direct tools take session_id — the same one you pass every other tool — and raise that tab; without one they fall back to the shared global target, which another task may have changed. Use activate_session="none" only for intentional input to the already-visible desktop or native UI. If the tab cannot be confirmed on screen the result is activation_failed and no input is sent, so a minimised window produces an error rather than a click into the wrong place.

A point on no display at all is refused with coordinates_off_screen, before the tab is raised and before anything is dispatched: SetCursorPos silently clamps an out-of-range point and reports success, so (2400, 1300) on a 1920×1080 panel becomes a click at (1919, 1079) — the hot corner that can minimise every window — and nothing in the reply would have said so. The rectangle it checks against is the virtual desktop across all displays, reported as screen_bounds on every physical result and by pointer_info. Where the geometry cannot be read the call proceeds with screen_bounds.enforced: false and a note, the same way the quiet gate reports a vacuous pass rather than taking physical input away from a machine where it works.

  • mouse_movex (integer), y (integer), duration (number, optional): glide time in seconds, default 0 (jumps straight to the point), session_id (string, optional): tab to raise, activate_session (string, optional): default current (raise the target tab first), a session id to raise a different tab, or none
  • mouse_clickx (integer, optional), y (integer, optional): omit both to click wherever the cursor already is, button (string, optional): default left, also right or middle, clicks (integer, optional): default 1, interval (number, optional): seconds between clicks, default 0.1, session_id (string, optional): the tab to raise, and what you should normally pass, activate_session (string, optional): default current, a session id, or none
  • mouse_dragx1 (integer), y1 (integer), x2 (integer), y2 (integer), duration (number, optional): seconds spent moving with the button held, default 0.3, button (string, optional): default left, session_id (string, optional): tab to raise, activate_session (string, optional): default current, a session id, or none
  • type_texttext (string), interval (number, optional): seconds per keystroke, default 0.01, click_x (integer, optional), click_y (integer, optional): click there first to focus the field, session_id (string, optional): the tab to raise, and what you should normally pass, activate_session (string, optional): default current, a session id, or none
  • hotkeykeys_csv (string): comma-separated, e.g. ctrl,c, session_id (string, optional): tab to raise, activate_session (string, optional): default current, a session id, or none
  • pointer_info — current cursor position, primary-display size, and the screen_bounds rectangle spanning every display (with the probe that answered in source, or null when the geometry cannot be read). Read-only, no approval needed. No parameters.

Troubleshooting

Run browsertap doctor first. For connection, version, dialog, permission, and physical-input recovery procedures, see the dedicated troubleshooting guide.

Credits

BTAP is maintained by LinVireo. The MIT copyright notice in LICENSE is retained unchanged (zhea); maintenance and copyright attribution are distinct roles. The canonical public repository for this distribution is LinVireo/browsertap-mcp.

The browser layer here began as GenericAgent's, and part of it still is. Thanks to that project and its author for the original implementation.

Originally from GenericAgent:

  • simphtml.py -- still substantially upstream's file, extended here
  • TMWebDriver.py (now maintained as browser_bridge.py)
  • the tmwd_cdp_bridge Chrome extension resources

GenericAgent is MIT-licensed, so its copyright notice has to reach anyone who receives a copy of this. It is reproduced in full, with a line-for-line measurement of how much of each file is still upstream's, in THIRD-PARTY-NOTICES.md -- which ships inside both the wheel and the sdist, not only in this repository. Everything else in this distribution -- the MCP tool surface, the bridge and its token authentication, the release evidence pipeline, the test suite, and both READMEs -- was written here.

If you fork or redistribute this, keep both notices: LICENSE and that file.

License

MIT

from github.com/LinVireo/browsertap-mcp

Установить BrowserTap MCP в Claude Desktop, Claude Code, Cursor

Рекомендуется · одна команда, все IDE
unyly install browsertap-mcp

Ставит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.

Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh

Или настроить вручную

Выполни в терминале:

claude mcp add browsertap-mcp -- uvx browsertap-mcp

Пошаговые гайды: как установить BrowserTap MCP

FAQ

BrowserTap MCP MCP бесплатный?

Да, BrowserTap MCP MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для BrowserTap MCP?

Нет, BrowserTap MCP работает без API-ключей и переменных окружения.

BrowserTap MCP — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить BrowserTap MCP в Claude Desktop, Claude Code или Cursor?

Открой BrowserTap MCP на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Playwright

Browser automation, scraping, screenshots

Microsoftавтор: Microsoft

Puppeteer

Browser automation and web scraping.

modelcontextprotocolавтор: modelcontextprotocol

opentabs-dev/opentabs

Plugin-based MCP server + Chrome extension that gives AI agents access to web applications through the user's authenticated browser session. 100+ plugins with a

opentabs-devавтор: opentabs-dev

robhunter/agentdeals

1,500+ developer infrastructure deals, free tiers, and startup programs across 54 categories. Search deals, compare vendors, plan stacks, and track pricing chan

robhunterавтор: robhunter

hlydecker/ucsc-genome-mcp

MCP server to interact with the UCSC Genome Browser API, letting you find genomes, chromosomes, and more.

hlydeckerавтор: hlydecker

34892002/bilibili-mcp-js

A MCP server that supports searching for Bilibili content. Provides LangChain integration examples and test scripts.

34892002автор: 34892002

achiya-automation/safari-mcp

Native Safari browser automation for AI agents with 80+ tools. No Chrome dependency, optimized for Apple Silicon with 60% less CPU overhead.

achiya-automationавтор: achiya-automation

agent-infra/mcp-server-browser

Browser automation capabilities using Puppeteer, both support local and remote browser connection.

bytedanceавтор: bytedance

aparajithn/agent-scraper-mcp

Web scraping MCP server for AI agents. 6 tools: clean content extraction, structured scraping with CSS selectors, full-page screenshots via Playwright, link ext

aparajithnавтор: aparajithn

apireno/DOMShell

Browse the web using filesystem commands (ls, cd, grep, click). 38 MCP tools map Chrome's Accessibility Tree to a virtual filesystem via a Chrome Extension.

apirenoавтор: apireno

Compare BrowserTap MCP with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории browse