agent-browser-mcp
Allows automation of the Opera browser via a Chrome extension and CDP, enabling real browser session control, tab management, page reading, JavaScript execution, and physical input.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@agent-browser-mcpOpen my GitHub and check pull requests"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
agent-browser-mcp
English | 中文文档
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.
It also reaches past the page: real mouse and keyboard input at the OS level, for cases where page-level JavaScript is not enough. Those five tools are the only ones that touch your desktop: safe asks on every call, while the default lab profile reuses session approval and can explicitly disable prompts.
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_tabretargets 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
#r1refs 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 input —
page_click,page_type,page_press, andpage_dragdispatch 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_pagereports how much it left outside the viewport instead of dropping it silently.Explicit dialog policies —
alert,confirm,prompt, andbeforeunloadeach get a per-calldismiss/accept/manualpolicy and are reported truthfully;handle_dialogresolves 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.
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.
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.Real physical input, behind approval — OS-level mouse move/click/drag, typing, and hotkeys, each requiring one accepted approval prompt for that exact call.
Multi-browser — Chrome, Edge, and Opera can all connect to one bridge at the same time without clobbering each other's sessions.
Related MCP server: cc-chrome-agent
Requirements
Python 3.10+
Chrome, Edge, or Opera
macOS or Windows
Claude Code, or any other MCP client
Getting started
1. Install
pip install -e .2. Load the Chrome extension
This project ships an unpacked extension that has to be loaded once by hand.
agent-browser-mcp extension-pathOpen chrome://extensions, turn on Developer mode, click Load unpacked, and pick the directory that command printed.
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.
3. Add the server to your client
Standard config works in most tools:
{
"mcpServers": {
"agent-browser-mcp": {
"type": "stdio",
"command": "agent-browser-mcp"
}
}
}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 agent-browser-mcp -- agent-browser-mcpAdd --scope user to make it available across all projects. For a virtualenv install:
claude mcp add agent-browser-mcp -- /path/to/venv/bin/agent-browser-mcpVerify 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":"agent-browser-mcp","command":"agent-browser-mcp"}'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:
agent_browser:
command: agent-browser-mcp
timeout: 120
connect_timeout: 60agent-browser-mcp 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.
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 agent-browser-mcp doctor.
Configuration
Environment variables
Variable | Default | Purpose |
|
| Bridge bind address. |
|
| WebSocket port. HTTP uses |
| unset | Set to |
| unset |
|
|
|
|
| unset | Set to |
|
| In lab, ordinary |
CLI
agent-browser-mcp # run the MCP server (stdio)
agent-browser-mcp extension-path # print the unpacked extension directory
agent-browser-mcp doctor # diagnose the local setup, as JSON
agent-browser-mcp bridge # run the bridge in the foreground
agent-browser-mcp print-hermes-config # print a Hermes config snippetdoctor reports the extension path, whether config.js was generated, port state, and connected tab count. It also returns a structured verdict: cause is one of healthy, ext_never_registered, sw_slept_or_dropped, or bridge_unreachable, and advice is the matching one-line fix — no manual netstat and curl archaeology.
How it works
Three layers:
Chrome extension (MV3) — injected into real pages, reaches
tabs,cookies,debugger, andmanagementthrough Chrome APIs.TMWebDriver bridge — 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 keyedclientId:tabId, so several browsers and profiles coexist.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.
Automation profiles. With AGENT_BROWSER_MODE unset, ABM defaults to lab: the first approved physical-input/site-allow action grants approval for the current MCP session, and AGENT_BROWSER_LAB_NO_ELICIT=1 can skip elicitation. safe still prompts for every action. Both profiles keep the cross-process lock, quiet-input gate, and on_screen check, so lab never queues stale input or sends while you are using the desktop.
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; lab reuses session approval or follows its no-elicitation setting. 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 ABM stops so you can finish it yourself in that same tab. ABM 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.
Structured statuses
Expected interruptions come back as a status field, not an exception:
| Meaning |
| Completed and verified as far as the protocol allows. |
| Navigation landed on a different URL than requested (login wall, SSO, canonical rewrite). |
| An |
| A JavaScript dialog is open and waiting for |
| Navigation was cancelled to keep the page; re-issue with |
| A dialog was seen but answering it failed; the tab may still be blocked. |
|
|
| Approval was declined, cancelled, or unavailable — nothing was done. |
| Another ABM process holds the physical-input lock, or the tab already has a pending manual execution. Returned immediately, never queued. |
| You used the mouse or keyboard during the post-approval quiet window, so no physical input was sent. |
| The target tab could not be confirmed on screen, so no physical input was sent. |
| The browser or extension API cannot provide this (e.g. clipboard permission leases). |
| A browser challenge made no progress within the attempt bound; hand the tab back to the user. |
| The script did not reach the tab or timed out — do not blindly retry anything with side effects. |
| The selector matched nothing; no input was dispatched. |
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.
safeprompts per call;labcan 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, activeTab, debugger, scripting, alarms, storage, contentSettings, declarativeNetRequest, management, bookmarks, and <all_urls>.
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.
get_setup_status — extension path, ports, connected tabs, and the active session. No parameters.
get_automation_profile / set_automation_profile — inspect or switch the current MCP process between
lab|safe; switching is not persisted and does not reload the extension.list_tabs — list connected tabs. Each carries a
browserfield. No parameters.list_all_tabs — (no tab needed) list every open tab, including
chrome-extension://pages thatlist_tabshides. Those never become sessions, so they have no session id; drive them withcdp_command(tab_id=...).session_id(string, optional): which browser to ask.
switch_tab — set the target tab for later calls. It does not raise the tab or focus the browser:
activatedefaults tofalse, so retargeting never disturbs what you are looking at. Passactivate=true, or callactivate_tab, when you actually need the tab in front.session_id(string, optional),url_pattern(string, optional): substring match,browser(string, optional):chrome,edge, oropera,activate(boolean, optional): defaultfalse.
activate_tab — bring a tab to the foreground and focus its window. This is the explicit way to raise a tab, and the only one that does not involve approving physical input. Check
on_screenin the reply: on Windows a minimised window cannot always be raised, andfalsemeans screen-coordinate clicks will miss.session_id(string, optional)
open_url — navigate the current tab. Global behavior remains
dismiss; lab automatically accepts beforeunload on configured shell/IDE hosts. If the extension'snavigateroute is unavailable on a heavy SPA, ABM falls back toPage.navigate.url,session_id,timeout,beforeunload,intent_leave(boolean, optional):falseforces page preservation
open_new_tab — open a tab and wait for load/session registration; returns
{tab_id,session_id,generation,ready,load_status}. The lifecyclegenerationprevents a reused native tab id from matching an older registration, soready=trueis immediately usable.url,timeout,active
close_tabs — (no tab needed) accept native numeric tab ids or full
client:tabIdsession ids, includingchrome-extension://tabs.
scan_page — read the page as simplified HTML or text. Returns
linksmapping each#rNref in the content to its absolute URL, andoffscreen+hintwhen content was left outside the viewport.session_id(string, optional),text_only(boolean, optional),cutlist(boolean, optional): collapse repetitive lists,maxchars(integer, optional),instruction(string, optional),extra_js(string, optional),timeout(number, optional)
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(string, optional): CSS match,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,timeout(number, optional),session_id(string, optional)
wait_for_url — wait for navigation to settle: blocks until the tab URL matches
url_pattern(regex, or plain substring — both are tried) and, unlesswait_ready=false,document.readyStateiscomplete; then returns finalurl,titleandready_state. Use after a click oropen_urlthat navigates;wait_for(url_pattern=...)only checks the URL and can return while the new document is still blank. 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): requirereadyState === 'complete', defaulttrue,session_id(string, optional)
scroll_page — scroll and report the new position, so a long page can be read in passes.
to(string, optional):bottom,top, a pixel offset, or a CSS selector to bring into view,session_id(string, optional),timeout(number, optional)
execute_js — run JavaScript in the page and return the result.
timeoutis one end-to-end deadline covering dialog-policy setup, monitor snapshots, delivery/retry, navigation inspection, and cleanup; an explicitsession_idis forwarded through every one of those roundtrips instead of relying on the shared default. When a script navigates the page,statusisnavigated(notsuccess) withlanded_url; the script's return value is genuinely lost in that case and is reported as such rather than substituted.dialog_policydecides what happens if the script opensalert/confirm/prompt:dismiss(default) andacceptanswer it and report it underdialogs, whilemanualpauses the script with the native dialog still open and returnsblocked_by_dialog— callhandle_dialogto release it. A tab already holding a manual pause returnsbusyimmediately.script(string),session_id(string, optional),no_monitor(boolean, optional),timeout(number, optional),dialog_policy(string, optional):dismiss(default),accept, ormanual
handle_dialog — inspect or answer a dialog left open on a tab.
action="manual"reports it without choosing (blocked_by_dialog, orno_dialogif nothing is open);accept/dismissanswer it and release any pausedexecute_jsoropen_url.prompt_textsupplies the text for an acceptedprompt.action,prompt_text,session_id,timeout(optional, 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.
upload_files — set files on a file input, which JavaScript cannot do (
input.filesis read-only). Runs as one CDP batch so the DOM node ids stay valid across the sequence.selector(string): the<input type=file>,paths(string or array of strings): absolute local paths,session_id(string, optional),timeout(number, optional)
get_cookies — read cookies for a page.
session_id(string, optional),tab_id(integer, optional)
set_cookies — write cookies into the real browser profile. Takes one cookie object or a list (JSON text is accepted):
nameis required, plus optionalvalue/url/domain/path/expires(Unix seconds)/httpOnly/secure/sameSite. Uses CDPNetwork.setCookie, so HttpOnly and cross-path cookies work; falls back todocument.cookieonly when CDP is unavailable, and then reports which cookies could not carry HttpOnly. Cookies with neitherurlnordomainare scoped to the current page.cookies(string or list or dict),session_id(string, optional),tab_id(integer, optional),timeout(number, optional)
delete_cookies — delete a cookie by name. Uses CDP
Network.deleteCookies, falling back to expiring it viadocument.cookie. Scope withdomain/path, orurlto target one site.name(string),domain(string, optional),path(string, optional),url(string, optional),session_id(string, optional),tab_id(integer, optional),timeout(number, optional)
storage_get — read localStorage or sessionStorage. Omit
keyto page withoffset/max_items/max_bytes; returnsnext_offsetandtruncated. The default timeout is 30s and a failed call does not close the MCP session.storage_set — write one localStorage/sessionStorage value (non-string values are JSON-encoded first). Verifies by read-back, so a quota-full or privacy-mode failure is reported instead of silently lost.
key(string),value(string),area(string, optional):local(default) orsession,session_id(string, optional),timeout(number, optional)
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 coordinates (relative to the top-left of the page area), never desktop coordinates.
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.
page_click — click a CSS selector or viewport coordinates. Exactly one targeting mode: either
selector, or bothxandy. With a selector, the click lands at its centre unlessoffset_x/offset_yshift it — which is how a Cloudflare Turnstile checkbox inside a cross-origin iframe gets clicked without reaching into the iframe's DOM. A missing selector returnsnot_foundand dispatches nothing. When a challenge widget is present, the reply carrieschallenge_detectedandattempts, and becomeschallenge_stalledonce repeated clicks stop changing it.selector(string, optional),x(number, optional),y(number, optional),offset_x(number, optional),offset_y(number, optional),button(string, optional): defaultleft,clicks(integer, optional): default1,session_id(string, optional),timeout(number, optional): default15
page_type — insert text into a CSS-selected field, or into whatever already has focus when
selectoris omitted. Xterm.js containers/descendants are automatically retargeted to.xterm-helper-textarea; when the background tab has no text editor focused and exposes one xterm helper, omittingselectorfocuses it automatically. A missing or unusable target returnsnot_foundwithout dispatching text or key events.clear=trueselects the existing value first;submit_keypresses a key afterwards (e.g.enter).text(string),selector(string, optional),clear(boolean, optional): defaultfalse,submit_key(string, optional),session_id(string, optional),timeout(number, optional): default15
page_press — press a key or a comma-separated modifier chord in the tab, e.g.
enterorctrl,shift,k.keys_csv(string),session_id(string, optional),timeout(number, optional): default15
page_drag — drag between two viewport points as one uninterrupted event sequence.
x1(number),y1(number),x2(number),y2(number),duration(number, optional): default0.3,button(string, optional): defaultleft,session_id(string, optional),timeout(number, optional): default15
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(orlocation),camera,microphone.settingisallow,block, orask. Insafe, everyallowrequires approval; the defaultlabprofile reuses approval for the MCP session or skips prompting whenAGENT_BROWSER_LAB_NO_ELICIT=1. Declining returnsrequires_user_actionand changes nothing.clipboardis accepted as a name but returnsunsupported, because its exact prior state cannot be restored. Omitoriginto use the target tab's current origin; onlyhttp/httpsorigins are accepted. The 60-second minimum is Chrome's MV3 alarm floor, not an arbitrary choice.permission(string),setting(string):allow,block, orask,origin(string, optional): defaults to the tab's origin,duration_seconds(integer, optional): 60–600, default300,session_id(string, optional)
reset_site_permissions — restore matching leases now instead of waiting for expiry. Omit both
originandpermissionto restore every lease on that browser.origin(string, optional),permission(string, optional),session_id(string, optional)
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, optional),extension_id(string, optional),target_id(string, optional)
cdp_batch — send a batch;
batch_jsonmust be a JSON object withcmd: "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_tabsnever shows.session_id(string, optional)
save_pdf — bounded
Page.printToPDF; validates PDF bytes and atomically writessave_path. A timeout forcibly releases its debugger lease.
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_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. ABM cannot uninstall itself through its active response channel.
get_bookmarks / create_bookmark / remove_bookmark — (no tab needed) read the tree, create bookmarks/folders, and remove a bookmark or folder subtree.
call_extension — (no tab needed) send JSON to another enabled extension; the target must allow ABM via
externally_connectable.
network_capture_start / network_capture_stop — continuously collect bounded request/response records and optional bodies. Defaults: 500-entry ring and 256 KiB per body. Always stop in cleanup so its debugger lease is released.
console_capture_start / get_console_messages / console_capture_stop — collect
console.*and uncaught exceptions; get supports paging/clear, and stop returns the remaining messages and releases the lease.
capture_page_screenshot — page capture via CDP. Returns text metadata plus attached MCP image content;
save_pathonly adds a disk copy and never suppresses the image attachment. Saving or attaching a screenshot does not mean a non-vision model saw its pixels: usescan_page,execute_js, a page-specific API, or OCR instead. Base64 is omitted unless explicitly requested.session_id(string, optional),tab_id(integer, optional),format(string, optional),save_path(string, optional),return_base64(boolean, optional): include base64 in structured metadata, defaultfalse
capture_desktop_screenshot — whole-screen capture, for verifying physical input.
save_path(string, optional)
Real OS-level input at desktop screen coordinates. 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 asks through MCP elicitation. Default lab reuses approval after the first accepted action; AGENT_BROWSER_LAB_NO_ELICIT=1 skips the prompt. Decline, cancel, or unavailable elicitation returns requires_user_action; every profile still enforces the lock, quiet window, and foreground check.
After approval the sequence is fixed: take the cross-process lock (contended → busy, returned immediately, never queued), wait out a short quiet window (you touched the mouse or keyboard → input_activity_detected, nothing sent), then raise the target tab, then act. mouse_click and type_text 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" to act on the desktop as-is. 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.
mouse_move —
x(integer),y(integer),duration(number, optional)mouse_click —
x(integer, optional),y(integer, optional),button(string, optional),clicks(integer, optional),interval(number, optional),session_id(string, optional): the tab to raise, and what you should normally pass,activate_session(string, optional): session id,current(default), ornonemouse_drag —
x1(integer),y1(integer),x2(integer),y2(integer),duration(number, optional),button(string, optional)type_text —
text(string),interval(number, optional),click_x(integer, optional),click_y(integer, optional),session_id(string, optional): the tab to raise, and what you should normally pass,activate_session(string, optional): session id,current(default), ornonehotkey —
keys_csv(string): comma-separated, e.g.ctrl,cpointer_info — current cursor position and screen size. Read-only, no approval needed. No parameters.
Troubleshooting
The client sees the server, but no tabs are connected. Check that the extension is loaded, and that a normal http/https page is open rather than a blank tab. Then run agent-browser-mcp doctor.
connected_tabs is 0. Usually the extension failed to load, there is no normal page open, or the extension was just reloaded and the page has not been refreshed. Refresh the page, or open a new URL, and run doctor again.
The client cannot start the server. Confirm the package installed, and that agent-browser-mcp is on PATH — if it is in a virtualenv, use the absolute path in your config. Then check doctor.
Physical input does nothing on macOS. Grant your terminal or MCP client Accessibility permission, plus Screen Recording if you need desktop capture.
Physical input returns requires_user_action and never prompts. Your MCP client does not implement elicitation. Page-level tools (page_click, page_type, page_press, page_drag) need no approval and cover most cases; the same applies to set_site_permission(setting="allow"), which cannot proceed without a prompt.
Physical input returns busy right away. Another ABM process holds the non-queued input lease. Stop this attempt and retry later rather than looping. ABM holds an OS advisory lock for the action's entire lifetime, even beyond the metadata lease's default 30-second TTL; TTL expiry never permits stealing ownership from a still-running action. After the action ends or its owner process exits, the next physical call can reclaim any stale metadata automatically. Do not delete the lock file, kill processes, or restart the bridge to clear it.
A tool rejects arguments that match the docs. Your client is still holding the schemas from an older server: restart the MCP session or the client. If the extension is the stale part, reload it manually at chrome://extensions; chrome.runtime.reload() restarts the service worker without re-reading files from disk.
A tab is stuck and every call on it returns blocked_by_dialog or busy. A manual dialog policy left a native dialog open and a paused execution behind it. Call handle_dialog(action="accept") or handle_dialog(action="dismiss") on that same session_id to release it. Other tabs keep working throughout.
A permission is still granted after the task finished. Leases restore on expiry, but you can force it with reset_site_permissions() — with no arguments it restores every lease on that browser. If a lease will not restore, it is retained and retried rather than dropped, so check bridge.log.
Credits
The browser automation core here was extracted from GenericAgent's browser stack and repackaged as an MCP server. Thanks to that project and its author for the original implementation.
Derived from or adapted from GenericAgent:
TMWebDriver.pysimphtml.pythe
tmwd_cdp_bridgeChrome extension resources
If you fork or redistribute this, please keep the attribution.
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityCmaintenancePixel-level browser automation MCP server that drives a real Chrome browser using screenshots as vision input and OS-level mouse/keyboard as output, evading anti-bot detection.3MIT
- Alicense-qualityDmaintenanceMCP server to control Chrome browsers locally or remotely via the Claude extension, enabling navigation, form filling, screenshots, and JavaScript execution from any MCP client.MIT
- Flicense-qualityCmaintenanceDrive your real, signed-in Chrome browser from any MCP client, enabling browser automation such as navigation, clicking, typing, and screenshots through standard MCP tools.1
- AlicenseAqualityAmaintenanceMCP server that lets AI agents drive your real Chromium browser with your existing signed-in sessions, providing visible, local, and inspectable automation for tasks like navigation, clicking, typing, and form filling.251Apache 2.0
Related MCP Connectors
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Access Kernel's cloud-based browsers and app actions via MCP (remote HTTP + OAuth).
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/0xlinn/agent-browser-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server