Skip to main content
Glama

WebMCP Controller — full-control Firefox MCP

Live Firefox (your current profile, all tabs) driven by any MCP harness (Claude, opencode, Cursor, anything speaking Streamable HTTP). There is no server to start: Firefox itself launches a small helper app (a native messaging host) when the add-on loads, and the helper serves MCP.

While the model works, a glowing AI cursor glides to whatever it is clicking or typing into, with a small bubble saying what it is doing (toggle it in the popup).

Install: add the add-on, paste one command (below) to install the helper, click Copy MCP config in the add-on popup, paste that into your harness.

Architecture

 Firefox                                   helper (native messaging host)        harness
+------------------------+  stdin/stdout  +-------------------------------+     +-----------------+
| WebExtension           | <------------> | webmcp-host              |     | Claude / opencode|
| background.js          |  (Firefox      |  started & stopped by Firefox |     | / any MCP client |
|  connectNative(...)    |   launches it) |  127.0.0.1:8901/mcp  <---------------- POST /mcp       |
+------------------------+                +-------------------------------+     +-----------------+
  • The add-on owns the settings: it generates the token on first run and pushes {token, port, bind} to the helper. Defaults: port 8901, bind 127.0.0.1.

  • Harness → helper: MCP Streamable HTTP POST /mcp, authenticated with Authorization: Bearer <token>. Unauthenticated: GET /health, GET /.

  • The helper lives exactly as long as the add-on is running — close Firefox and the MCP endpoint goes away.

Why an extension at all: Firefox's CDP/remote-debugging surface is incomplete, while a privileged WebExtension gets every tab, window, bookmark, history entry and cookie of the profile you actually use. Why a native host: extensions cannot listen on ports, and native messaging is the one sanctioned way for an add-on to talk to a local program — no manual server, no extra login.

Related MCP server: hronaut

Quickstart

1. Install the helper (once per computer)

The add-on's popup shows this for your system with a Copy command button.

Windows — open PowerShell or Command Prompt and paste:

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

macOS / Linux / WSL — open Terminal and paste (inside WSL it installs the Windows helper for you):

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

The script picks the right binary (x64/arm64), checks its SHA-256 and runs install. Prefer clicking? Grab webmcp-host-<os>-<arch> from the releases page — on Windows double-click the .exe; elsewhere chmod +x it and run it with install.

Already installed? Run the same command again to update: Firefox switches to the new helper by itself within a few seconds, no restart needed.

install copies the binary to a per-user folder and registers it with Firefox (no admin rights needed). The downloaded file can be deleted afterwards.

OS

Binary 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/ (+ snap path)

Other commands: status, uninstall, version, help. From a source checkout: npm install && npm run install-host (uses Node instead of the bundled binary).

2. Load the add-on

Development: about:debugging#/runtime/this-firefox → Load Temporary Add-on… → pick extension/manifest.json. Permanent: see Publishing below.

3. Connect your harness

Toolbar icon → Copy MCP config (green dot = ready). The settings page (toolbar icon → Settings) also has a Claude Code command and an opencode snippet. The copied config looks like:

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

Claude Code: claude mcp add --transport http firefox http://127.0.0.1:8901/mcp --header "Authorization: Bearer <token>". Codex (~/.codex/config.toml): [mcp_servers.firefox] with url = "http://127.0.0.1:8901/mcp" and 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":"…/mcp","headers":{"Authorization":"Bearer <token>"}}}}. Clients that only speak stdio: npx -y mcp-remote http://127.0.0.1:8901/mcp --header "Authorization: Bearer <token>".

Check from a shell:

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

Harness inside WSL, Firefox on Windows

WSL2 (default NAT networking) can't reach Windows' 127.0.0.1. Easiest:

  1. Add-on toolbar icon → Settings → Connect your AI → Your AI: pick yours (Claude Code, Codex, Gemini CLI, opencode, or Other) → Runs in: WSL. That turns on Let AIs in WSL connect to this Firefox for you.

  2. Windows Firewall asks about webmcp-host → tick Private networks → Allow access.

  3. Copy, then paste it inside WSL. The Claude Code and Gemini commands look up the Windows address with $(ip route show default | awk '{print $3}'), so they survive reboots; config files (Codex, opencode, JSON) hold the current address, so copy them again if the connection stops after a Windows restart.

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

AI cursor

Every tool accepts an optional thought argument (≤300 chars, e.g. "Opening the pricing page to compare plans"). The add-on shows it in a bubble beside an animated cursor on the page being driven:

  • act_* tools: the cursor glides to the target element first, then clicks with a ripple. Without a thought it shows a default label ("Clicking “Sign in”", "Typing a password", …). Typed passwords are never echoed.

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

  • cursor_note {note}: just say something, no action.

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

  • It fades after 8s idle. Toggle: popup → Show AI cursor, or Settings → AI cursor. On by default.

The server's MCP instructions tell models to pass thought, so most harnesses do it without prompting.

Versions and updates

  • Add-on and helper share one version (package.json, manifest.json, shared/src/version.ts), plus a separate wire PROTOCOL number that only changes on breaking bridge changes.

  • On connect they exchange both. Same protocol, different version → the popup suggests updating the helper but keeps working. Different protocol → the popup says which side is too old and shows the fix (the install command, or "update the add-on").

  • Re-running the installer while Firefox is open replaces the helper in place; the running helper notices, exits, and the add-on reconnects to the new one.

  • 0.3.4 renamed everything from firefox-mcp to WebMCP Controller (add-on ID, native host name, helper binary, install folder). Older add-ons and helpers don't talk to the new ones: install the new add-on and run the install command once. The new installer removes the old firefox_mcp_bridge registration; delete the old firefox-mcp folder once Firefox is closed.

Development

npm install
npm run build && npm run typecheck
node test/e2e.mjs                 # real background.js (mocked browser APIs) + 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   # real content script in real Firefox
npm test                          # all of the above
node test/e2e.mjs dist/host/webmcp-host-linux-x64   # same, against a compiled helper
npx web-ext@8 lint -s extension

Load extension/manifest.json as a temporary add-on and npm run install-host to point Firefox at your checkout.

Releasing

node scripts/set-version.mjs 0.3.1   # bumps package.json ×3, manifest.json, shared/src/version.ts
git commit -am "v0.3.1" && git tag v0.3.1 && git push --follow-tags

The tag triggers .github/workflows/release.yml: it checks the versions match the tag, runs the tests, builds every helper with bun and publishes the binaries, install.sh/install.ps1, SHA256SUMS and the add-on zip as a GitHub release. Locally the same thing is npm run release (needs bun and an authenticated gh); npm run build:host alone just builds (TARGETS="linux-x64" for one). Installers use releases/latest/download/…, so a new release is picked up without changing the add-on.

Single-file executables via bun build --compile (60–85 MB, no runtime needed). They are unsigned: expect SmartScreen on Windows and Gatekeeper on macOS (xattr -c <file> clears the quarantine flag; if macOS still refuses, codesign -s - -f <file> gives it an ad-hoc signature).

Tools

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

Inventory / manage (tabs.ts, 19)

Tool

What it does

tabs_list

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

tabs_query

Find tabs by URL/title pattern and state flags.

active_tab

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

tab_create

Open a new tab, optionally with a URL.

tab_navigate

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

tab_close

Close one or more tabs (destructive: 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 open Firefox windows (id, focused, tab count).

window_create

Open a new window, optionally with a URL.

window_focus

Bring a window to the front.

window_close

Close a window and all its tabs (destructive: 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 (focus) a tab by id.

Understand (understand.ts, 5)

Tool

What it does

snapshot_ax

Accessibility snapshot of the page for grounding act_* refs ([ref=N] nodes).

page_text

Extract the visible text of the page.

page_html

Extract page HTML, optionally limited to a CSS selector subtree.

screenshot

Capture a screenshot of the tab's visible area (returned as an image).

page_info

Basic page metadata: URL, title, load state.

Act (act.ts, 9)

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 (optional submit via its form or composer's own button, else Enter).

act_fill_form

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

act_select

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

act_hover

Send hover events (JS menus and tooltips; CSS :hover can't be triggered).

act_scroll

Scroll the page or an element (direction/pixels, or top/bottom).

act_key

Press a key, optionally with modifiers (e.g. Enter, a + Ctrl).

act_wait

Wait for text or a selector to appear (poll, timeoutMs default 10000 / max 60000).

act_find

Find text on the page (returns match locations/count).

Browser data (browser.ts, 8)

Tool

What it does

bookmarks_search

Search bookmarks by title/URL query.

bookmarks_create

Create a bookmark.

bookmarks_remove

Delete a bookmark by id (destructive: needs confirm:true).

history_search

Search browsing history.

downloads_list

List recent downloads (filename, state, progress).

cookies_for_tab

Read cookies visible to a tab's page, from its container/private store, including ones partitioned under that site (read-only).

sessions_recently_closed

List recently closed tabs/windows available for restore.

sessions_restore

Restore a recently closed tab/window by session id.

Meta (tools/index.ts, 3)

Tool

What it does

extension_status

Check whether the bridge extension is connected, plus profile details.

wait_for_tab_event

Wait for an extension-pushed event (tab.updated/removed/activated, download.done); pass a result's seq as afterSeq to catch events between calls.

cursor_note

Show a note in the AI cursor bubble without doing anything.

Troubleshooting

  • Popup says "Helper app not installed". The install step didn't run or wrote to a different place than this Firefox reads. Run the binary with status to see where it registered. Snap Firefox on Ubuntu reads ~/snap/firefox/common/.mozilla/native-messaging-hosts (the installer writes there too); Flatpak Firefox cannot run native hosts without extra sandbox overrides — use the deb/tarball build (the installer warns when it sees one). Then press Retry.

  • Popup says the helper is too old / the 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" after. Everything was renamed; run the install command once more.

  • "Port 8901 is already in use". Another app, or this add-on in a second Firefox profile, holds it. Settings → Port → pick another → Save, then re-copy the MCP config.

  • 401 unauthorized. The token changed (Settings → New) — re-copy the config.

  • Temp add-on gone after restart. Temporary add-ons unload when Firefox closes — reload via about:debugging, or install a signed .xpi. A temporary add-on keeps its token only while its ID stays the same (it does — the ID is fixed in manifest.json).

  • RESTRICTED_PAGE. Content-script ops (page.snapshot/text/html, all act.*) are blocked on about:*, chrome:*, resource:*, moz-extension:*, view-source:*, jar:, data:/blob: pages and on addons.mozilla.org (Firefox forbids scripting there).

  • act_wait vs bridge timeout. act_wait has its own timeoutMs (default 10000, max 60000); the helper extends its 30s watchdog to timeoutMs + 15s for that call.

  • REF_STALE / REF_NOT_FOUND. Refs from snapshot_ax belong to one page load and are never reused. Pass the snapshot's generation with refs; after the page changes or navigates, take a new snapshot.

  • PAYLOAD_TOO_LARGE. Firefox caps native messages at 1 MB; send less text per call.

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

Limits & 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.

  • Browser checks. Requests with a foreign Host or Origin header get 403 (DNS rebinding / cross-site protection); the token is checked before the body is parsed.

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

  • Confirm guard. tab_close, window_close, bookmarks_remove refuse without explicit confirm:true.

  • No password-store access. There is no tool for saved logins / the Firefox password manager. cookies_for_tab is read-only.

  • Screenshots are viewport-only. screenshot captures the tab's visible area (page.shot), not the full scrollable page or OS chrome.

Publishing the extension (AMO)

Two lanes, same upload flow at addons.mozilla.org/developers:

  • Unlisted (fast, self-distribution). Automated checks only, signed in minutes. Install the resulting .xpi via Add-ons Manager → gear icon → Install Add-on From File. No public listing, no manual review. This is the right lane for personal use. AMO only signs the add-on — the helper binaries are shipped separately (e.g. GitHub releases).

  • Listed (public store page). Full human review — expect days to weeks, and scrutiny proportional to permissions. This extension requests tabs, bookmarks, history, cookies, and <all_urls> content scripting, i.e. it can read and drive every page. A public listing will need a convincing justification: why full control is the product (AI browsing agent), why the data stays local (native helper on the same machine, token-gated), plus a privacy policy. Plan on review iterations.

Steps (both lanes):

  1. bash scripts/pack-extension.sh → webmcp-controller.zip (manifest at zip root, web-ext lint clean).

  2. Either upload the zip at addons.mozilla.org/developers → Submit a New Add-on, or sign from the CLI: AMO_JWT_ISSUER=… AMO_JWT_SECRET=… bash scripts/amo-sign.sh (get API keys at AMO API keys; default channel is unlisted, set AMO_CHANNEL=listed for a public listing).

  3. Fill in the submission: pick On your own (unlisted) vs On this site (listed), declare the data_collection_permissions already in manifest.json (browsingActivity, websiteContent, websiteActivity, bookmarksInfo — required because page content, URLs, interactions, and bookmarks flow to the local helper), and for listed also add icons (shipped in extension/icons/), description, and support info.

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
    15 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
    27 npm
    1
    GPL 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to control live Zen Browser and Firefox tabs via MCP, performing actions like navigation, clicking, typing, scrolling, and running JavaScript through a local WebSocket bridge.
    MIT