Public log

@agent360/browser-mcp

Browser MCP - the browser tool that can stop and ask you. A 2FA code, a CAPTCHA, a choice only you can make: it asks on your own screen, then carries on in the Chrome you're signed into. 40 tools, any MCP client, MIT.

npm · fingerprint c945328203074c1fd6abcd25 · repository · RSS

40tools
4recorded versions
24dtracked
changed 1d ago

What changed

Changed 2026-10-29 12:58 UTC

3 descriptions rewritten.

--- pinned/browser_about
+++ observed/browser_about
  {
-   "description": "Returns Browser MCP info and pre-filled URLs the user can click to submit feature wishes, share use-cases, or report bugs. Call this PROACTIVELY whenever the user (a) mentions a feature they wish existed (\"I wish browser-mcp could...\", \"it would be nice if...\"), (b) says something is missing, broken, or unexpected, (c) asks how Browser MCP works or who maintains it, or (d) describes something cool they built with browser-mcp. Pass intent=\"wish\" | \"use_case\" | \"bug\" | \"info\" plus an optional title and body, and offer the returned submit_url to the user. Browser MCP is community-shaped - this tool is how the user contributes back.",
+   "description": "Returns Browser MCP info and pre-filled URLs the user can click to submit feature wishes, share use-cases, or report bugs. Call this PROACTIVELY whenever the user (a) mentions a feature they wish existed (\"I wish browser-mcp could...\", \"it would be nice if...\"), (b) says something is missing, broken, or unexpected, (c) asks how Browser MCP works or who maintains it, or (d) describes something cool they built with browser-mcp. Pass intent=\"wish\" | \"use_case\" | \"bug\" | \"info\" plus an optional title and body, and offer the returned submit_url to the user. Browser MCP is community-shaped - this tool is how the user contributes back. It also lists the Browser MCP installations connected to this chat (installations: the profile name set in the extension popup, the installation id, the version), so you can see which names BROWSER_MCP_PROFILE can choose; it lists nothing before the browser has been used in the chat.",
    "inputSchema": {
      "properties": {
        "body": {
--- pinned/browser_execute_script
+++ observed/browser_execute_script
  {
-   "description": "Execute JavaScript in the current page. IMPORTANT: the parameter is `code` (NOT `script` - though that alias is accepted), and it must be an EXPRESSION, not statements: use an IIFE `(() => { ...; return x; })()`. Top-level `return` is a syntax error (the handler wraps code in parentheses).",
+   "description": "Execute JavaScript in the current page, through the Chrome debugger (Runtime.evaluate) in the page's own context: the page's globals are visible and its Content Security Policy does not block it. A returned promise is awaited. If the code throws, the answer is ok:false with error and name, and the code is not run again. The call answers within 29.5 s; if the time runs out before the code is sent, the answer is error call-timeout with sent:false (nothing ran). IMPORTANT: the parameter is `code` (NOT `script` - though that alias is accepted), and it must be an EXPRESSION, not statements: use an IIFE `(() => { ...; return x; })()`. Top-level `return` is a syntax error (the handler wraps code in parentheses).",
    "inputSchema": {
      "properties": {
        "code": {
--- pinned/browser_select_frame
+++ observed/browser_select_frame
  {
-   "description": "Execute JavaScript in a specific iframe by frame index. Use browser_list_frames first to find the right index.",
+   "description": "Execute JavaScript in a specific frame by frame index, through the Chrome debugger in that frame's own context - also a frame from another site. Use browser_list_frames first to find the right index. A returned promise is awaited; without code it returns the frame's text (first 5000 characters). The frame is identified by a one-time mark the extension puts in that frame's document, not by its address or position; if the mark is not found, nothing is run (error frame-not-reachable, and retry says whether calling again can help; a frame whose document is replaced while the call looks for it gives retry: true). The call answers within 29.5 s; if the time runs out before the code is sent, the answer is error call-timeout with sent:false (nothing ran).",
    "inputSchema": {
      "properties": {
        "code": {
-         "description": "JavaScript to execute in the frame (default: returns text content)",
+         "description": "JavaScript EXPRESSION to evaluate in the frame (default: returns its text content)",
          "type": "string"
        },
        "frame_index": {

Changed 2026-10-15 11:21 UTC

21 descriptions rewritten.

--- pinned/browser_ask_user
+++ observed/browser_ask_user
  {
-   "description": "Show an overlay dialog asking the user to perform an action or provide information (credentials, 2FA, CAPTCHA, OAuth consent). Can include input fields for the user to fill in. Returns user responses.",
+   "description": "Ask the user to do something or give information. Without fields it shows a small card in the bottom-left corner that the user can drag away while they act on the page (log in, solve a CAPTCHA, give consent); with fields it shows a dialog over the page. The prompt is drawn again after a navigation in the tab, but a prompt with fields ends with action navigated if the tab moves to another origin (another domain, subdomain or port) or, on a local file, to another file. Returns acknowledged, action (done, skip, timeout, tab_closed, replaced when a newer question in the same tab takes over, navigated, or removed_by_page when the page keeps removing the prompt) and, for done, values. The page cannot read the field values, but it can see the keystrokes, so ask only for secrets that belong to the page the tab is on. The tab must show a page that loaded (a web page or a local file): on Chrome's error page or about:blank the call fails at once and shows nothing. It makes the tab the active one in its window and restores a minimized window, but it does not bring Chrome in front of other apps.",
    "inputSchema": {
      "properties": {
        "fields": {
--- pinned/browser_click
+++ observed/browser_click
  {
-   "description": "Click an element on the page. Supports CSS selectors AND text-based selectors. Auto-scrolls element into view. Uses real mouse events (works on Angular/React SPAs and CSP-strict sites like Google, Stripe). Examples: \"button:text(Get started)\", \"text=Submit\", \"#my-button\", \"a.btn-primary\"",
+   "description": "Click an element on the page. Supports CSS selectors AND text-based selectors. Auto-scrolls element into view. Uses real mouse events (works on Angular/React SPAs and CSP-strict sites like Google, Stripe). A text selector looks inside an open modal dialog first (a <dialog> opened with showModal, or a visible element with aria-modal=\"true\"), exact text before partial. If the mouse would not reach the target at its center - another element lies in front of it (an overlay, a dialog's backdrop), the target does not take clicks there (pointer-events, visibility, clipping), or a different control inside it would get the click (a delete button on a card; for a container that is not clickable itself, its own plain link with a real address (a hash route with a path, such as #/orders, counts; a fragment with no target on the page does not) does not count, nor does a web component's own button that covers it, nor a box with a checkbox, switch or radio role right next to a hidden field and the only one in the nearest label around it, a label of that field (a box inside a closed shadow root cannot be seen and is not counted), which counts as that field) - the answer is ok:false with error \"covered\" and covered_by. Found before the mouse is sent, nothing is sent; if it comes in front afterwards, the rest of the presses and clicks are stopped (a release the page moves elsewhere is let through), and the answer also has maybe_landed: true, because the page may already have reacted. Examples: \"button:text(Get started)\", \"text=Submit\", \"#my-button\", \"a.btn-primary\"",
    "inputSchema": {
      "properties": {
        "selector": {
--- pinned/browser_click_xy
+++ observed/browser_click_xy
  {
-   "description": "ESCAPE HATCH: Click at raw viewport coordinates (CSS pixels) with fully trusted mouse events. Use when a visible button resists every selector strategy (Azure portal dialogs, Knockout-bound divs, canvas UIs): take a screenshot, read the button's position, click its center. Combine with browser_screenshot for coordinates.",
+   "description": "ESCAPE HATCH: Click at raw viewport coordinates (CSS pixels) with fully trusted mouse events. Use when a visible button resists every selector strategy (Azure portal dialogs, Knockout-bound divs, canvas UIs): take a screenshot, read the button's position, click its center. Coordinates are CSS pixels, NOT image pixels: convert with the viewport line browser_screenshot returns: x_css = x / W * css_width and y_css = y / W * css_width, where (x, y) is the point in the image and W is the width the image is shown to you at.",
    "inputSchema": {
      "properties": {
        "x": {
--- pinned/browser_double_click
+++ observed/browser_double_click
  {
-   "description": "True double-click on an element (two trusted press/release pairs with escalating clickCount). Use for open-item actions (calendar events, file lists) where two single clicks would trigger inline-rename instead (e.g. OWA month view).",
+   "description": "True double-click on an element (two trusted press/release pairs with escalating clickCount). Use for open-item actions (calendar events, file lists) where two single clicks would trigger inline-rename instead (e.g. OWA month view). If the mouse would not reach the target at its center - another element lies in front of it (an overlay, a dialog's backdrop), the target does not take clicks there (pointer-events, visibility, clipping), or a different control inside it would get the click (a delete button on a card; for a container that is not clickable itself, its own plain link with a real address (a hash route with a path, such as #/orders, counts; a fragment with no target on the page does not) does not count, nor does a web component's own button that covers it, nor a box with a checkbox, switch or radio role right next to a hidden field and the only one in the nearest label around it, a label of that field (a box inside a closed shadow root cannot be seen and is not counted), which counts as that field) - the answer is ok:false with error \"covered\" and covered_by. Found before the mouse is sent, nothing is sent; if it comes in front afterwards, the rest of the presses and clicks are stopped (a release the page moves elsewhere is let through), and the answer also has maybe_landed: true, because the page may already have reacted.",
    "inputSchema": {
      "properties": {
        "selector": {
--- pinned/browser_drop_file
+++ observed/browser_drop_file
  {
-   "description": "Upload a file when browser_upload_file fails. Two strategies: (1) finds a hidden <input type=\"file\"> in the target's subtree or up to 2 ancestor levels; (2) if there is no input at all, intercepts the NATIVE OS file-chooser - pass the selector of the button that opens the dialog, and the file is supplied programmatically without the dialog ever appearing. Strategy 2 handles sites like Google Ads that never put a file input in the DOM. Files must be regular files inside the working directory the MCP server runs in - folders, symlinks and hardlinks that point outside it are refused.",
+   "description": "Handing a file to the page needs \"Allow access to file URLs\" for the Agent360 Browser MCP extension (off by default for Chrome Web Store installs); that switch also lets the extension open any local file as a page, so whether to turn it on is the user's call. A \"Not allowed\" error from the handoff is reported as file-access-off unless Chrome confirms that file access is on. Upload a file when browser_upload_file fails. Two strategies: (1) uses the target itself when it is a file input, or a hidden <input type=\"file\"> inside it; (2) if there is none, clicks the target and intercepts the NATIVE OS file-chooser it opens - pass the selector of the button that opens the dialog, and the file is supplied programmatically without the dialog ever appearing. Other file inputs on the page do not get the file: when neither strategy works, the error lists them in other_file_inputs, each with a `selector` to pass if it is the right field - a CSS selector that matched that input alone when the tool looked (built from its id, its name or its position on the page), or null when none did, for instance for an input inside a shadow root. The answer describes the input that got the file in `input`. If the field is empty afterwards although the page's change handler saw the file in it, the answer is ok with cleared_by_page: true and seen_in_field - X and Facebook empty the field after reading the file, and a page that rejects the file can do the same, so check the page before attaching again. Files must be regular files inside the working directory the MCP server runs in - folders, symlinks and hardlinks that point outside it are refused.",
    "inputSchema": {
      "properties": {
        "file": {
--- pinned/browser_extract_list
+++ observed/browser_extract_list
  {
-   "description": "Read EVERY row of a long or virtualised list by scrolling its container until no new rows appear. Use this instead of browser_get_page_content whenever a page shows a repeating list longer than the viewport - mail lists (Outlook, Gmail), invoice/billing tables, search results, transaction histories. Those UIs keep only ~7 rows in the DOM at a time, so a single page read returns a sliver and looks complete. Pass the CSS selector of one repeating row (e.g. '[role=\"option\"]', 'tr', '[role=\"listitem\"]'); the scrollable ancestor is found automatically. Returns deduplicated row text plus reached_end so you know whether you saw the whole list.",
+   "description": "Read the rows of a long or virtualised list by scrolling its container until no new rows appear. Use this instead of browser_get_page_content whenever a page shows a repeating list longer than the viewport - mail lists (Outlook, Gmail), invoice/billing tables, search results, transaction histories. Those UIs keep only ~7 rows in the DOM at a time, so a single page read returns a sliver and looks complete. Pass the CSS selector of one repeating row (e.g. '[role=\"option\"]', 'tr', '[role=\"listitem\"]'); the scrollable ancestor is found automatically. When the tab is the active one in its window, it scrolls with real wheel events, also at the bottom, so a feed that loads more rows on wheel (Threads, X) loads them. A hidden page (a background tab) gets no wheel or scroll events, IntersectionObserver callbacks or animation frames, so a list that loads or renders rows while you scroll stops at what is already there; then reached_end is false and `note` says why. Returns deduplicated row text, scroll_method (wheel or script) and reached_end, which is true only when the list stood at its bottom in a visible page with no new rows, growth or movement for stable_rounds rounds in a row. If the selector matches no rows with text, the answer is error \"no-rows\".",
    "inputSchema": {
      "properties": {
        "container": {
-         "description": "Consecutive scrolls with no new rows before stopping (default 3)",
+         "description": "Rounds in a row with no new rows, growth or movement before stopping, and at the bottom before reached_end (default 3)",
          "type": "number"
        },
        "wait_ms": {
--- pinned/browser_extract_token
+++ observed/browser_extract_token
  {
-   "description": "Navigate to a provider's API settings page so you can read its API token from the page.",
+   "description": "Open a known provider's API page (stripe, hubspot, slack, shopify, mailchimp, pipedrive, calendly, google, linkedin) and get instructions for finding or creating its API credentials there. It reads nothing itself: read the page with browser_get_page_content or browser_screenshot afterwards. For HubSpot, Slack, Shopify and LinkedIn the page is a settings or app list on the way to the token, not the token page itself. Any other provider answers Unknown provider; use browser_navigate and read the page yourself.",
    "inputSchema": {
      "properties": {
        "provider": {
-         "description": "Provider slug (stripe, hubspot, slack, etc.)",
+         "description": "One of: stripe, hubspot, slack, shopify, mailchimp, pipedrive, calendly, google, linkedin",
          "type": "string"
        }
      },
--- pinned/browser_fetch
+++ observed/browser_fetch
  {
-   "description": "Make an HTTP request from the extension background (NOT subject to CORS). Use this when page-context fetch would be blocked by CORS or CSP. Useful for API calls to Google, Stripe, Slack APIs while on their pages.",
+   "description": "Make an HTTPS request from the extension background. It is not subject to the page's CORS or CSP, so it works for API calls (Google, Stripe, Slack) where a fetch from the page would be blocked. Your browser cookies are not sent and the answer's cookies are not stored: pass a token in headers. Plain http works only to 127.0.0.1.",
    "inputSchema": {
      "properties": {
        "body": {
--- pinned/browser_fill
+++ observed/browser_fill
  {
-   "description": "Fill a form input field with a value. Supports CSS selectors AND text-based selectors. Auto-scrolls and focuses the element. Works on CSP-strict sites via Chrome Debugger API. For date inputs use browser_set_date, for autocomplete/combobox use browser_set_combobox.",
+   "description": "Fill a form input field with a value. Supports CSS selectors AND text-based selectors. Auto-scrolls and focuses the element. Works on CSP-strict sites via Chrome Debugger API. For date inputs use browser_set_date, for autocomplete/combobox use browser_set_combobox. With a text selector the field is clicked first, so the same check as browser_click applies: if the mouse would not reach it at its center, nothing is typed and the answer is ok:false with error \"covered\".",
    "inputSchema": {
      "properties": {
        "selector": {
--- pinned/browser_handle_dialog
+++ observed/browser_handle_dialog
  {
-   "description": "Arm automatic handling of the next JavaScript alert/confirm/prompt on the active tab, then return immediately. Call this BEFORE the click that opens the dialog - a dialog freezes the tab, so nothing can be clicked once it is on screen. Pass wait=true only when the dialog is already opening.",
+   "description": "Answer a JavaScript alert/confirm/prompt or \"Leave site?\" on the active tab. If a dialog this extension saw open is on screen and nobody is answering it, it is answered now, and the reply says what Chrome reports happened (action accept or dismiss). Otherwise this arms automatic handling of the next dialog on the tab and returns immediately - call it BEFORE the click that opens the dialog, because a dialog freezes the tab. Pass wait=true only when the dialog is already opening. Errors: dialog-being-answered (browser_navigate or an earlier call is answering it - wait), dialog-unconfirmed (answered, but Chrome did not confirm it closed), tab-not-responding (a dialog that opened before anything listened, or a busy page; the note says what to do).",
    "inputSchema": {
      "properties": {
        "action": {
--- pinned/browser_hover
+++ observed/browser_hover
  {
-   "description": "Hover over an element to trigger tooltips, dropdown menus, or hover states. Supports CSS and text selectors.",
+   "description": "Hover over an element to trigger tooltips, dropdown menus, or hover states. Supports CSS and text selectors. If the mouse would not reach the target at its center - another element lies in front of it (an overlay, a dialog's backdrop), or the target does not take clicks there (pointer-events, visibility, clipping) - the answer is ok:false with error \"covered\" and covered_by. Found before the mouse is sent, nothing is sent; if it comes in front afterwards, the rest of the mouse movement is stopped and the answer also has maybe_landed: true, because the page may already have reacted.",
    "inputSchema": {
      "properties": {
        "duration": {
--- pinned/browser_list_tabs
+++ observed/browser_list_tabs
  {
-   "description": "List all open browser tabs with their URLs and titles.",
+   "description": "List the tabs this session owns (tabs it opened or adopted): id, URL, title, and each tab's active flag (whether Chrome shows it in front of its window, not which tab this session is working in). Tabs of other sessions are not included.",
    "inputSchema": {
      "properties": {},
      "type": "object"
--- pinned/browser_navigate
+++ observed/browser_navigate
  {
-   "description": "Navigate the active browser tab to a URL. Reuses the current tab by default (no tab spam). Pass new_tab=true only when you need to keep the current page open.",
+   "description": "Navigate the active browser tab to a URL. Reuses the current tab by default (no tab spam). Pass new_tab=true only when you need to keep the current page open. In the current tab the answer is ok:true only when Chrome took the address and the new page is proven loaded within about 25 s (the wait ends half a second before that limit). If the page asks \"Leave site?\", the default answer is Stay (an armed browser_handle_dialog answers instead) and the reply is error beforeunload-blocked with the tab's actual url: the page may hold unsaved work, so ask the user (browser_ask_user) before you call again with leave_page:true. Other errors, each with sent (was the address sent to Chrome?) and a note on what to do: navigation-in-progress (another navigate is working in this tab, or - with op_id null - an earlier answer to a dialog on the tab is not confirmed yet; wait a moment), dialog-open (a dialog is on screen - answer it with browser_handle_dialog), tab-not-responding (busy page, or a dialog Chrome does not show us), navigation-failed, dialog-unconfirmed, navigation-unconfirmed (no load proven in time; may_complete_later and pending_url say it can still happen - read the tab before sending it again; a download or a 204 answer never loads a page and ends here too), tab-closed (Chrome no longer has the tab), session-changed (another call changed the session's active tab while this one waited; nothing was sent).",
    "inputSchema": {
      "properties": {
        "eget_vindue": {
-         "description": "Requires new_tab. Opens the page in its own Chrome window. On its own this does NOT make input work: measured 19 Sept, an unfocused window delivers no keystrokes at all, exactly like a background tab - what decides it is whether the WINDOW has the operating system's focus, not whether the tab is the visible one in it. What this is genuinely for: on a machine with more than one display, put the window on a screen nobody is looking at and pass fokuser:true. Then Chrome delivers input - but focus is exclusive: while this window has it, whatever the person types lands in it. Use it only on a machine nobody is typing on.",
+         "description": "Requires new_tab. Opens the page in its own Chrome window, without focus unless you pass fokuser:true. Whether Chrome delivers mouse and keyboard input to a window without focus is unmeasured in desktop Chrome (a measurement on 19 Sept probably tested a background tab, because the tab was moved back into the session's first window; fixed in 1.30.2), so read landed in each input tool's answer. On a machine with more than one display, you can put the window on a screen nobody is looking at and pass fokuser:true - but focus is exclusive: while this window has it, whatever the person types lands in it. Use that only on a machine nobody is typing on.",
          "type": "boolean"
        },
        "fokuser": {
-         "description": "With eget_vindue: ASK for the new window to get the operating system's focus, which keyboard and mouse input needs. ⛔ Measured 21 Sept: Chrome often refuses, and the window was on none of the machine's three screens. The reply now carries what actually happened - read `fokuseret` and `placeret_som_bedt` rather than assuming this worked. Do not rely on vindue_x to keep a run off the user's screen.",
+         "description": "With eget_vindue: ASK for the new window to get the operating system's focus. Chrome can refuse it. The reply carries what actually happened - read `fokuseret` and `placeret_som_bedt` rather than assuming this worked. Do not rely on vindue_x to keep a run off the user's screen.",
          "type": "boolean"
        },
+       "leave_page": {
+         "description": "Answer \"Leave site?\" with Leave instead of Stay (default: false), so unsaved changes on the current page are lost. Only after the user has agreed (browser_ask_user). An armed browser_handle_dialog on the tab takes precedence over this.",
+         "type": "boolean"
+       },
        "new_tab": {
-         "description": "Open in new tab instead of reusing current (default: false)",
+         "description": "Open in new tab instead of reusing current (default: false). If the session's only tab is still an empty about:blank placeholder, that tab is used instead of opening another.",
          "type": "boolean"
        },
        "url": {
--- pinned/browser_press_key
+++ observed/browser_press_key
  {
-   "description": "Press a keyboard key (Enter, Tab, Escape, ArrowDown, etc.). Useful for submitting forms, navigating dropdowns, closing dialogs. Supports modifier keys (ctrl, alt, shift, meta).",
+   "description": "Press a keyboard key (Enter, Tab, Escape, ArrowDown, etc.). Useful for submitting forms, navigating dropdowns, closing dialogs. Supports modifier keys (ctrl, alt, shift, meta). While ctrl, meta or alt is held, no character is typed. With ctrl or meta (and no shift), the letters a, c, x, v, z and y are sent with Chrome's own editing command - select all, copy, cut, paste, undo, redo; z with shift is redo - including on macOS, where Chrome runs no shortcut for a synthetic key without it. The answer names the command that was sent in `shortcut`; `landed` says the page received the key, not what the command did. Paste inserts the user's real clipboard into the page, and copy and cut replace what is on it.",
    "inputSchema": {
      "properties": {
        "alt": {
--- pinned/browser_provide_feedback
+++ observed/browser_provide_feedback
  {
-   "description": "Self-diagnosis + feedback in one call. Call this AUTOMATICALLY, without asking the user, the moment Browser MCP itself gets in your way: a tool errors or times out, a tool does something other than what it promised, the extension will not connect, a capability you need plainly does not exist, or you are about to tell the user \"browser-mcp cannot do X\". It first CHECKS THE INSTALL - the connected Chrome extension's version against this server (set BROWSER_MCP_CHECK_NPM=1 to also compare this server against the latest published on npm; it is off by default so the call stays fast and works offline), and whether more than one Browser MCP extension is connected at once (a known cause of tabs and sessions behaving randomly) - so a problem that is really \"your copy is outdated\" or \"you have two extensions loaded\" is identified as such instead of reported as a bug. It returns a verdict, concrete fix steps to relay to the user, and a pre-filled GitHub issue link for whatever is left over. Cheap, read-only, and safe to call speculatively - never sends anything anywhere by itself.",
+   "description": "Self-diagnosis + feedback in one call. Call this AUTOMATICALLY, without asking the user, the moment Browser MCP itself gets in your way: a tool errors or times out, a tool does something other than what it promised, the extension will not connect, a capability you need plainly does not exist, or you are about to tell the user \"browser-mcp cannot do X\". It first CHECKS THE INSTALL - the connected Chrome extension's version against this server (set BROWSER_MCP_CHECK_NPM=1 to also compare this server against the latest published on npm; it is off by default so the call stays fast and works offline), and whether more than one Browser MCP extension is connected at once, also the same extension connected more than once, for instance from several Chrome profiles or browsers (a known cause of tabs and sessions behaving randomly) - so a problem that is really \"your copy is outdated\" or \"you have two extensions loaded\" is identified as such instead of reported as a bug. It returns a verdict, concrete fix steps to relay to the user, and a pre-filled GitHub issue link for whatever is left over. Cheap to call speculatively. It uploads nothing: it returns the report to you and tries to add one line to ~/.browser-mcp/feedback.jsonl on this machine (a report with the same kind, tool and first 160 characters of what_happened, with numbers and long hex strings ignored, is written once per server run, and again only if the earlier write failed; logged_locally says whether a line was written). Email addresses and numbers of six or more characters are replaced with [email] and [number] in what_happened, attempted and worked before they are written or put in the issue link. With BROWSER_MCP_CHECK_NPM=1 it also runs `npm view` against your configured npm registry.",
    "inputSchema": {
      "properties": {
        "attempted": {
+       },
+       "worked": {
+         "description": "The route that did work in the end, if any. This is the most useful part.",
+         "type": "string"
        }
      },
      "required": [
--- pinned/browser_right_click
+++ observed/browser_right_click
  {
-   "description": "Right-click an element (trusted CDP mouse events) to open page-level context menus (web apps like OWA/Google Docs render their own). Note: Chrome's NATIVE context menu does not open via CDP - only in-page menus.",
+   "description": "Right-click an element (trusted CDP mouse events) to open page-level context menus (web apps like OWA/Google Docs render their own). Note: Chrome's NATIVE context menu does not open via CDP - only in-page menus. If the mouse would not reach the target at its center - another element lies in front of it (an overlay, a dialog's backdrop), or the target does not take clicks there (pointer-events, visibility, clipping) - the answer is ok:false with error \"covered\" and covered_by. Found before the mouse is sent, nothing is sent; if it comes in front afterwards, the rest of the presses and clicks are stopped (a release the page moves elsewhere is let through), and the answer also has maybe_landed: true, because the page may already have reacted.",
    "inputSchema": {
      "properties": {
        "selector": {
--- pinned/browser_screenshot
+++ observed/browser_screenshot
  {
-   "description": "Take a screenshot of the visible area of the current tab. Returns base64 PNG, or saves to disk if path is provided.",
+   "description": "Take a screenshot of the visible area of the current tab. Returns base64 PNG, or saves to disk if path is provided. The image is in device pixels; when Chrome reports them in time, the answer also gives the CSS viewport size and devicePixelRatio, which browser_click_xy coordinates need. Chrome stays where it is: if its window is covered by other windows, there is no picture and the answer is error \"window-covered\".",
    "inputSchema": {
      "properties": {
+       "bring_to_front": {
+         "description": "Raise Chrome in front of the user if its window is covered, and leave it there. Takes the user's screen: only after the user agreed (browser_ask_user). Default false.",
+         "type": "boolean"
+       },
        "path": {
          "description": "File path to save the screenshot to (e.g. /path/to/screenshot.png)",
          "type": "string"
--- pinned/browser_select_option
+++ observed/browser_select_option
  {
-   "description": "Select an option from a dropdown menu. Works with native <select> elements AND custom dropdowns (Angular Material, React Select, etc.). For custom dropdowns: clicks the trigger, waits for options, then clicks the matching option by text. For autocomplete (typing filters options) use browser_set_combobox instead.",
+   "description": "Select an option from a dropdown menu. Works with native <select> elements AND custom dropdowns (Angular Material, React Select, etc.). For custom dropdowns: clicks the trigger, waits for options, then clicks the matching option by text. For autocomplete (typing filters options) use browser_set_combobox instead. A custom dropdown's trigger gets the same check as browser_click: if the mouse would not reach it at its center, nothing is opened and the answer is ok:false with error \"covered\". So does the option once the list is open: if it is covered, it is not clicked, and the answer is ok:false with error \"covered\" and trigger_clicked: true - the list may still be open. If the click on the trigger was sent, but neither its press is shown to have reached it nor its click landed, no option is clicked and the answer is ok:false with maybe_landed: true; a click that cannot be checked in the page is not sent, and the answer says so; if it opens a dialog, or the option is not found, the answer has trigger_clicked: true.",
    "inputSchema": {
      "properties": {
        "option": {
--- pinned/browser_solve_captcha
+++ observed/browser_solve_captcha
  {
-   "description": "Detect and solve CAPTCHAs on the current page. Auto-detects reCAPTCHA v2/v3, hCaptcha, Cloudflare Turnstile, and FunCaptcha. Tries auto-click first (often clears reCAPTCHA v2 when signed into Google), then returns a screenshot for AI vision analysis, then falls back to asking the user. Returns detection info and solving status.",
+   "description": "Detect CAPTCHAs on the current page and work through them, one action per call. \"detect\" reports reCAPTCHA v2/v3, hCaptcha, Cloudflare Turnstile and FunCaptcha. \"click_checkbox\" tries the reCAPTCHA checkbox and detects again. \"click_grid\" clicks the reCAPTCHA image-challenge cells you choose; this tool returns no image, so take one with browser_screenshot first. \"ask_human\" returns the message to show the user with browser_ask_user.",
    "inputSchema": {
      "properties": {
        "action": {
-         "description": "Action to take. \"detect\" scans for CAPTCHAs. \"click_checkbox\" clicks the reCAPTCHA checkbox. \"click_grid\" clicks specific grid cells (pass cells param). \"ask_human\" shows overlay to user. Default: \"detect\"",
+         "description": "Action to take. \"detect\" scans for CAPTCHAs. \"click_checkbox\" clicks the reCAPTCHA checkbox. \"click_grid\" clicks specific reCAPTCHA grid cells (pass cells, and grid 3 or 4). \"ask_human\" returns the message to show the user with browser_ask_user; it shows nothing itself. Default: \"detect\"",
          "enum": [
            "detect",
            "click_checkbox",
-         "description": "Grid cell indices to click (0-indexed, left-to-right, top-to-bottom) for image challenges. E.g. [2, 5, 7] to click cells 3, 6, 8.",
+         "description": "Grid cell indices to click (0-indexed, left-to-right, top-to-bottom) for image challenges. E.g. [2, 5, 7] to click cells 3, 6, 8. Pass grid to say which grid you see; without it, the grid is read as 4x4 only when an index is 9 or higher.",
          "items": {
-           "type": "number"
+           "type": "integer"
          },
          "type": "array"
+       },
+       "grid": {
+         "description": "For click_grid: the size of the reCAPTCHA image grid in your screenshot, 3 (3x3) or 4 (4x4).",
+         "enum": [
+           3,
+           4
+         ],
+         "type": "number"
        }
      },
      "type": "object"
--- pinned/browser_switch_tab
+++ observed/browser_switch_tab
  {
-   "description": "Switch to a specific browser tab by ID. Get tab IDs from browser_list_tabs or browser_get_new_tab.",
+   "description": "Switch to a specific browser tab by ID. Get tab IDs from browser_list_tabs or browser_get_new_tab. The other tools then act on that tab. Chrome is not brought in front of the user: the tab is made active in its window, and visibility says whether the page is hidden behind other windows.",
    "inputSchema": {
      "properties": {
+       "bring_to_front": {
+         "description": "Also bring the Chrome window in front of the user. Takes the user's screen: only after the user agreed (browser_ask_user). Default false.",
+         "type": "boolean"
+       },
        "tab_id": {
-         "description": "Tab ID to activate",
+         "description": "Tab ID to switch to",
          "type": "number"
        }
      },
--- pinned/browser_upload_file
+++ observed/browser_upload_file
  {
-   "description": "Upload a file to a <input type=\"file\"> element on the page. Uses Chrome Debugger API to set files programmatically - no dialog needed. For drag-drop zones without visible file input use browser_drop_file. Files must be regular files inside the working directory the MCP server runs in - folders, symlinks and hardlinks that point outside it are refused.",
+   "description": "Handing a file to the page needs \"Allow access to file URLs\" for the Agent360 Browser MCP extension (off by default for Chrome Web Store installs); that switch also lets the extension open any local file as a page, so whether to turn it on is the user's call. A \"Not allowed\" error from the handoff is reported as file-access-off unless Chrome confirms that file access is on. Upload a file to a <input type=\"file\"> element on the page. Uses Chrome Debugger API to set files programmatically - no dialog needed. For drag-drop zones without visible file input use browser_drop_file. If the field is empty afterwards although the page's change handler saw the file in it, the answer is ok with cleared_by_page: true and seen_in_field - X and Facebook empty the field after reading the file, and a page that rejects the file can do the same, so check the page before attaching again. Files must be regular files inside the working directory the MCP server runs in - folders, symlinks and hardlinks that point outside it are refused.",
    "inputSchema": {
      "properties": {
        "file": {

Changed 2026-10-08 13:23 UTC

1 schema or annotation change with the description left byte-identical.

--- pinned/browser_navigate
+++ observed/browser_navigate
  {
    "description": "Navigate the active browser tab to a URL. Reuses the current tab by default (no tab spam). Pass new_tab=true only when you need to keep the current page open.",
    "inputSchema": {
-         "description": "EXPERIMENTAL, requires new_tab. Opens the page in its own unfocused Chrome window instead of a background tab. Whether Chrome delivers mouse and keyboard input in that state is exactly the open question - measure it, do not rely on it.",
+         "description": "Requires new_tab. Opens the page in its own Chrome window. On its own this does NOT make input work: measured 19 Sept, an unfocused window delivers no keystrokes at all, exactly like a background tab - what decides it is whether the WINDOW has the operating system's focus, not whether the tab is the visible one in it. What this is genuinely for: on a machine with more than one display, put the window on a screen nobody is looking at and pass fokuser:true. Then Chrome delivers input - but focus is exclusive: while this window has it, whatever the person types lands in it. Use it only on a machine nobody is typing on.",
          "type": "boolean"
        },
+       "fokuser": {
+         "description": "With eget_vindue: ASK for the new window to get the operating system's focus, which keyboard and mouse input needs. ⛔ Measured 21 Sept: Chrome often refuses, and the window was on none of the machine's three screens. The reply now carries what actually happened - read `fokuseret` and `placeret_som_bedt` rather than assuming this worked. Do not rely on vindue_x to keep a run off the user's screen.",
+         "type": "boolean"
+       },
        "new_tab": {
          "description": "Open in new tab instead of reusing current (default: false)",
          "type": "boolean"
+       },
+       "vindue_bredde": {
+         "description": "With eget_vindue: window width in points.",
+         "type": "number"
+       },
+       "vindue_hoejde": {
+         "description": "With eget_vindue: window height in points.",
+         "type": "number"
+       },
+       "vindue_x": {
+         "description": "With eget_vindue: the window's left edge in global screen points. A NEGATIVE value is a display to the left of the main one - that is how you put it on another screen. No tool lists the displays: use a coordinate you know is on an unused screen, and read `placeret_som_bedt` in the reply.",
+         "type": "number"
+       },
+       "vindue_y": {
+         "description": "With eget_vindue: the window's top edge in global screen points.",
+         "type": "number"
        }
      },
      "required": [

Current tools

Show all 40 tool fingerprints
browser_about
3b66a4b375dad5fe
browser_ask_user
57dd17453f872415
browser_click
5225b39c112def5e
browser_click_xy
44909a8244c7d42e
browser_close_tab
e40cbbc174723eb7
browser_console_logs
388944aba83b1db6
browser_dismiss_overlays
1710ab09819240b5
browser_double_click
2ed8fb8a5dc7c731
browser_drop_file
afc7aeb9dfee01d4
browser_execute_script
00e4eb4bc555facf
browser_extract_list
0ffb6a77232ec867
browser_extract_token
485289cac7c973d9
browser_fetch
cf5a8d91b4403c0d
browser_fill
1eef0a77af8262fa
browser_get_cookies
182b772c91735090
browser_get_local_storage
69295cc094040fa7
browser_get_new_tab
cc6a4bf742b2c239
browser_get_page_content
51fc82446ab93f43
browser_handle_dialog
9883b534bc201c46
browser_hover
da2cfab831c5b631
browser_list_frames
833223e83794df58
browser_list_tabs
63520e00e6932031
browser_navigate
7e2aa06c72468839
browser_press_key
24d0c472a19cdb4b
browser_provide_feedback
ff5459d7018486c7
browser_reattach_debugger
a54a9a1744a09102
browser_right_click
65ace4dfb5a0a9a7
browser_screenshot
6e970c6c0308e122
browser_scroll
d42f3383d19c00c2
browser_select_frame
248149f275676fe9
browser_select_option
281ad70e2029c91d
browser_set_combobox
754bcad37bcc5e24
browser_set_cookies
683cb9c370f2f4ec
browser_set_date
4d4688f08cd23ffa
browser_set_local_storage
daac9c9b35929272
browser_solve_captcha
4548312610456f73
browser_switch_tab
bd8d7357a5eaa42f
browser_upload_file
182de00feb26faa6
browser_wait
39b264da5ff053b5
browser_wait_for_network
57834c95c174c78e

Watch this server yourself

If you run this server, put the proxy in front of it. It pins these exact fingerprints on first connect and stops the session if they move.

npx -y mcp-pin@0.2.3 -- <your @agent360/browser-mcp command>

Or subscribe to this page's RSS feed to be told when it changes.

Badge

mcp-pin status badge for @agent360/browser-mcp

The badge states one fact about time and nothing else. It never claims a server is safe.

[![mcp-pin](https://mcp-pin.gautamkhosla.com/badge/f94c1416681ed1a1.svg)](https://mcp-pin.gautamkhosla.com/servers/f94c1416681ed1a1.html)