firefox-bidi-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| DSH_BROWSER_BIDI_PORT | No | The WebDriver BiDi port to connect to (first available of: DSH_BROWSER_BIDI_PORT → ~/.firefox-devtools-mcp/instances/<pid>.port → 9222) | 9222 |
| DSH_BROWSER_OUTPUT_DIR | No | Directory used to store oversized results (default system temp directory) | |
| DSH_BROWSER_MAX_OUTPUT_CHARS | No | Maximum number of output characters (default 1000000) | 1000000 |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| browser_infoA | Report the connected Firefox: version, window count and whether the privileged (chrome) context is reachable. |
| browser_page_infoA | Read the active tab title and URL. |
| browser_list_tabsA | List open tabs with their context id, URL and title. |
| browser_evaluateA | Run an expression in the page and return its value (JSON). Promises are awaited. It can change the page, so treat it as a write. |
| browser_clickA | Click the first element matching a CSS selector with a real mouse action (pointer move, down, up) at its centre. |
| browser_upload_fileA | Attach one or more local files to the file input matching a CSS selector (privileged not required). IMPORTANT (measured on Windows): paths must be NATIVE (D:dirile.txt); a file:///D:/... URL is rejected with "File error: Unrecognized path". The file is read by Firefox itself, so it must exist and be readable. |
| browser_typeB | Focus the element matching a CSS selector and type text with real per-character key events. |
| browser_navigateA | Navigate the active tab to a URL and wait for the page to load. |
| browser_new_tabB | Open a new tab, optionally navigating it to a URL. Returns the new context id. |
| browser_screenshotA | Capture a base64 PNG. By default only the visible viewport is captured (physical pixels, i.e. viewport size x devicePixelRatio); Firefox ignores the BiDi clip argument and this call does not scroll. Set fullPage to capture a whole scrollable area by scrolling and stitching segments inside the page (the original scroll position is restored). Pass selector to capture the full content of one scrollable element (an overflow:scroll container) — document scrolling alone would miss it. |
| browser_pdfA | Render the page to PDF and return it base64-encoded (verified: browsingContext.print returns %PDF-1.4). Unlike browser_screenshot this covers the WHOLE document, including parts below the viewport, so it is the way to capture a long page. Note the output is binary and can be large; write it to a file to view it. |
| browser_pick_color_atA | Capture the viewport and return the colour at a CSS-pixel coordinate (x from the left, y from the top). Fully deterministic: it decodes the screenshot in-process, so no eyedropper and no human click is needed. Coordinates are CSS pixels; the screenshot is physical pixels, so devicePixelRatio is applied internally. |
| browser_set_download_dirA | Point Firefox at a download directory (privileged context) by writing browser.download.folderList=2, browser.download.dir and browser.download.useDownloadDir=true. BiDi cannot configure downloads itself (browsingContext.setDownloadBehavior is not implemented), so the preferences are the supported route. Send the path with FORWARD slashes: single backslashes inside a JSON string are invalid escapes (D:DSH... fails on D), the request is then dropped before reaching the server, and it looks like the tool hung. Use browser_list_downloads to see what landed there. |
| browser_console_logsA | Report console messages the page produced since this server connected (BiDi log.entryAdded). Each entry keeps the level, text and source. Buffered in memory (most recent 500) and only covers this connection, so open the page and act first, then read. |
| browser_network_requestsA | Report network activity seen since this server connected: network.beforeRequestSent (request URL/method), network.responseCompleted (status) and network.fetchError. This observes rather than intercepts — use it to find out what a page actually loaded. Buffered in memory (most recent 500) for this connection only. |
| browser_network_interceptA | Turn BiDi request interception on or off (network.addIntercept / removeIntercept, phase beforeRequestSent). While interception is ON, matching requests are HELD until something continues them — a page can appear to hang if nothing does. ⚠️ So always pair this with browser_network_continue_all (release everything held) or browser_network_provide_response (answer one request), and turn interception OFF when finished. Intercepted requests are listed by browser_network_requests (network.beforeRequestSent). |
| browser_network_continue_allA | Continue every request currently held by interception (network.continueRequest for each). This is the safety valve: call it whenever a page seems stuck while interception is on. Returns how many were released. |
| browser_network_provide_responseA | Fulfil one intercepted request with your own response body (network.provideResponse), instead of letting it reach the network. Pass the request id reported by browser_network_requests. Use this to stub an API, then release the rest with browser_network_continue_all. |
| browser_list_download_eventsA | Report the download events the browser pushed since this server connected (browsingContext.downloadWillBegin / downloadEnd). Complements browser_list_downloads: the directory shows the RESULT, these events show WHEN a download started, finished, or failed. Events are buffered in memory (most recent 500) and only cover the lifetime of this connection. |
| browser_list_downloadsA | List the files sitting in the configured download directory of Firefox (read via the browser.download.dir preference; no path guessing). Newest first, with sizes. Returns an empty list when nothing has been downloaded yet or the directory is unset. |
| browser_set_viewportA | Set (or clear) the BiDi viewport override and read back the result. The override PERSISTS across sessions and changes screenshot resolution (screenshots are physical pixels: viewport x devicePixelRatio), so clearing it matters. Measured: clearing works by sending viewport=null and OMITTING devicePixelRatio; sending null for both times out, so this tool never does that. |
| browser_profile_infoA | Report the profile the connected Firefox is running with: profile directory (ProfD), its name, the local cache directory, whether it is the default profile, and whether this Firefox was started with MOZ_REMOTE_ALLOW_SYSTEM_ACCESS. Use it to confirm you are driving the browser you think you are. NOTE: the profile is chosen when Firefox STARTS; this server attaches to an already-running browser and cannot switch it. To work against another profile, start a second Firefox with "-profile " plus its own --remote-debugging-port, then register a second MCP entry pointing at that port. |
| browser_close_tabA | Close one tab (browsingContext.close). MEASURED: this closes only that tab; the browser keeps running. Pick the tab by context id (from browser_list_tabs), by 0-based index among the tabs of the active window, or default to the active tab. The reply states how many tabs remain, and warns when none are left — at that point Firefox itself may exit. |
| browser_quitA | Ask the connected Firefox to exit (browser.close). This tears down the browser and this server loses its connection until Firefox is started again, so use it deliberately. LIMIT (measured): it CANNOT rescue an already-leaked session. Firefox allows ONE active BiDi session; when a client is killed with SIGKILL it never sends session.end, every call — including this one, since it must open a session too — fails with "Maximum number of active sessions". In that state only restarting Firefox externally helps. What this tool does give you is a clean, orderly shutdown while a session IS available, instead of killing the process. |
| browser_set_addon_stateA | Turn an installed extension on or off by id (AddonManager, privileged context). MEASURED: BiDi itself has no webExtension.* commands, so the privileged route is the only one available. Disabling is reversible; get ids from browser_list_addons. |
| browser_uninstall_addonA | Uninstall an installed extension by id (AddonManager, privileged context). This is destructive and, unlike browser_set_addon_state, not simply reversible — the add-on would have to be installed again. Some add-ons cannot be uninstalled (permissions are reported as PERM_CAN_UNINSTALL false). |
| browser_get_toolbar_layoutA | List which widgets sit in each toolbar area (nav-bar, TabsToolbar, PersonalToolbar, ...), read from browser.uiCustomization.state. Widget ids are exactly what browser_set_toolbar_widget expects. Read this before changing the layout, and use it to put things back afterwards. |
| browser_set_toolbar_widgetA | Move a widget into a toolbar area, or remove it from wherever it is (CustomizableUI.addWidgetToArea / removeWidgetFromArea). MEASURED: the CustomizableUI route applies immediately to the running browser, unlike editing browser.uiCustomization.state directly, which needs a restart. Get ids from browser_get_toolbar_layout or browser_toolbar_widgets, and note the layout so you can undo it. |
| browser_list_themesA | List installed themes and which one is active (AddonManager, type "theme"). Switch with browser_set_addon_state on a theme id — enabling a theme disables the previously active one, so the change is reversible. |
| browser_add_bookmarkA | Insert a bookmark (PlacesUtils.bookmarks.insert) and return its guid, which is what browser_remove_bookmark needs. Defaults to the bookmarks menu; pass the toolbar or unfiled guid to target those instead. |
| browser_remove_bookmarkA | Remove a bookmark or folder by guid (PlacesUtils.bookmarks.remove). Get guids from browser_list_bookmarks, or from browser_add_bookmark which returns the guid of what it just created. Passing a folder guid removes the whole subtree, so look the entry up first. |
| browser_list_closed_tabsA | List tabs that were closed in this session (SessionStore.getClosedTabData), newest first, with the index that browser_reopen_closed_tab takes. The list lives in memory for the session, so it is empty after a restart. This is the counterpart of browser_close_tab. |
| browser_reopen_closed_tabA | Reopen a tab that was closed in this session (SessionStore.undoCloseTab). index 0 is the most recently closed tab; get indices from browser_list_closed_tabs. This undoes browser_close_tab, so the pair is reversible. |
| browser_get_proxyA | Read the proxy settings (network.proxy.* preferences): the mode, the manual HTTP/HTTPS/SOCKS host and port, the "no proxy for" list, and the PAC URL. Mode values are the Firefox values: 0 none, 1 manual, 2 PAC, 4 auto-detect, 5 use system settings. Note a proxy change may need a restart to affect existing connections. |
| browser_set_proxyA | Set the proxy configuration (network.proxy.* preferences) and read back what was written. mode: 0 none, 1 manual (give host/hostPort), 2 PAC (give pacUrl), 4 auto-detect, 5 use system settings. Existing connections may keep using the old proxy until they are reopened. Read browser_get_proxy first so you can put the previous values back. A local HTTP proxy for testing is often written mode=1, host=127.0.0.1. |
| browser_list_cert_overridesA | List the TLS certificate exceptions the user has accepted (nsICertOverrideService.getOverrides), with host, port, flags and whether each is temporary. Useful to confirm a test site was trusted, or to check nothing unexpected was accepted. Temporary exceptions disappear when Firefox exits. |
| browser_set_window_sizeA | Set the client window position and size through BiDi (browser.setClientWindowState). Read the current geometry with browser_list_windows first. These are window pixels, not page pixels — use browser_set_viewport when you mean the page viewport instead. |
| browser_save_sessionA | Write the current window and tab session to a JSON file (SessionStore.getBrowserState). This only READS the browser; the file it writes is the input for browser_restore_session. Save before restoring, so the state you have now can be brought back. |
| browser_restore_sessionA | Replace the current session with one saved earlier (SessionStore.setBrowserState). WARNING: this REPLACES the windows and tabs that are open now — run browser_save_session first if you might want them back. The file must be JSON produced by the save tool; anything else is rejected before the state is touched. |
| browser_install_addonA | Install an extension from a local .xpi file or an unpacked extension directory (AddonManager.getInstallForFile). The path must exist and be readable by Firefox. Most add-ons work immediately; some need a restart. Confirm with browser_list_addons, then use browser_set_addon_state to enable or disable it, or browser_uninstall_addon to remove it. |
| browser_get_cookiesA | List cookies visible to the browser (BiDi storage.getCookies). Filter by name and/or domain to narrow it down. Values are returned as stored; treat them as secrets and do not paste them into shared logs. |
| browser_set_cookieA | Create or replace one cookie (BiDi storage.setCookie). domain and path scope it; omit domain for a host-only cookie. Use browser_get_cookies afterwards to confirm the value and expiry the browser actually stored. |
| browser_delete_cookiesA | Delete cookies matching a filter (BiDi storage.deleteCookies). Give name and/or domain; with no filter it clears every cookie the session can see, which is destructive — pass at least one of them unless you mean it. |
| browser_activate_tabA | Make a tab the active one (browsingContext.activate). Pass its context id from browser_list_tabs, or an index among the tabs. Useful before typing or clicking, since input goes to the active tab. |
| browser_traverse_historyA | Move the active tab through its session history (browsingContext.traverseHistory): delta -1 is back, +1 is forward. Fails with "no such history entry" when that direction has nothing to go to, which is a normal answer rather than a broken tool. |
| browser_list_promptsA | List page prompts (alert / confirm / prompt / beforeunload) that opened since this server connected, as reported by BiDi browsingContext.userPromptOpened, newest first. A page that shows one of these blocks its script until it is answered — use browser_handle_prompt to accept or dismiss. If a page seems stuck, check here first. |
| browser_handle_promptA | Answer the alert / confirm / prompt that a page is waiting on (BiDi browsingContext.handleUserPrompt). accept: true clicks OK, false cancels. userText supplies the answer for a prompt() dialog. Without this the page stays blocked; "no such alert" means nothing is waiting, which is a normal answer. |
| browser_set_permissionA | Set one permission for an origin (BiDi permissions.setPermission). MEASURED: the permission goes in a "descriptor" object — { descriptor: { name }, state, origin } — passing the name at the top level is rejected. state is granted / denied / prompt. Useful to stop a page blocking on a location or notification request during automation. |
| browser_create_containerA | Create a new container / user context (BiDi browser.createUserContext) and return its id. The id can be passed to browser_create_container style workflows and is what isolates cookies and storage. Remove it again with browser_remove_container; containers are not the same thing as Firefox account tabs. |
| browser_remove_containerA | Delete a container / user context by id (BiDi browser.removeUserContext). Anything stored inside that container is discarded, so check the id with browser_list_user_contexts first. |
| browser_set_toolbar_visibilityA | Show or hide a whole toolbar such as the bookmarks toolbar or the menu bar (CustomizableUI.setToolbarVisibility, which applies immediately). This is coarser than browser_set_toolbar_widget, which moves individual widgets. |
| browser_get_faviconA | Ask Places which favicon URL it has stored for a page (PlacesUtils.favicons.getFaviconForPage). Returns the URL only — it does not download the image. Empty result means no icon has been recorded for that page yet. |
| browser_new_windowA | Open a new browser window (BiDi browsingContext.create with type "window") and return its context id. A window is a top-level context; browser_close_window closes one again. Use browser_new_tab when you only need another tab — a new window is a visible change on screen and leaves a window to clean up. |
| browser_close_windowA | Close a whole window — every tab in it (BiDi browsingContext.close on a top-level context). Without a context it acts on the active tab is NOT what happens here: it closes the window that tab belongs to, so check first. browser_close_tab closes a single tab instead. |
| browser_clear_dataA | Delete stored data for ONE host or origin (ClearDataService). Requires host or origin — there is no "clear everything" path here on purpose, because that is not something to trigger by accident. Clears cookies, storage and caches belonging to that site. |
| browser_collect_response_bodyA | Turn on BiDi response-body collection (network.addDataCollector). It must be switched on BEFORE the request you care about happens — a request that already completed has no body to collect. Then read it with browser_get_response_body. Measured: Firefox clones the body while handling the response-started phase, so this tool also intercepts that phase; browser_get_response_body releases the response and only then reads the body only after network.responseCompleted for that request — asking earlier blocks, and asking with no hold at all can come back as "no such network data". Clear the interception with browser_network_intercept stop when finished. |
| browser_get_response_bodyA | Read the stored body of one request (network.getData, dataType response). The request must have happened AFTER browser_collect_response_body ran. Returns the base64 the protocol gives plus a decoded preview. "no such network data" means nothing was stored for that request id — check that collection was started first and that the id comes from browser_network_requests. |
| browser_relax_certificate_checksA | SECURITY SENSITIVE. Switches the certificate-checking preferences that make a self-signed or internally issued test certificate usable: cert pinning, OCSP and OCSP stapling/must-staple. relax:true turns those checks off; relax:false puts them back. Restoring is precise: a preference that had no user value before is cleared so it falls back to the Firefox default again, instead of being overwritten with a guess. The pre-relax values are remembered by THIS SERVER PROCESS, so restore only works while it is alive; if the server restarted, restore reports that instead of guessing. Some TLS changes only affect new connections. |
| browser_get_gpcA | Read whether the browser sends the Global Privacy Control signal (Sec-GPC header). NOTE: this command only exists in Marionette, not BiDi — so the browser must have been started with --marionette (scripts/launch-firefox.mjs takes care of it; port from DSH_BROWSER_MARIONETTE_PORT, default 2828). |
| browser_set_gpcA | Enable or disable the Global Privacy Control signal (requests then carry Sec-GPC: 1). MEASURED: the parameter is { gpc: boolean }, not { value: boolean } — the latter is rejected with "Value of |
| browser_split_viewA | Put two tabs side by side in one window (Firefox split view, gBrowser.addTabSplitView). Give the two tab contexts, or one context plus a url to open in the second pane. Useful when a human wants to watch two pages at once — including watching one page while the agent works on another. Reverse it with browser_unsplit_view, or by closing one of the two tabs. |
| browser_unsplit_viewA | Close the split view in the current window (gBrowser.unsplitView on the active tab). The two tabs stay open; they just stop sharing the window. Reports whether there was a split view to begin with. |
| browser_group_tabsA | Create a tab group from the given tabs (Firefox tab groups, gBrowser.addTabGroup). Give the tab context ids; label and color are optional. This is the same grouping the tab strip offers, so a human can collapse or reopen the group from the UI afterwards. |
| browser_duplicate_tabA | Duplicate a tab, keeping its history and state (Firefox tab cloning, gBrowser.duplicateTab). The clone opens next to the original. Duplicating is the cheapest way to keep "where I was" while the agent continues in the new copy — close the extra tab when you are done. |
| browser_pin_tabA | Pin a tab (it shrinks to an icon and stops being closed by "close others") or unpin it again. This is the same state the tab context menu toggles. |
| browser_mute_tabA | Mute a tab so it stays silent in the background, or unmute it. Mirrors the "mute tab" item in the tab context menu. Reports whether the tab is now muted. |
| browser_close_other_tabsA | Close all tabs in the window except the given one ("close other tabs" in the tab context menu). Pinned tabs are left alone, the same way the menu behaves. Use it to clean up after a series of agent tabs. |
| browser_close_tabs_to_rightA | Close every tab to the right of the given one ("close tabs to the right" in the tab context menu). Pinned tabs at the end are kept, matching the menu behaviour. |
| browser_move_tab_to_windowA | Take a tab out of its window and put it in a new window (gBrowser.replaceTabWithWindow). Handy to park a page on a second monitor — or to keep one page visible while the agent works in another window. |
| browser_move_tab_to_containerA | Reopen the current page in a tab belonging to a container/user context, then close the original (ContextualIdentityService). This is how the tab context menu moves a tab into a container. Get container ids from browser_list_user_contexts; the page reloads, so in-page state is lost. |
| browser_move_tabA | Move a tab to a new position in the tab strip. target is one of: start, end, before, after, index. For before/after give the reference tab context; for index give a 0-based position. This is drag-and-drop on the tab strip, done without the mouse (gBrowser.moveTabTo / moveTabToStart / moveTabToEnd). |
| browser_remove_duplicate_tabsA | Close tabs that load the same page as another tab, keeping one of each (gBrowser.removeDuplicateTabs). This is the same cleanup the tab context menu offers when it notices duplicates. Reports how many were closed. |
| browser_close_tabs_by_urlA | Close all tabs whose URL contains the given text. MEASURED: gBrowser.closeTabsByURI takes nsIURI objects and matches them EXACTLY, so it cannot do substrings — this walks the tabs and calls removeTab instead. The value is matched as a substring against the tab URL, so "example.com" catches all of that site. Useful to clean up after a batch run without touching unrelated tabs. |
| browser_list_tab_groupsA | List the tab groups (collapsible groups in the tab strip) with their id, label, colour, collapsed state and the tabs inside (gBrowser.getAllTabGroups). Use the id with browser_remove_tab_group or browser_ungroup_tabs. |
| browser_remove_tab_groupA | Remove a tab group by id (gBrowser.removeTabGroup). ungroup:true keeps the tabs and just dissolves the group; without it the tabs are closed. Get ids from browser_list_tab_groups. |
| browser_get_tab_processesA | Report the OS process id behind each tab (browsingContext.currentWindowGlobal.osPid). MEASURED: there is no gBrowser.getTabPids — that name does not exist in Tabbrowser.sys.mjs. Useful to see how many content processes Firefox spun up, or whether two tabs share one. |
| browser_set_window_stateA | Change the window state: normal, maximized, minimized or fullscreen. This is the title-bar buttons and F11, done programmatically. MEASURED: fullscreen is a PROPERTY (window.fullScreen = true), while the others are methods (maximize/minimize/restore). Minimizing hides the window — restore it before interacting again. |
| browser_set_window_positionA | Move the window to a screen coordinate (window.moveTo) and optionally resize it (window.resizeTo). Coordinates are in screen pixels. Use browser_get_window_geometry first if you need the current values, and remember multi-monitor setups can have negative coordinates. |
| browser_get_window_geometryA | Report the window state (normal/maximized/minimized/fullscreen), its screen position and its outer size. This is the readable counterpart of browser_set_window_state / browser_set_window_position / browser_set_window_size. |
| browser_picture_in_pictureA | Open a floating always-on-top mini window showing a URL (Document Picture-in-Picture). MEASURED: requestWindow throws "requires user activation" when called cold, so this tool first performs a real click in the page to satisfy that requirement. PiP windows are per-document: closing the tab closes the window. |
| browser_screen_infoA | Report the screen the window is on: size, usable area, and the window position within it. LIMITATION (measured): Firefox exposes only the CURRENT screen to page/chrome script — Services.appShell.screenManager is undefined, so there is no way to enumerate or name other monitors from here. To place a window on another monitor, use browser_set_window_position with that monitor coordinates (they can be negative). |
| browser_list_sidebarsA | List every sidebar this Firefox can show (history, bookmarks, passwords, tabs, AI chat, resource monitor, extension panels …) with its id and title, plus which one is currently open. Use the id with browser_set_sidebar. |
| browser_set_sidebarA | Open a sidebar by id, close the current one, or toggle it. Get ids from browser_list_sidebars. MEASURED entry point: window.SidebarController.show(id) / hide() / toggle(id). Action: open (needs id), close, toggle (needs id). |
| browser_set_menubar_visibilityA | Show or hide the classic menu bar (File/Edit/View…). MEASURED: the element is #toolbar-menubar and it normally carries autohide="true" (Firefox hides it by default and shows it on Alt). Setting visible=true clears autohide; visible=false restores the auto-hiding behaviour. |
| browser_set_downloads_panelB | Open or close the downloads panel (the toolbar arrow that lists recent downloads). MEASURED: the entry points are DownloadsPanel.showPanel() / hidePanel() — NOT .show(), which does not exist. Also reports whether the panel is currently showing. |
| browser_set_main_menuA | Open or close the application (hamburger) menu, the same one the ☰ button opens. Uses PanelUI.show() / PanelUI.hide(). Opening it is a visible change on screen — close it again if you only needed to inspect it. |
| browser_list_loginsA | List saved logins from the Firefox password manager, with origin, form action origin and username — but NEVER the password itself. Filter with origin to look at one site. Passwords are only readable through browser_get_login_password, which is a separate, explicitly named tool on purpose. Requires signon.rememberSignons=true for new logins to be saved. |
| browser_add_loginA | Save a username/password pair in the Firefox password manager. MEASURED: nsILoginInfo.init needs a formActionOrigin (or an httpRealm) — adding without one fails with "Can't add a login without a httpRealm or formActionOrigin"; this tool defaults formActionOrigin to the origin. The password is written to the browser, never echoed back. |
| browser_remove_loginA | Delete saved logins. Give origin and optionally username; with username omitted, every login for that origin is removed. Reports how many were removed so the result can be checked. |
| browser_get_login_passwordA | SECURITY SENSITIVE — returns a saved password in clear text. It is a separate, explicitly named tool so that reading a password is always a deliberate act, never a side effect of listing. Never paste the value into chat output, logs or screenshots. Requires both origin and username. |
| browser_find_login_formA | Inspect the page for login/registration forms: each form id and action, plus its input fields with name, type and autocomplete hint. Use this before filling a form so the field names are known instead of guessed. |
| browser_fill_loginA | Type a username and password into a form using real keystroke input (not a scripted value assignment), then optionally submit. This is the "you give me credentials, I fill them" path — the agent types what it was given. It does NOT use the browser password manager: MEASURED, Firefox native autofill only fills after a human picks an entry from its own dropdown, which is native UI no automation can click. |
| browser_list_popup_notificationsA | List the browser doorhanger notifications currently offered (the "Save password?" / "Remember this site" kind). These are the prompts Firefox itself shows after a form submit. Use browser_accept_popup_notification to press one — this is how "let the browser remember the password" is done programmatically. |
| browser_accept_popup_notificationA | Press the main action of a doorhanger notification by id — for example accepting "Save password?". Get ids from browser_list_popup_notifications. This is the programmatic equivalent of the human clicking the button in the browser UI. |
| browser_hoverA | Move the real mouse pointer onto an element (or a viewport coordinate) so hover-only UI appears: menus, tooltips, :hover styles, "show on hover" controls. Uses BiDi input.performActions pointerMove, so the page sees a genuine pointer position, not a synthetic event. Give selector OR x/y. |
| browser_dragA | Perform a real mouse drag: press on the source, move in steps, release on the target. Source and target may each be a CSS selector or viewport coordinates. Step count matters for pages that track dragging (sliders, canvas) — raise steps if a plain move is not picked up. COLOR: for HTML5 drag-and-drop this will NOT produce a drop event. That is upstream bug 1515879 ("HTML5 drag and drop does not work", Remote Protocol / Marionette, filed 2018-12-21, still NEW) — EventUtils.js ends the drag session without synthesizing drop. Use browser_drag_html5 for HTML5 targets. |
| browser_scrollA | Scroll the page (or one scrollable element) using real wheel events (BiDi input.performActions type "wheel"). Give deltaX/deltaY in pixels: positive Y scrolls down. With selector, the wheel is aimed at that element, which is how inner overflow containers scroll — document.scrollTop never moves for those. |
| browser_press_keyA | Send a real key press, optionally with modifiers — Ctrl/Shift/Alt/Meta plus one key. Works for shortcuts (Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+Z), navigation (Tab, Enter, Escape) and repeated keys. Uses BiDi input.performActions with the "key" action type, so pages see ordinary keydown/keyup events with the modifier flags set. Give keys in the WebDriver key vocabulary: Control, Shift, Alt, Meta, Enter, Tab, Escape, Backspace, Delete, ArrowUp/Down/Left/Right, Home, End, PageUp, PageDown, F1-F12, or a single character. |
| browser_key_holdA | Press and HOLD a key for N milliseconds, then release — what you need for games (walk right, keep accelerating) and for drag-modifier workflows (hold Shift while clicking). A real keyDown is sent, the wait happens, then keyUp. Do not hold for very long; the page may repeat the key itself. |
| browser_mouse_actionA | Perform a mouse action other than a plain left click: right button (context menu), middle button, double-click, or press-and-hold for N ms. Target by selector or viewport coordinates. The page sees the matching mousedown/mouseup/contextmenu events with the right button number. NOTE: a site that blocks the context menu in JavaScript (preventDefault on contextmenu) will still block it — that is the page deciding, not the input layer. |
| browser_hide_automation_flagsA | Make pages stop seeing the automation marker: registers a BiDi preload script that reports navigator.webdriver as false in every new document. MEASURED: with a plain WebDriver-driven session navigator.webdriver is true, which is exactly what anti-bot checks look for; after this preload it reads false. It is a document-level patch, not a promise of undetectability — other signals (missing plugins, synthetic event timing, CDP-style fingerprints) can still give automation away. Pass remove:true to undo. |
| browser_drag_html5A | Perform an HTML5 drag-and-drop by dispatching the DOM events (dragstart/dragenter/dragover/drop/dragend) with a real DataTransfer. WHY THIS EXISTS: MEASURED — the pointer-based browser_drag triggers dragstart and dragover but never drop or dragend, because BiDi only synthesizes mouse/wheel/touch (there is no Input.dispatchDragEvent as in CDP) and Firefox cancels a drag on pointer-up. This tool therefore SIMULATES the drop in page script: it works on pages that listen for the standard drag events, but it is NOT a real drag — pages that check event.isTrusted, or that require genuine OS drag semantics, will still refuse. This exists because of upstream bug 1515879: WebDriver-driven HTML5 DnD never gets a drop event. |
| browser_move_tab_to_groupA | Move a tab into a group that already exists (gBrowser.moveTabToExistingGroup). Unlike browser_group_tabs, which creates a new group, this joins one. Get the group id from browser_list_tab_groups. |
| browser_move_tab_to_split_viewA | Move a tab into the split view that is already open in this window (gBrowser.moveTabToSplitView). Requires an existing split view — create one first with browser_split_view. Complements browser_split_view, which builds a new split view from two tabs. |
| browser_multi_select_tabsA | Multi-select tabs so batch operations apply to all of them (gBrowser.addToMultiSelectedTabs / selectAllTabs / clearMultiSelectedTabs). Reports how many are selected. This is Ctrl+click on the tab strip, done programmatically. |
| browser_set_tab_visibleA | Hide a tab from the tab strip (gBrowser.hideTab) or bring it back (gBrowser.showTab). A hidden tab stays loaded and reachable by context id, it just is not shown — handy to park a tab without closing it. The last visible tab in a window cannot be hidden. |
| browser_get_cache_usageA | Report the size of the on-disk HTTP cache (Services.cache2.asyncGetDiskConsumption). Note: this is only the network cache — it does not include IndexedDB, Service Worker caches or storage, which live elsewhere. |
| browser_clear_cacheA | Clear the on-disk HTTP cache (Services.cache2.clear). Pass origin to clear just that site, or domain to clear a base domain. Without arguments the whole HTTP cache is cleared, which is a separate operation from browser_clear_data (that one goes through ClearDataService and can also clear cookies/storage). |
| browser_list_permissionsA | List the per-site permission grants Firefox has stored (camera, microphone, notifications, geolocation, popups, cookie behaviour …), read through Services.perms. Filter by origin to look at one site, or by type. Use browser_set_permission to change one. |
| browser_set_download_stateA | Act on one entry of the download list by index (as shown by browser_list_downloads): pause it, resume it, or cancel it. Cancelling removes the partial file — this cannot be undone. |
| browser_search_prefsA | Search all Firefox preference NAMES (Services.prefs.getChildList) for a substring and report the matching names with their current values and whether a user value is set. Use it to discover the pref behind a setting before changing it with browser_set_pref. Values are truncated; this is a discovery tool, not a dump. |
| browser_reset_prefA | Remove the user value of a preference so it falls back to the built-in default (Services.prefs.clearUserPref). Reports whether it had a user value before and whether it still has one after. Pair with browser_search_prefs to find the name, and browser_get_pref to see the current effective value. |
| browser_set_themeA | Activate one of the installed themes by id (from browser_list_themes), or pass default:true to go back to the default theme. Uses the theme add-on enable path, which is what the Add-ons manager does when you click "Enable". The change is visible immediately. |
| browser_set_homepageA | Set the homepage (browser.startup.homepage, one URL per line for several) and what Firefox opens on startup (browser.startup.page: 0 blank, 1 homepage, 2 last session, 3 bookmarks). Pass only homepage to leave the startup behaviour alone. |
| browser_set_tracking_protectionA | Switch the tracking-protection level: off, standard (blocks known trackers in private windows only), strict (also blocks them in normal windows and tightens cookie behaviour). This sets the same prefs the Privacy panel toggles, so the change appears in about:preferences immediately. |
| browser_set_dohA | Set network.trr.mode (0 off, 2 on with fallback, 3 on only, 5 explicitly off) and optionally the resolver URI (network.trr.uri). This is what the Network Settings > DNS over HTTPS control writes. |
| browser_get_search_engineA | Report the default search engine name as configured (browser.search.defaultenginename) plus the list of engine names found in the profile search settings file (search.json.mozlz4). LIMITATION: changing the default engine needs the internal SearchService, whose ES module is not reachable from a remote script in this build (every resource:// path fails to load), so this tool is read-only. |
| browser_extension_detailsA | Report what UI entry points an extension actually exposes: its options page URL, browser action popup, and sidebar panel. Read from the live manifest, because AddonManager.optionsURL is null for MV3 extensions that declare options_ui instead of options_page — a trap worth knowing. |
| browser_open_extension_optionsA | Open the options page of an extension in a new tab, resolved from its live manifest (options_ui.page or options_page). Fails clearly when the extension has no options page. Use browser_extension_details first if you want to see the URL without opening it. |
| browser_open_extension_popupA | Open the browser-action popup of an extension, the same thing clicking its toolbar button does. Firefox does not expose extension.browserAction to remote scripts (policy.extension is undefined), so this drives the real UI: it opens the unified-extensions panel, finds the item for this id, and clicks its action button. Reports the popup URL that ended up loaded, so silence is not mistaken for success. |
| browser_close_extension_popupA | Close the tool-panel popup that browser_open_extension_popup opened, by hiding the widget panel. Firefox has no public "close popup" API, so this hides the panel element the popup lives in. Reports what it found and closed. |
| browser_extension_storageA | Read or write the private key/value storage of an extension (browser.storage.local) — where an extension keeps its own settings and lists. It works by opening one of the extension own pages in a background tab, running the storage call in that page context (the only place where the extension APIs exist), then closing the tab. MEASURED limitation: a page of the extension is required, so extensions without an options page cannot be reached this way. |
| browser_run_in_extensionA | Evaluate an expression inside the context of one of the extension own pages, where the WebExtension APIs (browser.*) and the extension DOM are available. It opens the page in a background tab, evaluates, then closes the tab. This is the general escape hatch behind browser_extension_storage: with it an extension that exposes no command line of its own can still be inspected and driven. The expression should return a JSON value or a string. |
| browser_tampermonkey_listA | List the userscripts Tampermonkey currently manages, by asking its own internal API (loadTree). Works with the official Firefox build (firefoxbeta@tampermonkey.net) out of the box; a converted Chrome package needs its INTERNAL_PAGE_PROTOCOLS patched to moz-extension: first. |
| browser_tampermonkey_installA | Install (or update) a userscript by handing its source to Tampermonkey saveScript. Pass code for a whole userscript, or url to fetch one over HTTP first. De-duplicates by the @name header: an existing script with that name is updated instead of duplicated, because MEASURED saveScript creates a NEW script for every new uuid. Returns the assigned uuid. |
| browser_tampermonkey_set_enabledA | Turn a userscript on or off in Tampermonkey (modifyScriptOptions with {enabled}). Identify the script by uuid, or by the @name shown in browser_tampermonkey_list. A disabled script stays installed but is not injected. |
| browser_tampermonkey_removeA | Delete userscripts by uuid or by the @name shown in browser_tampermonkey_list (purgeScripts). Removed scripts go to Tampermonkey trash by default, so the deletion is recoverable from its dashboard. |
| browser_devtools_toolsA | List the Developer Tools panels this Firefox exposes (inspector, webconsole, jsdebugger, netmonitor, styleeditor, performance, memory, storage, accessibility, application, options) together with the id you can pass to browser_devtools_open. MEASURED: these modules live behind their own loader, not the normal ESM one — the module id is "devtools/client/framework/devtools" (no leading g) and it exports gDevTools. |
| browser_devtools_openA | Open the Firefox DevTools toolbox on a tab and select one tool panel. MEASURED: the entry point is gDevTools.showToolboxForTab(tab, {toolId}) — there is no gDevTools.openToolbox in this build. Tool ids come from browser_devtools_tools. The toolbox may open in a separate window, which is why its markup is not visible in the browser document. |
| browser_devtools_closeB | Close the DevTools toolbox for a tab, or all of them. MEASURED: gDevTools.closeToolboxForTab(tab, {destroy}) and gDevTools.closeAllToolboxes() are the entry points. |
| browser_open_about_pageB | Open a Firefox internal page in a new tab. This covers the entries of the Tools > Browser Tools menu that are plain about: pages rather than DevTools APIs — MEASURED: the Task Manager is about:processes and Developer Tools for extensions is about:debugging. Both render real content this way. Also accepts addons, config, support, profiles, netmonitor-free pages and any other whitelisted about: name. |
| browser_about_page_infoA | Report the title and visible text of an already-open about: page, so a caller can confirm the internal page actually rendered instead of assuming it did. MEASURED: about:processes starts with an empty table skeleton and fills in asynchronously, so this may need calling again after a short wait. |
| browser_consoleB | Open or close the Browser Console (the Ctrl+Shift+J window that logs browser-wide messages). MEASURED: call BrowserConsoleManager.openWindow() with NO arguments — its openBrowserConsole(win) variant throws "can't access property windowRoot, win is undefined" because the manager has no commands bound outside a real DevTools context. openWindow() spawns the standalone devtools:webconsole window, which is what the menu item does. |
| browser_page_sourceA | Return the serialised DOM of a tab (document.documentElement.outerHTML). This is what the page looks like after scripts ran. It is NOT the original bytes on the wire, so it is not the same as the View Source menu item — use the network tools if the raw response matters. |
| browser_responsive_modeB | Open, close or query Firefox Responsive Design Mode for a tab. MEASURED details that matter: (1) the entry point is ResponsiveUIManager.openIfNeeded(window, tab, {trigger}) from module "devtools/client/responsive/manager" — that module IS the manager instance, not a container; (2) the window argument MUST come from Services.wm.getMostRecentWindow("navigator:browser"); passing gBrowser.ownerGlobal through the DevTools loader fails with "can't access property gBrowser, window is undefined"; (3) the manager must be cached on the chrome window — a fresh DevToolsLoader cannot see a UI opened through another loader, so closing would silently do nothing. IMPORTANT: iframe.rdm-toolbar is a PERMANENT node in the browser document — it stays after closing, so never use its presence to decide whether RDM is active. Use the responsive-mode class on the browser container (or the manager isActiveForTab) instead. |
| browser_eyedropperA | Drive the DevTools eyedropper / colour picker on a tab. MEASURED, and this is the whole trick: Firefox deliberately supports the eyedropper WITHOUT a toolbox (bug 1701004), so it is reachable through a standalone commands instance — CommandsFactory.forTab(tab), targetCommand.startListening(), target.getFront("inspector"), then inspectorFront.pickColorFromPage({copyOnSelect, fromMenu}). That call returns immediately and the colour arrives later as a "color-picked" event carrying color:Arg(0,"string") (see devtools/shared/specs/inspector.js); "color-pick-canceled" fires on escape. Because picking requires a real pointer press on the page, this tool starts the picker and then polls: action:"start" arms it, action:"result" reports the colour once the user has clicked, action:"cancel" aborts. The picker instance is cached on the chrome window so polling reaches the same session. |
| browser_remote_debuggingA | Report or change Firefox remote debugging settings — the "Remote Debugging" entry in the Browser Tools menu. MEASURED on Firefox Developer Edition: the port lives in the integer pref devtools.debugger.remote-port (observed 6000) and the server module is resource://devtools/server/devtools-server.sys.mjs (an object, so this build does ship a DevTools server). Note that DSH itself talks to Firefox over WebDriver BiDi on the separate --remote-debugging-port flag, which is not this pref. |
| browser_element_screen_coordsA | Return the on-screen pixel centre of an element, for handing to a native input driver (Cua, SendInput). MEASURED: compute it as mozInnerScreenX/Y + getBoundingClientRect() centre, WITHOUT multiplying by devicePixelRatio — mozInnerScreen* is already in screen pixels, and scaling it again puts the cursor far off target. This exists because some browser features only react to genuine OS-level input — the DevTools eyedropper and HTML5 drag-and-drop both ignore synthesised DOM/WebDriver events, but respond normally to a real SendInput click or drag. |
| browser_extension_permissionsA | Report the user-granted permissions and origin patterns of installed extensions. MEASURED: AddonWrapper exposes userPermissions.permissions (string array) and userPermissions.origins (string array), so the permission surface is readable without opening about:addons. Note this reads what is granted; it does not grant or revoke — those prompts are extension-owned UI. |
| browser_autofill_statusA | Report the address / credit-card autofill state. MEASURED: resource://autofill/FormAutofill.sys.mjs loads and exposes isAutofillEnabled, and FormAutofillStorage.sys.mjs also loads. This only reports status — the stored address and card records live behind the autofill storage API, which this build does not expose for remote write, so treat editing saved autofill entries as out of scope. |
| browser_search_engineC | Manage Firefox search engines. MEASURED, and this is the whole trick: the SearchService is NOT reachable via resource:// in this build — it lives under the moz-src protocol, so the module id is "moz-src:///toolkit/components/search/SearchService.sys.mjs" and it exports SearchService (a singleton). That object exposes getEngines/setDefault/setDefaultPrivate/moveEngine/removeEngine/restoreDefaultEngines and the add* family (addEngineFromExtension / addOpenSearchEngine / addUserEngine). Older guidance that the service is unreachable was wrong. |
| browser_clear_site_cacheA | Clear cached data for a SINGLE site instead of the whole cache. MEASURED: this needs ClearDataService from resource://gre/modules/ClearDataService.sys.mjs, constructed with new, then deleteDataFromSite(site, {}, isUserRequest, flags, callback). Two traps: the site argument is a SCHEMELESS site (example.com, not https://example.com/), and the callback must be an object with onDataDeleted(resultFlags) — passing a plain function hangs forever. resultFlags 0 means every cleaner succeeded. |
| browser_autofill_dataA | Read and write the saved form-autofill ADDRESSES (not passwords). MEASURED: resource://autofill/FormAutofillStorage.sys.mjs exports formAutofillStorage, a FormAutofillStorage instance with .addresses and .creditCards; the addresses store exposes add/update/removeMany/getAll-style methods, so this is writable (contrary to an earlier note that only status was reachable). NOTE: the profile may serve addresses from the Rust store depending on extensions.formautofill.addresses.storage.rust.* prefs. |
| browser_sync_probeA | Inspect the Sync/FxAccounts surface WITHOUT signing in to any real account. MEASURED: resource://gre/modules/FxAccounts.sys.mjs exports FxAccounts whose constructor takes a mocks object (constructor(mocks = null)), which is the supported way to exercise account code paths offline — this is how the Sync test suites run. This tool reports what can be reached and whether a real account is signed in; it never sends credentials anywhere. |
| browser_reader_modeA | Open a tab in Reader View (about:reader) or report whether the current page is reader-able. MEASURED: ReaderMode lives at moz-src:///toolkit/components/reader/ReaderMode.sys.mjs (NOT resource:// — that path fails), and about:reader?url= is the page form. This is the "reading mode" that strips chrome and ads from an article. |
| browser_sync_nowA | Run the real Firefox Sync engine instead of the mock account. MEASURED: the entry point is NOT Weave.sys.mjs — that module only exports a WeaveService class whose instance is an empty object. The real singleton comes from resource://gre/modules/services-sync/service.sys.mjs, which exports Service with sync()/status. Without a signed-in account the engine reports notConfigured, which is the expected offline answer rather than a bug. Pair with browser_sync_probe for mock-account testing. |
| browser_bookmark_folderA | Manage bookmark FOLDERS (the existing bookmark tools only handle individual bookmarks). MEASURED: PlacesUtils.bookmarks exposes insert/update/remove/search/fetch, and the root folders are addressed by the well-known guids (toolbar____, menu________, unfiled_____, root________). Removing a folder removes its contents, so check the child count first with action:"list". |
| browser_open_firefox_viewA | Open the Firefox View page (about:firefoxview), the tab that lists recently closed tabs, tabs from other devices and sync status. MEASURED: it is a plain about: page, so no privileged API is needed — bBrowser.addTab suffices. This is separate from browser_open_about_page because firefoxview is a browser/ page rather than a toolkit one, and the tool enum keeps them apart on purpose. |
| browser_add_search_engineB | Install a user-defined search engine. MEASURED: SearchService from moz-src:///toolkit/components/search/SearchService.sys.mjs exposes addUserEngine(formInfo) — the simplest of the four add paths (addOpenSearchEngine needs a full OpenSearch URL, addSearchEngine needs an engine object, addEngineFromExtension needs an add-on). formInfo takes name plus a URL template containing {searchTerms}. |
| browser_printA | Open the printing surface for a tab. MEASURED: PrintUtils is NOT a module — it is the global object defined by chrome/toolkit/content/global/printUtils.js and reachable as PrintUtils on the browser window. It offers startPrintWindow, togglePrintPreview, showPageSetup, getPrintSettings and handleSystemPrintDialog (there is no print()/showPrintDialog()). Use browser_pdf when you want the PDF without any UI. |
| browser_oauth_clientsA | List devices and web sessions attached to the signed-in Mozilla account (listAttachedOAuthClients). MEASURED: with no account signed in it resolves to an empty array rather than throwing, and the underlying _fetchAttachedOAuthClients needs a session token, so this only returns data for a REAL signed-in account. The mock account from browser_sync_probe deliberately has no session, so it yields an empty list — that is the expected offline answer. |
| browser_translationsB | Report the full-page translation state and optionally enable the feature. MEASURED: the pref is browser.translations.enable (false by default in this build); the actor resource://gre/actors/TranslationsParent.sys.mjs exposes getIsTranslationsEngineSupported / getWebContentLanguages / isTranslationsEngineMocked. With a real engine the supported-language map is populated only after language packs are downloaded, so an empty map while isEngineSupported is true means "engine present, packs absent" rather than a broken feature. |
| browser_bookmark_manageA | The management half of bookmarks that browser_add_bookmark / remove_bookmark / bookmark_folder do not cover. MEASURED: PlacesUtils.bookmarks.update(info) accepts guid plus parentGuid/index for a move, or title/url for an edit; bookmarks.search({query}) finds by title or URL; PlacesUtils.tagging.tagURI/untagURI/getTagsForURI handle tags. NOTE: fetchTree is NOT implemented in this build, so listing stays on search({parentGuid}). |
| browser_history_manageA | The management half of history that browser_list_history does not cover. MEASURED: PlacesUtils.history exposes clear() and remove() and removeVisitsByFilter(); the query-style methods (getNewQuery/executeQuery) are also present if you need raw querying. Use action:"search" to find entries, action:"remove" with a url to drop one page, or action:"clear" to wipe everything. |
| browser_download_manageA | The management half of downloads that browser_list_downloads and set_download_state do not cover. MEASURED: the service comes from resource://gre/modules/Downloads.sys.mjs as Downloads.getList(Downloads.ALL), and that list has remove(item) / removeFinished(filter) / getAll() / add(). NOTE: Services.downloads does not exist in this build, so always go through Downloads.getList. |
| browser_dom_treeA | Walk a real DOM tree through the DevTools inspector walker and return a bounded outline. MEASURED: the path is CommandsFactory.forTab -> targetCommand.startListening -> target.getFront("inspector") -> inspector.getWalker() -> walker.getRootNode(), and the root reports nodeName "#document" with numChildren. This is the structured counterpart to browser_page_source (which returns raw HTML text) and is far cheaper for large pages. |
| browser_a11y_treeA | Walk the accessibility tree through the DevTools accessibility front and return a bounded outline of roles and names. MEASURED: getRoot() does NOT exist on the front — the path is target.getFront("accessibility") then getWalker() (the front also offers getTraits/getSimulator/bootstrap). Useful for checking labels, roles and whether controls are exposed to assistive tech. |
| browser_bookmark_backupA | List the JSON bookmark backups in the profile and optionally create a new one. MEASURED: the module is resource://gre/modules/PlacesBackups.sys.mjs and exports PlacesBackups with getBackupFiles / getMostRecentBackup / saveBookmarksToJSONFile / create / getBookmarkCountForFile. Note PlacesUtils.importFromHTML / exportToHTML do NOT exist in this build, so HTML import/export is unavailable — JSON backup is the supported path. |
| browser_download_addA | Begin a download of a URL to a local file. MEASURED: Downloads.createDownload exists on the module from resource://gre/modules/Downloads.sys.mjs (Downloads.getDataItem does NOT). This is the write half that browser_download_manage does not cover. Pass an absolute target path; the parent directory must exist. |
| browser_bookmarks_exportA | Write the bookmark tree to a Netscape Bookmark HTML file. That is the format Chrome, Edge and Safari also export, so it is how bookmarks move between browsers. MEASURED: Firefox has no usable exportToHTML in this build, so this walks PlacesUtils.bookmarks.search({parentGuid}) recursively and builds the HTML itself. TWO TRAPS: item.url is a URI object (read .spec), and search({parentGuid}) also returns the parent folder itself which must be skipped or the walk never ends. |
| browser_bookmarks_importA | Read a Netscape Bookmark HTML file and recreate its folders and links under a chosen bookmark folder. MEASURED: Firefox has no importFromHTML in this build, so this parses the file with DOMParser and calls PlacesUtils.bookmarks.insert per item. Import is one level of grouping: each opens a folder and the following entries land inside it, which matches how browsers export a single grouping level. |
| browser_memory_usageA | Read live memory figures from nsIMemoryReporterManager. MEASURED: resident / residentUnique / heapAllocated are properties (not methods) and getReports() also exists for the full breakdown. Use this when a page feels heavy and you want numbers rather than guesses. |
| browser_troubleshootA | Return the same diagnostic snapshot that about:support shows. MEASURED: the module is resource://gre/modules/Troubleshoot.sys.mjs and Troubleshoot.snapshot() resolves to sections including modifiedPreferences, lockedPreferences, printingPreferences, media, accessibility, pdfjs, intl, securitySoftware, startupCache and libraryVersions. Use sections to narrow the output. |
| browser_certificate_manageC | Read the certificate database and manage per-host certificate exceptions. MEASURED: nsIX509CertDB offers addCert / getCerts / importCertsFromFile (removeCert is absent in this build), and nsICertOverrideService offers rememberValidityOverride / clearValidityOverride / clearAllOverrides. Clearing all overrides is irreversible, so it requires confirm:true. |
| browser_import_loginsB | Import saved logins from a CSV (the format Chrome and Firefox both export). MEASURED: the module is resource://gre/modules/LoginCSVImport.sys.mjs and its static entry point is LoginCSVImport.importFromCSV (the class instance has no prototype methods). Column names are mapped by the browser itself, so pass a file already in the standard url/username/password layout. |
| browser_import_cookiesA | Set a batch of cookies in one call instead of one browser_set_cookie per cookie. Needed because Firefox allows only ONE active BiDi session, so an external script cannot open its own connection while this server holds the session — the batch has to run inside the shared session. Pass an array of {name, value, domain, path, expirySeconds, secure, httpOnly, sameSite}. Reports per-item failures instead of stopping on the first error. |
| browser_import_cookies_from_fileA | Read a JSON file containing an array of cookie objects and set them all inside the existing shared BiDi session. Each object may have {name, value, domain, path, expirySeconds, secure, httpOnly, sameSite}. This exists because an external script cannot open its own BiDi session while this server holds the only one, and hand-typing hundreds of cookies through browser_import_cookies is impractical. |
| browser_import_logins_from_fileA | Read a JSON file holding an array of {origin, user, password} and insert each one through nsILoginManager directly, bypassing LoginCSVImport (which fails with FILE_PERMISSIONS_ERROR in this build). Tell the truth about duplicates: Firefox silently merges or rejects entries whose origin+username already exist. |
| browser_import_historyA | Read a JSON file holding an array of {url, title, lastVisit, visitCount} and insert each into Places via PlacesUtils.history.insert, so migrated history keeps its original timestamps instead of looking like fresh visits. Entries whose url already exists are still inserted (Places merges visits for a known url). |
| browser_list_windowsA | List Firefox windows with their state and geometry (privileged context). |
| browser_get_prefA | Read a preference by name (privileged context). Returns type and value. |
| browser_set_prefA | Set a preference (privileged context). This changes the browser and outlives the session. Some preferences are locked; some are only read at startup, so a change has no effect until Firefox restarts. |
| browser_list_historyB | Read recent history entries (privileged context). PlacesUtils.history.executeQuery is synchronous and returns a result whose root must be container-opened. |
| browser_list_bookmarksA | Read bookmarks from a folder (privileged context), each entry with the guid that browser_remove_bookmark needs. Defaults to the toolbar; the bookmarks menu and Other Bookmarks are reachable through parentGuid. |
| browser_toolbar_widgetsA | List the widget ids currently in a toolbar area (privileged context). Use area "nav-bar" for the main toolbar. |
| browser_list_addonsB | List installed extensions (privileged context). |
| browser_list_user_contextsA | List BiDi user contexts. The "default" one always exists; others are containers created via browser.createUserContext. |
| browser_selfcheckA | Run after a Firefox upgrade: report the version and verify the non-standard pieces this server relies on (the moz:scope "chrome" scope, privileged Services access, PlacesUtils.history.executeQuery, CustomizableUI, AddonManager). A failing line means the upstream API moved and this server needs updating. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 178 tools
Despite extremely rich individual descriptions, the surface contains many near-synonym clusters that an agent must read at length to separate: browser_clear_data vs browser_clear_cache vs browser_clear_site_cache, browser_list_downloads vs browser_list_download_events vs browser_download_manage vs browser_download_add, browser_search_engine vs browser_get_search_engine vs browser_add_search_engine, browser_evaluate vs browser_run_in_extension, and a large pile of tab tools (close_tab/close_other_tabs/close_tabs_to_right/close_tabs_by_url/remove_duplicate_tabs/move_tab/move_tab_to_window/move_tab_to_container/move_tab_to_group/move_tab_to_split_view). With 178 tools, selection among overlapping neighbours is genuinely error-prone even with good prose.
The browser_ snake_case prefix is used throughout, which is a real plus, but verb choice is inconsistent: new_tab/new_window alongside create_container, open_about_page, add_bookmark, insert-style tools, and many noun-only names (browser_info, browser_page_info, browser_screen_info, browser_memory_usage, browser_selfcheck, browser_troubleshoot). The result is readable but not predictable enough to guess a name without consulting the list.
178 tools is an extreme mismatch for any agent-facing server: the tool list alone consumes enormous context and forces the model to discriminate among dozens of low-frequency operations (DevTools opener, Tampermonkey userscripts, Sync probing, toolbar widget layout, certificate overrides). Far past the 50+ threshold for 'extreme mismatch', even though each tool is individually justified.
The surface is remarkably complete for full browser control: tabs/windows lifecycle, navigation, input, screenshots/PDF, cookies, storage clearing, bookmarks (including folders, tags, HTML import/export, backups), history, downloads, logins, prefs, add-ons/themes, proxies, certificates, containers, DevTools, network interception and response bodies. Even acknowledged upstream gaps are documented and worked around rather than left as blind spots.