Skip to main content
Glama
Agent Bridge for Firefox

Agent Bridge for Firefox

by Jetemple

Agent Bridge for Firefox

Lets coding agents and MCP clients drive the Firefox you're already signed into, in background tabs, without touching the tab you're looking at.

Two ways in, sharing one local bridge and one extension:

  • ff CLI for any agent with a shell (Claude Code, Codex, Gemini CLI). No MCP config.

  • MCP server: stdio for Claude Desktop, Streamable HTTP at /mcp for Cowork and any HTTP MCP client.

Claude Desktop ──stdio/MCP──▶ bridge (:3002/:3003) ◀──ws── Firefox extension
Cowork ─────────HTTP /mcp───▶   │
Claude Code ──── ff … ──────────┤  /health + /dispatch (loopback, token)
codex / gemini ─ ff … ──────────┘

Trust: anything that can run ff or reach the MCP endpoint can act as you on every site you're signed into, including running JavaScript in your pages. Only connect agents you trust. Everything binds to localhost by default; see Security.

Requirements

  • Firefox 142 or newer

  • Node.js 20 or newer

  • Tested on macOS and Linux. --cowork setup is macOS-only; Windows is untested.

Related MCP server: firefox-bridge

Install

git clone https://github.com/Jetemple/firefox-agent-bridge.git
cd firefox-agent-bridge
npm run setup                # deps, and `ff` on your PATH

npm run setup -- --desktop also registers the MCP server with Claude Desktop; --cowork does that plus LAN exposure for Cowork (see MCP clients).

Then load the extension: Firefox → about:debugging#/runtime/this-firefox → Load Temporary Add-on… → firefox-extension/manifest.json. A temporary add-on goes away when Firefox restarts; see Permanent install. Try it:

ff status            # bridge + extension state
ff tabs              # list your tabs
ff nav example.com   # open a page in a background tab
ff text              # read it

The bridge starts on demand (log: ~/.firefox-mcp/bridge.log); ff start / ff stop control it by hand.

Using it from a coding agent

An agent working in this repo (to set it up for you or change it) should read AGENTS.md. For day-to-day use, drop this in your CLAUDE.md / AGENTS.md:

## Firefox
You can drive my real, logged-in Firefox with the `ff` CLI (run `ff --help` once).
Read: `ff tabs`, `ff text`, `ff links`, `ff find <text>`, `ff screenshot -o x.png`
Act:  `ff nav <url>`, `ff click <visible text>`, `ff type <field label> <text>`,
      `ff key Enter`, `ff scroll --dy 600`, `ff js <code>`
Targeting is by visible text (add `--css <sel>` to scope, `--at X,Y` for raw
coordinates). `navigate`/`click` are fire-and-forget, so verify with `ff text`.
Commands run in your own background tab (🤖), never the one I'm viewing; use
`--here` only when I ask about the page I'm on.

The agent tab

ff never switches the tab you're looking at or raises a Firefox window. ff nav <url> opens a background tab the first time (stamped 🤖 in the tab strip) and reuses it after that; ff text, ff click, ff screenshot and the rest act on it by default. --here targets the tab you're viewing instead, and ff tab activate brings the agent tab into view when you want to watch. If you close the agent tab, commands error until the next ff nav rather than falling through to your tab.

Some extensions reopen a tab under a new id as it loads; container extensions do this to sites they assign to a container. ff follows the swap, and ff open/ff nav report the id the page ended up in. When you're done, ff tab close --mine closes every tab the agent opened except the one you're viewing (--dry-run lists them first). It only knows about tabs opened since the extension last started, and it covers every agent's tabs, not just yours.

Agents forget, so the bridge also sweeps: every 5 minutes it closes tabs an agent opened (or a session owns) that no command has touched for 2 hours. It never closes the tab you're viewing, a pinned tab, or the reused 🤖 tab, and it drops session records whose tab is gone. FIREFOX_BRIDGE_AGENT_TAB_IDLE_MS changes the cutoff; 0 turns the sweep off.

Containers

ff containers lists your Firefox containers. ff open <url> --container Gmail opens the page in one, matched by name (case-insensitive) or by its firefox-container-N id; an unknown name fails without opening anything. The reply names the container the tab actually landed in, since an "always open in" rule can move it. ff tabs marks container tabs {Gmail}. An open tab can't change containers, so to keep working in one, name a session: ff open <url> --container Gmail --as mail, then ff -s mail <command>.

Tab groups

Firefox's native tab groups. ff open <url> --group "TV hunt" opens the page straight into the group with that title (case-insensitive), creating it if there's none; add --color to set its color (grey, blue, red, yellow, green, pink, purple, cyan, orange). ff group <name> [TAB_ID...] moves open tabs in (no ids: --tab N, --here, -s NAME, or the agent tab), --collapse/--expand folds the group, and ff ungroup [TAB_ID...] takes tabs out. ff groups lists them, and ff tabs marks grouped tabs ‹TV hunt›. Grouping never changes which tab you're viewing. Pinned tabs can't join a group.

Sessions & profiles

Multiple agents (or one agent juggling tabs) can each pin a named session to a tab, so nothing ever depends on which tab you happen to be looking at:

ff open https://docs.example --as research   # background tab, never steals focus
ff -s research text                          # every command can target the session
ff -s research tab close                     # closes the tab and the session

Sessions fail safe: if Firefox restarted or the tab is gone you get SESSION_STALE, never a command silently running against the wrong tab.

Run several Firefox profiles at once and name each one (once, per profile):

ff profiles name work        # with one profile connected
ff --profile work nav https://mail.example
ff tabs                      # more than one profile? tabs come grouped

With one profile connected, nothing changes: name flags are only needed when two Firefoxes are up.

CLI reference

Run ff --help for the one-screen version. Highlights:

ff status / start / stop / ping

bridge lifecycle & health

ff tabs, ff tab close|activate [--tab N], ff tab close --mine [--dry-run]

tab management

ff nav <url>, ff reload [--hard], ff back, ff forward

navigation

ff text, ff links, ff tables, ff find <text>

reading pages

ff screenshot [-o f.png] [--jpeg]

capture (writes a file, prints the path)

ff click|hover|dblclick|rclick <query> [--css SEL] | --at X,Y

pointer (query = visible text)

ff type <query> <text> [--clear], ff type <text> --focused

typing

ff key <key>, ff scroll --dy N | --into <css>, ff drag --from X,Y --to X,Y

input

ff select <css> <val> [--by-text], ff check|uncheck <css>

forms

ff js <code>

run JavaScript in the page

Global flags: --tab N, --json, --timeout MS, --no-autostart. Exit codes: 0 ok · 1 tool/bridge error · 2 usage.

MCP clients

  • Claude Desktop: npm run setup -- --desktop registers the stdio server; restart Desktop afterward.

  • Cowork: npm run setup -- --cowork, then upload the generated firefox-bridge.plugin (Settings → Upload local plugin). This exposes the bridge on your LAN behind a token; see Security.

  • Any HTTP MCP client: point it at http://127.0.0.1:3003/mcp. Multiple concurrent sessions are supported.

The first bridge instance owns both ports. Extra instances (a second MCP client, the CLI's auto-start) find it through /health and forward through it. If the owner quits, a follower takes over in-process and the extension reconnects.

Security

  • Localhost by default. The HTTP endpoint binds 127.0.0.1:3003; nothing on your network can reach it.

  • LAN exposure always has a token. --cowork generates a 256-bit token (.firefox-bridge-token, gitignored), binds 0.0.0.0, requires the token on every request (Authorization: Bearer <token> or ?token=), and bakes the header into the Cowork plugin. The setup script can't expose the endpoint without one. If you bind a non-loopback host by hand (FIREFOX_BRIDGE_SSE_HOST) without FIREFOX_BRIDGE_SSE_TOKEN, the bridge starts anyway and logs a warning. Don't.

  • WebSocket relay (127.0.0.1:3002, bridge ↔ extension) rejects connections whose Origin is a web page, so a site you visit can't hijack it.

  • /dispatch (the CLI's endpoint) accepts only loopback connections, requires the local token (an owner-only file web pages can't read), rejects web-page origins, requires exact application/json, and validates every tool name and argument against the MCP schemas.

  • /health exposes only name, version, pid, start time, and connection state.

Configuration

Variable

Default

Purpose

FIREFOX_BRIDGE_WS_PORT

3002

WebSocket port (Desktop + extension relay)

FIREFOX_BRIDGE_WS_HOST

127.0.0.1

WebSocket bind address

FIREFOX_BRIDGE_SSE_PORT

3003

Streamable HTTP port

FIREFOX_BRIDGE_SSE_HOST

127.0.0.1

HTTP bind address: set 0.0.0.0 to expose (use a token!)

FIREFOX_BRIDGE_SSE_TOKEN

(none)

Require this bearer/query token on HTTP requests

FIREFOX_BRIDGE_TOKEN_FILE

.firefox-bridge-token

Path to local token file (0600, prevents web access)

FIREFOX_BRIDGE_LOG_DIR

~/.firefox-mcp

Directory for bridge logs (bridge.log)

FIREFOX_BRIDGE_EXPOSE

0

1 is the same as npm run setup -- --cowork

FIREFOX_BRIDGE_WS_ALLOW_ANY_ORIGIN

0

1 → disable the WS origin guard (debug escape hatch)

FIREFOX_BRIDGE_AGENT_TAB_IDLE_MS

7200000

Close agent tabs idle this long (ms); 0 disables the sweep

Caveats

  • navigate and click are fire-and-forget: they return when the action is initiated, without waiting for the page to finish loading or settling. Add your own read-back (e.g. get_page_text) when you need to be sure a step landed.

  • Mouse and keyboard tools dispatch synthetic DOM events: they drive page handlers, not native browser chrome.

  • find_element, get_page_text, the pointer tools, type and press_key reach into iframes and shadow roots (open and closed). get_links, get_tables, javascript_tool, drag, scroll, select_option and set_checkbox stay in the top frame's light DOM.

  • A successful click means events were dispatched. A click aimed through find_element's ref fails if another element covers the target; tabHidden: true flags a background tab, where some pages ignore clicks.

Permanent install

Release Firefox only installs signed extensions. Mozilla signs unlisted (self-distributed) add-ons automatically, usually within minutes:

  1. Get API credentials at https://addons.mozilla.org/developers/addon/api/key/ and export them as WEB_EXT_API_KEY (the JWT issuer) and WEB_EXT_API_SECRET.

  2. Change browser_specific_settings.gecko.id in firefox-extension/manifest.json to your own: an add-on id belongs to the account that first signed it.

  3. npm run sign, then open the .xpi from web-ext-artifacts/ in Firefox (or drag it onto a window) and approve the install. Remove the temporary copy from about:debugging first.

Each upload needs a new version in the manifest, so bump it before signing again.

Development

npm install --prefix firefox-mcp-bridge
npm test                 # bridge, extension, and bench unit tests
npm run lint:extension   # web-ext lint

Add a tool: define it in firefox-mcp-bridge/tools.mjs, add a handler under firefox-extension/tools/, register it in the extension manifest, and add tests. A parity test checks that the bridge and the extension expose the same tool names.

The extension's manifest.json version is the one that matters for releases; the package.json versions are internal.

License

MIT, see LICENSE.

Related MCP Connectors

Related MCP Servers

  • 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.
    2
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to create, read, and operate granted background tabs in Chrome via a local stdio MCP server, preserving the human's front tab. Supports tab groups, navigation, clicking/typing, screenshots, and human-priority permissions.
    MIT
  • 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