firefox-mcp
Drives a live Firefox browser (the user's current profile with all its tabs and windows) through a privileged WebExtension plus native-messaging helper. Exposes tools for tab and window management (listing, querying, creating, navigating, closing, duplicating, moving, pinning, muting), plus page-level actions such as clicking, typing, reading page HTML/accessibility snapshots, taking screenshots, and working with bookmarks, history, and cookies, with an on-page AI cursor overlay that shows what the agent is doing.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@firefox-mcpopen a new tab and search Amazon for a 4K monitor under $300"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 withAuthorization: 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 | shThe 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 |
|
|
macOS |
|
|
Linux |
|
|
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:
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.
Windows Firewall asks about
webmcp-host→ tick Private networks → Allow access.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 athoughtit 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 intopage_html/snapshot_ax, and it is hidden whilescreenshotcaptures.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 wirePROTOCOLnumber 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_bridgeregistration; delete the oldfirefox-mcpfolder 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 extensionLoad 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-tagsThe 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 |
| List all open tabs (id, url, title, active/pinned/audible state). |
| Find tabs by URL/title pattern and state flags. |
| Get the currently active tab (id, url, title, window). |
| Open a new tab, optionally with a URL. |
| Navigate a tab to a URL (defaults to the active tab). |
| Close one or more tabs (destructive: needs |
| Duplicate a tab (defaults to the active tab). |
| Move a tab to a new index, optionally to another window. |
| Pin a tab (defaults to the active tab). |
| Unpin a tab (defaults to the active tab). |
| Mute or unmute a tab (defaults to the active tab). |
| List open Firefox windows (id, focused, tab count). |
| Open a new window, optionally with a URL. |
| Bring a window to the front. |
| Close a window and all its tabs (destructive: needs |
| Go back in a tab's history (defaults to the active tab). |
| Go forward in a tab's history (defaults to the active tab). |
| Reload a tab (defaults to the active tab). |
| Activate (focus) a tab by id. |
Understand (understand.ts, 5)
Tool | What it does |
| Accessibility snapshot of the page for grounding |
| Extract the visible text of the page. |
| Extract page HTML, optionally limited to a CSS selector subtree. |
| Capture a screenshot of the tab's visible area (returned as an image). |
| Basic page metadata: URL, title, load state. |
Act (act.ts, 9)
Tool | What it does |
| Click an element by snapshot ref or CSS selector. Fails on disabled elements. |
| Type into a text field (optional submit via its form or composer's own button, else Enter). |
| Fill several fields in one call; every field is checked first, so a bad one changes nothing. |
| Select option(s) in a |
| Send hover events (JS menus and tooltips; CSS |
| Scroll the page or an element (direction/pixels, or top/bottom). |
| Press a key, optionally with modifiers (e.g. |
| Wait for text or a selector to appear (poll, |
| Find text on the page (returns match locations/count). |
Browser data (browser.ts, 8)
Tool | What it does |
| Search bookmarks by title/URL query. |
| Create a bookmark. |
| Delete a bookmark by id (destructive: needs |
| Search browsing history. |
| List recent downloads (filename, state, progress). |
| Read cookies visible to a tab's page, from its container/private store, including ones partitioned under that site (read-only). |
| List recently closed tabs/windows available for restore. |
| Restore a recently closed tab/window by session id. |
Meta (tools/index.ts, 3)
Tool | What it does |
| Check whether the bridge extension is connected, plus profile details. |
| Wait for an extension-pushed event ( |
| 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
statusto 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 inmanifest.json).RESTRICTED_PAGE. Content-script ops (page.snapshot/text/html, allact.*) are blocked onabout:*,chrome:*,resource:*,moz-extension:*,view-source:*,jar:,data:/blob:pages and onaddons.mozilla.org(Firefox forbids scripting there).act_waitvs bridge timeout.act_waithas its owntimeoutMs(default 10000, max 60000); the helper extends its 30s watchdog totimeoutMs + 15sfor that call.REF_STALE/REF_NOT_FOUND. Refs fromsnapshot_axbelong to one page load and are never reused. Pass the snapshot'sgenerationwith 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
HostorOriginheader get 403 (DNS rebinding / cross-site protection); the token is checked before the body is parsed.Cancellation. Closing an HTTP request or sending
notifications/cancelledstops the command in Firefox too.Confirm guard.
tab_close,window_close,bookmarks_removerefuse without explicitconfirm:true.No password-store access. There is no tool for saved logins / the Firefox password manager.
cookies_for_tabis read-only.Screenshots are viewport-only.
screenshotcaptures 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
.xpivia 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):
bash scripts/pack-extension.sh→webmcp-controller.zip(manifest at zip root,web-ext lintclean).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 isunlisted, setAMO_CHANNEL=listedfor a public listing).Fill in the submission: pick On your own (unlisted) vs On this site (listed), declare the
data_collection_permissionsalready inmanifest.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 inextension/icons/), description, and support info.
This server cannot be deployed
Maintenance
Related MCP Connectors
Live browser debugging for AI assistants — DOM, console, network via MCP.
Stealth web browser for agents: search, fetch, click, download and type in persistent MCP sessions.
Hyperbrowser MCP — wraps the Hyperbrowser AI-agent browsing API
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Related MCP Servers
- AlicenseCqualityCmaintenanceEnables 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.5215 npmMIT
- FlicenseNot gradedqualityAmaintenanceEnables AI agents to control a persistent local browser with live tabs, navigation, interaction, inspection, and state management through MCP.6-
- AlicenseBqualityBmaintenanceEnables 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.1827 npm1GPL 2.0
- AlicenseNot gradedqualityBmaintenanceEnables 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