Skip to main content
Glama

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 extension
  • Extension (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.1 only. 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_resources exist, 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.

  1. Build and test:

    npm install
    npm run build
    npm test
  2. Run 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 broker

    The secret is stored in ~/.config/foxwire/secret (or $XDG_CONFIG_HOME/foxwire/secret).

  3. 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 (needs AMO_JWT_ISSUER and AMO_JWT_SECRET in the environment). First change the gecko id in extension/manifest.json: foxwire@vidr.cc is tied to the author's AMO account and cannot be signed by anyone else. See docs/RELEASE.md.

  4. 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.

  5. 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.js

    Other MCP clients: configure a stdio server with the command node /path/to/foxwire/mcp/dist/server.js.

  6. Check with the status tool: it should report paired: yes and 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

status

Health check: pairing, extension and Firefox versions, grants, option switches, selected tab

list_pages

List open tabs, optionally filtered by a title/URL substring (no site grant needed)

select_page

Choose the tab later calls act on, by index, tab id, or URL/title substring

new_page

Open a URL in a new tab and select it

navigate_page

Load a URL in the selected tab

navigate_history

Go back or forward

close_page

Close a tab

take_snapshot

Accessibility-style text tree with a uid on each interactable element; frames are merged in

get_page_text

The page's visible text, or the text of one uid's subtree or of a CSS selector's matches in any frame

click_by_uid

Click an element (optionally double-click)

hover_by_uid

Hover an element

fill_by_uid

Set a field's value, or check/uncheck a checkbox or radio

type_text

Type text key by key into an element or the focused element, optionally pressing Enter after

press_key

Press a key with optional modifiers

select_option

Choose options of a <select> by value or label

upload_file_by_uid

Attach local files to a file input

screenshot_page

PNG of the viewport or full page

screenshot_by_uid

PNG of one element

wait_for

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

sleep

Pause without touching the browser (up to 60 s)

handle_dialog

Pre-answer the next alert/confirm/prompt

evaluate_script

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 (isTrusted is false). Most sites accept them; some editors need type_text rather than fill_by_uid, and interactive bot checks need a human click. Synthetic clicks cannot open pop-ups: foxwire opens a target=_blank link's address itself, but a window.open from a click handler stays blocked unless pop-ups are allowed for that site.

  • Typed text arrives as trusted beforeinput/input events, 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.captureTab requires <all_urls>.

  • No console or network capture, downloads, cookies, viewport resizing or PDF export.

  • File uploads are capped at 15 MB in total; wait_for waits 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

npm run build

esbuild bundles for extension, broker and MCP server

npm run watch

Rebuild on change

npm run typecheck

tsc --noEmit

npm run lint

web-ext lint on the extension

npm test

Unit tests with node --test

npm run run:scratch

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Drive 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.
    41
    58 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Lets 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
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables 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.
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables 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