Skip to main content
Glama

WebMCP Controller

Let an AI agent drive the Firefox you already use: your current profile, your open tabs, your logins. Any MCP client can connect, including Claude Code, Codex, Gemini CLI, opencode and Cursor.

There is no server to start by hand. Firefox launches a small helper app (a native messaging host) when the add-on loads, and the helper serves MCP on your own machine.

Install the add-on from addons.mozilla.org

Features

  • Your real browser. Works on your current profile, with your tabs, bookmarks, history and logged-in sessions. Not a clean headless browser.

  • Local by design. The helper listens on 127.0.0.1 and every request needs a 256-bit bearer token. Nothing leaves your machine unless the AI tool you connect sends it somewhere.

  • Visible AI cursor. While the agent works, a glowing cursor glides to whatever it is clicking or typing into, with a bubble that says what it is doing. Toggle it from the popup.

  • Pause AI. The popup button, or Ctrl+Shift+., stops every command until you resume. The toolbar badge shows II while paused and a lime dot while the AI is working.

  • 44 tools. Tabs, windows, navigation, page reading, screenshots, form filling, clicking, bookmarks, history, downloads and cookies (read-only). See Tools.

Related MCP server: hronaut

Quick start

  1. Install the helper (once per computer). The add-on's popup shows the right command for your system with a Copy command button. See Installing the helper.

  2. Install the add-on from the link above.

  3. Connect your AI. Click the toolbar icon, then Copy MCP config (a green dot means ready). Paste the config into your AI tool.

Claude Code, for example:

claude mcp add --transport http firefox http://127.0.0.1:8901/mcp \
  --header "Authorization: Bearer <token>"

The settings page (toolbar icon → Settings) also has commands and snippets for the other clients. The generic config looks like this:

{
  "mcpServers": {
    "firefox": {
      "type": "http",
      "url": "http://127.0.0.1:8901/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}
  • Codex (~/.codex/config.toml):

    [mcp_servers.firefox]
    url = "http://127.0.0.1:8901/mcp"
    http_headers = { Authorization = "Bearer <token>" }
  • Gemini CLI: gemini mcp add --scope user --transport http firefox http://127.0.0.1:8901/mcp --header "Authorization: Bearer <token>"

  • opencode: {"mcp":{"firefox":{"type":"remote","url":"http://127.0.0.1:8901/mcp","headers":{"Authorization":"Bearer <token>"}}}}

  • stdio-only clients: npx -y mcp-remote http://127.0.0.1:8901/mcp --header "Authorization: Bearer <token>"

Check the connection from a shell:

curl http://127.0.0.1:8901/health
# {"ok":true,"extensionConnected":true,"version":"0.4.0"}

Architecture

 Firefox (WebExtension)                 Helper (native messaging host)          AI client
+----------------------+   stdio     +-----------------------------+   HTTP   +--------------+
| background.js       | <---------> | webmcp-host                 | <-----> | Claude Code,  |
| content scripts     |  Firefox    | serves MCP on 127.0.0.1     |  POST   | Codex, ...    |
| (tabs, page, cursor)|  starts it  | Bearer-token authenticated  |  /mcp   |              |
+----------------------+            +-----------------------------+         +--------------+
  • The add-on owns the settings. It generates the token on first run and sends {token, port, bind} to the helper. Defaults: port 8901, bind 127.0.0.1.

  • AI clients talk to the helper over MCP Streamable HTTP, POST /mcp, with Authorization: Bearer <token>. Two endpoints need no token: GET /health and GET /.

  • The helper runs as long as the add-on runs. Close Firefox and the MCP endpoint goes away.

Why an extension: a WebExtension gets every tab, window, bookmark and cookie of the profile you use, which the remote-debugging protocol does not offer. Why a native host: extensions cannot listen on ports, and native messaging is the sanctioned way for an add-on to reach a local program.

Installing the helper

Windows, in PowerShell or Command Prompt:

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hamb1y/webmcp-controller/releases/latest/download/install.ps1 | iex"

macOS, Linux, WSL, in a terminal (inside WSL it installs the Windows helper):

curl -fsSL https://github.com/hamb1y/webmcp-controller/releases/latest/download/install.sh | sh

The script picks the x64 or arm64 binary, checks its SHA-256 and runs install. Prefer to do it by hand? Download webmcp-host-<os>-<arch> from the releases page, then run it with install (on Windows, double-click the .exe).

install copies the helper to a per-user folder and registers it with Firefox. No admin rights needed.

OS

Helper goes to

Registered via

Windows

%LOCALAPPDATA%\webmcp-controller\

HKCU\Software\Mozilla\NativeMessagingHosts\webmcp_controller

macOS

~/Library/Application Support/webmcp-controller/

~/Library/Application Support/Mozilla/NativeMessagingHosts/

Linux

~/.local/share/webmcp-controller/

~/.mozilla/native-messaging-hosts/ (and the Snap path)

Other commands: status, uninstall, version, help.

To update, run the install command again. Firefox switches to the new helper within a few seconds, with no restart.

Running from a source checkout

npm install
npm run install-host   # registers a Node-based helper with Firefox

Then load extension/manifest.json as a temporary add-on at about:debugging#/runtime/this-firefox → Load Temporary Add-on….

Harness inside WSL, Firefox on Windows

WSL2's default NAT networking cannot reach Windows' 127.0.0.1. The easy path:

  1. Toolbar icon → Settings → Connect your AI. Pick your AI, then set Runs in to WSL. This allows AIs in WSL to connect.

  2. If Windows Firewall asks about webmcp-host, allow it on Private networks.

  3. Copy the config and paste it inside WSL. The Claude Code and Gemini commands look up the Windows address on each run, so they survive reboots. Config files hold the current address, so copy them again if the connection drops after a Windows restart.

The helper then listens on the vEthernet (WSL) adapter only, not your LAN. Alternatively, enable WSL mirrored networking (networkingMode=mirrored under [wsl2] in %UserProfile%\.wslconfig, then wsl --shutdown). After that, 127.0.0.1:8901 works from WSL as-is.

AI cursor

Every tool accepts an optional thought argument (up to 300 characters, for example "Opening the pricing page to compare plans"). The add-on shows it in a bubble next to an animated cursor on the page being driven.

  • Actions (act_*): the cursor glides to the target, then clicks with a ripple. Without a thought, it shows a default label such as "Clicking “Sign in”". Typed passwords are never echoed.

  • Tab and navigation tools: the thought appears on the tab they affect.

  • cursor_note: shows a note without doing anything else.

  • The cursor is drawn in a closed shadow root with pointer-events: none. It never blocks real input or appears in page_html or snapshot_ax, and it is hidden while screenshot captures.

  • It fades after 8 seconds idle. Turn it off in the popup (Show AI cursor) or in Settings (Show the AI cursor). It is on by default.

Cursor shapes are Bibata by ful1e5 (GPL-3.0).

Tools

Tool names match registerTool( in mcp-server/src/tools/*.ts. All tools except extension_status, wait_for_tab_event and cursor_note accept the optional thought described under AI cursor.

Tool

What it does

tabs_list

List all open tabs (id, url, title, active/pinned/audible state).

tabs_query

Find tabs by URL or title pattern and state flags.

active_tab

Get the active tab (id, url, title, window).

tab_create

Open a new tab, optionally at a URL.

tab_navigate

Navigate a tab to a URL (defaults to the active tab).

tab_close

Close one or more tabs. Needs confirm:true.

tab_duplicate

Duplicate a tab (defaults to the active tab).

tab_move

Move a tab to a new index, optionally to another window.

tab_pin

Pin a tab (defaults to the active tab).

tab_unpin

Unpin a tab (defaults to the active tab).

tab_mute

Mute or unmute a tab (defaults to the active tab).

window_list

List windows (id, focused, tab count).

window_create

Open a new window, optionally at a URL.

window_focus

Bring a window to the front.

window_close

Close a window and its tabs. Needs confirm:true.

nav_back

Go back in a tab's history (defaults to the active tab).

nav_forward

Go forward in a tab's history (defaults to the active tab).

nav_reload

Reload a tab (defaults to the active tab).

focus_tab

Activate a tab by id.

Tool

What it does

snapshot_ax

Accessibility snapshot with [ref=N] nodes for the act_* tools.

page_text

Visible text of the page.

page_html

Page HTML, optionally limited to a CSS selector subtree.

screenshot

Screenshot of the tab's visible area, returned as an image.

page_info

URL, title and load state.

Tool

What it does

act_click

Click an element by snapshot ref or CSS selector. Fails on disabled elements.

act_type

Type into a text field, optionally submitting it through its form or composer button, otherwise with Enter.

act_fill_form

Fill several fields in one call. Every field is checked first, so one bad field changes nothing.

act_select

Choose option(s) in a <select>: exact value first, then label.

act_hover

Send hover events for JS menus and tooltips. CSS :hover cannot be triggered.

act_scroll

Scroll the page or an element, by direction and pixels, or to top or bottom.

act_key

Press a key with optional modifiers, such as Enter or a + Ctrl.

act_wait

Wait for text or a selector to appear. timeoutMs defaults to 10000, max 60000.

act_find

Find text on the page, returning match locations and count.

Tool

What it does

bookmarks_search

Search bookmarks by title or URL.

bookmarks_create

Create a bookmark.

bookmarks_remove

Delete a bookmark by id. Needs confirm:true.

history_search

Search browsing history.

downloads_list

List recent downloads (filename, state, progress).

cookies_for_tab

Read the cookies a tab's page can see, from its container or private store, including partitioned ones. Read-only.

sessions_recently_closed

List recently closed tabs and windows that can be restored.

sessions_restore

Restore a recently closed tab or window by session id.

Tool

What it does

extension_status

Whether the add-on is connected, plus profile details.

wait_for_tab_event

Wait for an event such as tab.updated, tab.removed, tab.activated or download.done. Pass the result's seq as afterSeq to catch events between calls.

cursor_note

Show a note in the AI cursor bubble without acting.

Limits and safety

  • Local only by default. The helper binds 127.0.0.1. Every MCP request needs the 256-bit bearer token, compared in constant time. Rotate the token in Settings (New) and re-copy the config.

  • Browser checks. Requests with a foreign Host or Origin header get a 403, which blocks DNS rebinding and cross-site requests. The token is checked before the body is read.

  • Cancellation. Closing the HTTP request, or sending notifications/cancelled, stops the command in Firefox too.

  • Confirm guard. tab_close, window_close and bookmarks_remove refuse to run without confirm:true.

  • No password access. No tool reads saved logins or the Firefox password manager. cookies_for_tab is read-only.

  • Screenshots are viewport-only. screenshot captures the visible area of the tab, not the full scrollable page or the browser chrome.

  • Prompt injection is real. A web page can try to instruct the agent. Don't point it at anything you wouldn't let a stranger click through.

Troubleshooting

  • "Helper app not installed." The install step didn't run, or it registered somewhere this Firefox doesn't read. Run webmcp-host status to see where it registered, then press Retry in the popup.

    • Snap Firefox on Ubuntu reads ~/snap/firefox/common/.mozilla/native-messaging-hosts. The installer writes there too.

    • Flatpak Firefox can't run native helpers without extra sandbox overrides. Use the deb or tarball build instead. The installer warns when it finds one.

  • "Helper is too old" or "add-on is too old." Run the install command again, or update the add-on, as the popup says.

  • Worked before 0.3.4, "not installed" now. Everything was renamed from firefox-mcp. Install the new add-on and run the install command once. The installer removes the old registration. Delete the old firefox-mcp folder after closing Firefox.

  • "Port 8901 is already in use." Another app, or this add-on in a second Firefox profile, holds the port. In Settings, choose another port, save, then re-copy the MCP config.

  • 401 Unauthorized. The token changed. Re-copy the config.

  • Temporary add-on disappeared after a restart. Temporary add-ons unload when Firefox closes. Reload it from about:debugging, or install the signed add-on.

  • RESTRICTED_PAGE. Page-content tools (page_*, snapshot_ax, act_*) don't run on about:, chrome:, resource:, moz-extension:, view-source:, jar:, data: or blob: pages, or on addons.mozilla.org. Firefox doesn't allow scripting there.

  • REF_STALE or REF_NOT_FOUND. Refs from snapshot_ax belong to one page load. Pass the snapshot's generation with the refs. After the page changes or navigates, take a new snapshot.

  • PAYLOAD_TOO_LARGE. Firefox limits native messages to 1 MB. Ask for less text per call.

  • Helper logs. The helper writes to stderr, which Firefox shows in the Browser Console (Ctrl+Shift+J) with a [webmcp] prefix.

Development

npm install
npm run build && npm run typecheck
npm test                                 # everything below
node test/e2e.mjs                        # real background.js (mocked browser APIs) and real helper over native messaging and HTTP
MISSING=1 node test/e2e.mjs              # the "helper not installed" flow
node test/host.mjs                       # helper: reconfiguration, Host/Origin checks, size limits, cancellation, protocol mismatch
npx playwright install firefox && node test/content.mjs   # content scripts in real Firefox
node test/e2e.mjs dist/host/webmcp-host-linux-x64         # same, against a compiled helper
npx web-ext@8 lint -s extension

The add-on and helper share one version number (package.json, manifest.json, shared/src/version.ts). A separate wire PROTOCOL number changes only on breaking bridge changes. On connect the two sides exchange both numbers. A version mismatch gets a warning and keeps working. A protocol mismatch tells you which side needs updating.

Releasing

node scripts/set-version.mjs X.Y.Z   # bumps every version file
git commit -am "vX.Y.Z" && git tag vX.Y.Z && git push --follow-tags

Pushing a v* tag runs .github/workflows/release.yml. It checks that the versions match the tag, runs the test suite, builds the helper binaries with Bun, and publishes them with the installers, SHA256SUMS and the add-on zip as a GitHub release. npm run release does the same locally and needs Bun and an authenticated gh. The installers always fetch releases/latest, so the published installers pick up each new release automatically.

Single-file helper binaries are built with bun build --compile and are unsigned. Windows SmartScreen and macOS Gatekeeper will warn. On macOS, xattr -c <file> clears the quarantine flag.

Add-on packaging: bash scripts/pack-extension.sh writes webmcp-controller.zip, with manifest.json at the root. Upload it at addons.mozilla.org/developers. scripts/amo-sign.sh signs with the AMO API from the command line.

Building from source

The add-on has no build step. The JavaScript, CSS and HTML in extension/ ship unmodified: nothing is transpiled, bundled or minified. Packaging copies those files into a zip.

Requirements

  • Linux, macOS, or Windows with Git Bash or WSL

  • bash and python3 3.8 or newer (standard library only)

  • unzip is optional; the script lists the zip contents without it

Steps (tested with Python 3.14 on Linux)

git clone https://github.com/hamb1y/webmcp-controller.git
cd webmcp-controller
git checkout v0.4.0
bash scripts/pack-extension.sh      # writes webmcp-controller.zip

The zip contains the 20 files listed in scripts/pack-extension.sh, with manifest.json at its root.

Third-party assets. The Google Sans Flex font (extension/fonts/, SIL OFL 1.1) and the Bibata cursor license (extension/cursors/bibata/, GPL-3.0) ship with their license texts.

The native helper is a separate program that isn't part of the add-on zip. To build it too, install Node.js 22 (the version CI uses) and, for the single-file binaries, Bun. Then run:

npm ci                 # uses the lockfile
npm run build          # compiles shared/ and mcp-server/ with tsc
npm run build:host     # optional: single-file binaries in dist/host/ (needs Bun)

License

Mozilla Public License 2.0.

Related MCP Connectors

Related MCP Servers

  • A
    license
    C
    quality
    C
    maintenance
    Enables AI assistants to read and drive a real, logged-in Firefox browser, including tabs, cookies, history, and site interactions, all through the Model Context Protocol.
    52
    9 npm
    MIT
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to control a persistent local browser with live tabs, navigation, interaction, inspection, and state management through MCP.
    6
    -
  • A
    license
    B
    quality
    B
    maintenance
    Enables AI agents to drive a user-launched Firefox instance via its native Marionette protocol, providing precise DOM actuation like clicking, typing, uploading files, taking screenshots, and evaluating JavaScript.
    18
    12 npm
    1
    GPL 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables an MCP client to drive a real, already-logged-in Firefox session through a WebExtension rather than a WebDriver connection, so tabs, cookies and logins work as-is without relaunching the browser under remote-control flags. Exposes tools for tab listing and selection, navigation, accessibility-style snapshots, clicking, filling, typing, key presses, file uploads, screenshots and waiting, with per-origin permission grants and opt-in script evaluation.
    MIT