foxwire
Allows an MCP client to drive a real, logged-in Firefox browser via a WebExtension, providing tools for tab management, navigation, page snapshots, clicking, form filling, typing, screenshots, waiting, dialog handling, and optional script evaluation while preserving existing cookies and sessions.
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., "@foxwireopen my Gmail and screenshot the newest unread email"
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.
foxwire
foxwire lets an MCP client such as Claude Code drive your real, logged-in Firefox: your tabs, cookies and sessions,
in the browser you already use. It is a WebExtension, not a WebDriver session, so Firefox is started normally,
navigator.webdriver stays false, and nothing about the browser's configuration changes. Marionette-based routes
(such as @mozilla/firefox-devtools-mcp --connect-existing) need Firefox relaunched with remote-control flags. That
sets navigator.webdriver = true, shows the remote-control icon in the URL bar, and can trip bot checks on sites behind
Cloudflare and similar services; see docs/WHY-NOT-WEBDRIVER.md.
Status
Version 0.1.1. Requires Firefox 140 or later. Tested on Linux only. Single author, in daily use by that author; expect rough edges. Not listed on addons.mozilla.org: build it yourself and load it temporarily or sign your own copy.
Related MCP server: mcp-zen
How it works
MCP client ──stdio──▶ foxwire-mcp ──Unix socket──▶ foxwire-broker ◀──ws://127.0.0.1── Firefox extensionExtension (MV2, persistent background): dials out to the broker, runs each command with
browser.tabs.*, and injects short-lived scripts into pages only for the duration of a call.Broker: one long-lived host process per user. It owns the loopback WebSocket and multiplexes any number of MCP clients onto the one extension connection. The MCP server spawns it on demand.
MCP server: one stdio process per client session, holding only that session's selected tab.
The full design, including the wire protocol, snapshot/uid model and detectability review, is in docs/DESIGN.md.
Security model
The broker binds
127.0.0.1only. The extension connects out to it; nothing listens inside Firefox.Pairing uses a 32-byte secret generated by the broker on first run and stored with mode 0600. You paste it into the extension's options page once. Each connection proves knowledge of it with an HMAC challenge-response; the secret is never sent over the socket.
The broker accepts WebSocket connections only with a
moz-extension://Origin, so ordinary web pages cannot connect to it.Page access requires a per-origin host permission that you grant by clicking, either in the options page or when foxwire asks just in time. Without a grant, tools fail with
NO_GRANT; they never widen access by themselves. Tab titles and URLs are visible without grants, with query strings and fragments stripped for ungranted tabs.evaluate_script(arbitrary JavaScript) is off by default and must be switched on in the options page.No content scripts are declared, no
web_accessible_resourcesexist, and injected code runs in the extension's isolated world and cleans up after each call.
What this does not protect against: any process running as your user that can reach the broker's Unix socket can
drive every site you have granted, with your logged-in sessions. That includes every MCP client you register
foxwire with, and whatever those clients' models decide to do. Grant only the sites you want driven, and keep
evaluate_script off unless you need it. Actions use synthetic DOM events, so interactive bot checks such as
"I am human" checkboxes still need your own click.
Install from source
Requires Node 22 or later and Firefox 140 or later.
Build and test:
npm install npm run build npm testRun the broker once in a terminal to generate the pairing secret. It prints the secret once, then keeps running; stop it with Ctrl-C afterwards (the MCP server starts it again on demand).
npm run brokerThe secret is stored in
~/.config/foxwire/secret(or$XDG_CONFIG_HOME/foxwire/secret).Load the extension, either:
Temporarily:
about:debugging#/runtime/this-firefox→ Load Temporary Add-on →extension/manifest.json. It is dropped when Firefox restarts.Permanently, by self-signing an unlisted build on addons.mozilla.org with your own API keys:
scripts/sign.sh(needsAMO_JWT_ISSUERandAMO_JWT_SECRETin the environment). First change the gecko id inextension/manifest.json:foxwire@vidr.ccis tied to the author's AMO account and cannot be signed by anyone else. See docs/RELEASE.md.
Open the extension's options page (
about:addons→ foxwire → Preferences), paste the secret, Save, and grant the sites you want driven. "Grant all sites" is available but not required, except for screenshots.Register the MCP server. For Claude Code, at user scope:
scripts/install-mcp.sh # equivalent to: claude mcp add -s user foxwire -- node /path/to/foxwire/mcp/dist/server.jsOther MCP clients: configure a stdio server with the command
node /path/to/foxwire/mcp/dist/server.js.Check with the
statustool: it should reportpaired: yesand list your grants.
Environment variables FOXWIRE_PORT, FOXWIRE_SOCKET and FOXWIRE_CONFIG_DIR override the defaults.
Firefox as a Flatpak
The Firefox Flatpak cannot read arbitrary host directories, so "Load Temporary Add-on" may not see your checkout.
Copy the built extension somewhere it can read, such as ~/Downloads, and load it from there. Loopback networking is
shared with the host, so the broker connection works without native messaging or Flatpak overrides.
docs/RELEASE.md has the exact commands.
Tools
Tool | What it does |
| Health check: pairing, extension and Firefox versions, grants, option switches, selected tab |
| List open tabs, optionally filtered by a title/URL substring (no site grant needed) |
| Choose the tab later calls act on, by index, tab id, or URL/title substring |
| Open a URL in a new tab and select it |
| Load a URL in the selected tab |
| Go back or forward |
| Close a tab |
| Accessibility-style text tree with a |
| The page's visible text, or the text of one uid's subtree or of a CSS selector's matches in any frame |
| Click an element (optionally double-click) |
| Hover an element |
| Set a field's value, or check/uncheck a checkbox or radio |
| Type text key by key into an element or the focused element, optionally pressing Enter after |
| Press a key with optional modifiers |
| Choose options of a |
| Attach local files to a file input |
| PNG of the viewport or full page |
| PNG of one element |
| Wait for text, a selector or a uid to appear, or for the page's or an element's text to change, returning what was added |
| Pause without touching the browser (up to 60 s) |
| Pre-answer the next |
| Run a JavaScript function in the page (off by default) |
Names and semantics follow @mozilla/firefox-devtools-mcp where they overlap. Uids look like 37kqx in the top
frame and f2_37kqx inside iframe f2; the letters are a per-document tag, so a uid from before a navigation or
extension reload fails with STALE_UID instead of hitting the wrong element. Actions that change a page (click,
fill, type, press, select, upload) end with an after: note describing the visible change, or saying there was none
within a moment. Every call has a timeout and fails with a named error code.
Intent bubble and just-in-time grants
Tools that act on a tab accept an optional intent: a short note of what the model is doing and why. foxwire shows
it as a small thought bubble on the page, drawn with a user stylesheet on the root element's pseudo-elements, so no
DOM nodes or page scripts are added. It can be switched off in the options page. The toolbar popup lists recent
calls with their intents and results.
When a tool needs a site you have not granted, the toolbar button shows an orange ? and the popup asks you to Allow
or Deny that one origin. Allow opens Firefox's own permission prompt for exactly that origin, and the waiting call
then continues. A Deny is remembered for 10 minutes. All-sites access is never requested this way.
Limitations
Firefox only, Manifest V2.
Input is synthetic DOM events (
isTrustedisfalse). Most sites accept them; some editors needtype_textrather thanfill_by_uid, and interactive bot checks need a human click. Synthetic clicks cannot open pop-ups: foxwire opens atarget=_blanklink's address itself, but awindow.openfrom a click handler stays blocked unless pop-ups are allowed for that site.Typed text arrives as trusted
beforeinput/inputevents, but key events are untrusted. A widget that only reacts to trusted keystrokes (some bank address lookups) will take the text and not open its suggestions.Screenshots need the all-sites grant, because
tabs.captureTabrequires<all_urls>.No console or network capture, downloads, cookies, viewport resizing or PDF export.
File uploads are capped at 15 MB in total;
wait_forwaits at most 60 seconds.While the thought bubble is visible, a page could detect it by reading the computed style of the root element's
::before/::after. Turn the bubble off to remove even that.
Development
Command | Purpose |
| esbuild bundles for extension, broker and MCP server |
| Rebuild on change |
|
|
|
|
| Unit tests with |
| Launch a throwaway Firefox profile with the extension |
The codebase is meant to stay small enough to read in a sitting, with no frameworks and no runtime dependencies
beyond @modelcontextprotocol/sdk and ws. CLAUDE.md is the brief for contributors and coding agents;
test/E2E.md is the manual acceptance checklist. foxwire was built with Claude Code.
Licence
MIT, see LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Stealth web browser for agents: search, fetch, click, download and type in persistent MCP sessions.
Browserless MCP — wraps the Browserless headless-Chromium REST API (browserless.io)
Related MCP Servers
- AlicenseAqualityCmaintenanceDrive Firefox-based browsers (Floorp, LibreWolf, Zen, Waterfox, Mullvad, Firefox) from any MCP client — read pages, screenshot, click, fill forms and manage tabs in your real session, over Marionette/WebDriver. OS input & JS eval locked by default.4158 npm2MIT
- AlicenseNot gradedqualityBmaintenanceLets MCP clients control a live Zen/Firefox browser to navigate, click, fill forms, screenshot, and execute JavaScript through a persistent server and browser extension.MIT
- AlicenseNot gradedqualityAmaintenanceEnables MCP-capable CLIs to operate real, already-logged-in Firefox tabs via a WebExtension and native messaging, without simulated input. Supports navigation, clicking, typing, reading, screenshots, and console/network capture with policy gating and frame awareness.1MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to drive your already-open Chrome browser like a human, using 45 tools for navigation, perception, capture, and trusted input that pages receive as genuinely user-generated.MIT