Skip to main content
Glama
marionette-mcp
by bornmw

firefox-mcp-marionette

Zero-dependency Model Context Protocol server that drives a Firefox with Marionette enabled — the instance you launched with --marionette (the MCP asks before attaching to a running one), or a dedicated instance the MCP starts for you on request — via its native wire protocol.

AI agents get a precise DOM actuator: snapshot the interactive elements of a page, then click, type, select, toggle checkboxes, upload files, wait for conditions, run JS, and screenshot — all against the browser session you control (your profile, your logins, your kill switch).

Why not Playwright/CDP?

Playwright's browser-automation channels (CDP, extension mode) target Chromium, or force the library to launch and own a pinned browser build. Marionette is different:

  • You own the browser. The server connects over loopback TCP (default port 2828) to the Firefox you launched, in whatever profile you chose — but never silently: on the first browser call of a session it probes the endpoint, and if a running Firefox answers, the tool asks you whether to attach to it, launch a new dedicated instance, or do something else (nothing attaches on its own). If nothing is reachable, it asks instead of failing — and if you choose it, fx_launch starts a fresh, dedicated, loopback-only instance with a clean profile. Nothing in this repo downloads or upgrades a browser.

  • Zero dependencies. No npm install of runtime deps, no browser downloads, no CDP shim. One Node runtime (>= 20) plus your existing Firefox.

  • Native protocol. Frames are length-prefixed JSON over TCP — the same protocol Selenium's Firefox driver speaks. No protocol translation, no version drift.

Related MCP server: Foxhand

Install

Prerequisites: Node.js >= 20 and Firefox — that's all; there are no runtime dependencies and nothing ever downloads or upgrades a browser. And the mental model: the server runs as the child process of your MCP client over stdio — the client spawns it per session, so there is no daemon to install, start, or stop yourself.

Node MCPs are usually distributed through the npm registry, run via npx from the client config (the same pattern as @playwright/mcp and the official @modelcontextprotocol/server-* packages). This server is published that way: firefox-mcp-marionette — the whole install is one command, no git needed (npx ships with Node). Three ways to get the code on your machine:

  • npm (recommended). npx -y firefox-mcp-marionette — one-shot run straight from the registry (that is all step 1 of the Quick start is); or npm i -g firefox-mcp-marionette to keep it installed.

  • Source zip — no git, no npm. Download the archive in a browser: main.zip, unzip it, and point your client config at the extracted src/server.mjs instead.

  • git clone (development/contributing). git clone https://github.com/bornmw/firefox-mcp-marionette; npm link optionally puts the bin on PATH.

Copying the repo around? The minimal footprint is the whole src/ directory (4 files: server.mjs, marionette.mjs, protocol.mjs, evalwrap.mjs) — they import each other by relative path, so a single server.mjs alone will not work. scripts/ (smoke tests) and test/ are optional; nothing in src/ imports them.

Quick start

The whole setup is: register the server in your MCP client → have a Firefox to attach to (or let the MCP start one) → use the fx_* tools.

1. Register the server with your MCP client (it spawns the server process for you, per session):

opencode (opencode.json):

{
  "mcp": {
    "marionette": {
      "type": "local",
      "command": ["npx", "-y", "firefox-mcp-marionette"],
      "environment": {
        "FX_MARIONETTE_PORT": "2828",
        "FX_MCP_FILE_ROOTS": "/tmp,/your/projects"
      },
      "enabled": true
    }
  }
}

Pin a version with "command": ["npx", "-y", "firefox-mcp-marionette@0.8.0"]; developing from a local checkout instead? Use "command": ["node", "/absolute/path/to/firefox-mcp-marionette/src/server.mjs"] — the same environment block works for both.

2. Have a Firefox listening on port 2828 — or don't. Either launch one yourself:

firefox --marionette        # dedicated profile recommended; default port 2828 matches

or start your agent session — on the first browser call the MCP probes the endpoint non-invasively (no session is opened by the probe): a reachable Firefox → you're asked whether to attach to it, launch a new dedicated instance instead, or do something else; nothing reachable → the same kind of question (a new instance via fx_launch, connect to one you provide, or your own direction). FX_MCP_AUTO_LAUNCH=1 skips the question and auto-launches when nothing is listening.

3. Smoke test (optional):

npm run e2e:live

The fx_* tools are now available in-session.

First trigger in a session: the MCP probes, then asks

The stdio server lives with one automation session (one opencode session). On the first browser call it probes the configured endpoint (FX_MARIONETTE_HOST:PORT) with a short, non-invasive connection: open TCP, expect the Marionette hello frame, close the socket — the probe never opens a session, so it cannot count as the browser's active client and cannot displace another client. Then:

  • A Firefox answers → nothing attaches. Every browser tool returns a structured browser-detected decision, and the agent asks you:

    1. Attach to the detected browser — fx_connect {host, port}. This commits that endpoint for the session; later reconnects (e.g. after a browser restart) are silent.

    2. Start a new dedicated instance instead — fx_launch: the server picks a free port, writes that port into a fresh profile's user.js prefs (user_pref("marionette.port", N) + marionette.enabled — there is no --marionette-port CLI flag), runs firefox --marionette --no-remote -profile <dir>, and attaches to it. The detected instance keeps running, untouched. The new instance starts empty (no cookies/logins). Stop it later with fx_shutdown.

    3. Something else — your direction (point at a different endpoint, reconfigure, stop that browser first, …).

  • Nothing reachable → instead of a raw ECONNREFUSED, every browser tool returns a structured need_bootstrap decision with three options, and the agent asks you:

    1. Start a new dedicated instance — fx_launch (as above).

    2. Connect to an already-running instance — fx_connect {host, port} with details you provide.

    3. Something else — your direction (launch it yourself, reconfigure the endpoint, …).

  • Something answers but drops the handshake → busy-other-client: a Marionette browser there likely already holds another active client (Marionette serves ONE client per browser). The agent asks how to proceed — free the other client, point elsewhere, or launch new.

Every decision payload carries an explicit instruction field: the browser choice belongs to you, not the agent — the agent must present the question and options to you and wait for your explicit choice before calling any listed tool (never launch/attach on its own judgment).

Once an endpoint is committed (you chose fx_connect for a detected browser, fx_launch started an instance, or FX_MCP_AUTO_LAUNCH=1 booted one when nothing was listening), later reconnects within the session are silent — the question is asked at most once per endpoint per session. fx_status never opens a session itself: with no committed endpoint it only probes and reports (connected, endpoint vs configured, the probe result, and the matching options — never an error); it attaches and reports live state only for committed endpoints. It also reports launched/launchedCurrent when this server started the browser. FX_MCP_AUTO_LAUNCH=1 auto-boots option 1 transparently in the nothing-reachable case (the tool runs in the same call, result tagged auto_started) and never auto-attaches to a detected browser.

Matching the port (attach only when you choose it; launch on request)

The server attaches over loopback TCP to a firefox --marionette instance on FX_MARIONETTE_HOST:PORT (you launched it, or fx_launch started it for this session). So the browser's Marionette port must equal the endpoint of the MCP:

  • Default port 2828 (both sides) → just firefox --marionette. No extra config needed.

  • Custom port → there is no --marionette-port CLI flag. The port is always set through the profile's user.js prefs. Let fx_launch {port: 2829} do the whole procedure (fresh profile dir + prefs + launch + attach), or do it by hand:

    PROFILE=~/.mozilla/firefox/mcp-2829   # any dir
    mkdir -p "$PROFILE"
    printf 'user_pref("marionette.enabled", true);\nuser_pref("marionette.port", 2829);\n' > "$PROFILE/user.js"
    firefox --marionette --no-remote -profile "$PROFILE"

    Then point the MCP at it via FX_MARIONETTE_PORT (opencode config) — or, if the MCP is already running, re-point it at runtime with fx_connect {host, port} (no restart needed).

  • Verify / diagnose: fx_status reports both the active endpoint and the configured endpoint. If it can't connect, the error names the port it tried and how to launch Firefox there (e.g. ECONNREFUSED 127.0.0.1:2829 → nothing listening; launch as above). Remember Marionette serves one active client per browser — don't leave another firefox-mcp-marionette (or another automation) attached to the same instance.

Typical flow:

  1. fx_navigate to the page

  2. fx_snapshot → numbered map of interactive elements (refs)

  3. fx_click / fx_type / fx_select / fx_toggle / fx_upload by ref (or CSS selector)

  4. Forms: fx_form → field map (index/label/context/value), then fx_field (set by index/id/label) and fx_answer (Yes/No or radio/checkbox questions by question text + option label); fx_scroll before clicking elements obscured by fixed headers

  5. fx_wait for the next state; fx_screenshot + your own vision pass to verify what the DOM can't

Form-tool gotchas (from live ATS/portal forms): re-renders can silently drop checked boxes — re-verify all fields after any state change; a free-text location field is often separate from a city checkbox group; required radio groups are sometimes not wrapped in labeled field containers — audit fx_form.groups (and a final screenshot) instead of assuming the labeled fields are the whole form; DOM checked ≠ the framework's form state — trust the tools' confirmed/verified output (a stale pre-selected option is the classic failure: fx_answer handles it via the toggle cycle). Long application forms (e.g. Google) hide mandatory consent/attestation checkboxes ("…hereby certify that…", "I understand that the information I submit…") that gate the whole submit/apply: the button is left hard-disabled or the click silently no-ops until the box is ticked — that is client-side enablement, not bot protection; fx_gates surfaces these boxes (plus the disabled button and any alert banner) so you can find and check the actual gate. Material-style rows put the real <input> visually hidden under its own li/button chrome, so a direct input click can be reported "not clickable … obscured" — fx_click/fx_field/fx_answer recover by clicking the obscuring same-widget topmost and report it via overlay-top:….

Architecture

 AI agent (e.g. opencode)
        │  stdio · newline-delimited JSON-RPC 2.0
        ▼
  firefox-mcp-marionette (src/server.mjs)          ── tools: fx_* (29)
        │  loopback TCP · <byteLen>:<json> frames
        ▼
 your Firefox (firefox --marionette)      ── your profile, your cookies
  • src/protocol.mjs — pure wire codec (frame encode/parse, element-ref unwrap). No I/O, fully unit-tested.

  • src/marionette.mjs — async Marionette client (one socket, one session, pending-command map).

  • src/server.mjs — MCP stdio server + tool implementations.

Design notes (bugs that cost real debugging time)

  • Frames are pure ASCII. JSON.stringify does not escape U+E000/U+E001 (W3C file markers) or any char ≥ 0x7F; in UTF-8 those are multi-byte, while the length prefix is computed from string length. That desynchronizes the stream for the rest of the connection. Every frame is \uXXXX-escaped so declared length always equals actual bytes (regression-tested).

  • Element refs are unwrapped. FindElement replies wrap the uuid ({ "element-…": "uuid" }); subsequent commands (ElementClick, ElementSendKeys, …) take the bare uuid.

  • File uploads use the raw absolute path in ElementSendKeys — this protocol generation has no W3C base64 file encoding (those codepoints are the legacy Selenium NULL/CANCEL keys there).

  • Script bodies must return. W3C ExecuteScript bodies are function bodies: a bare expression statement evaluates and is discarded.

  • Marionette never awaits returned Promises. A return (async () => { … })() body would serialize to null immediately, so fx_eval runs the body through a synchronous wrapper and polls window until the Promise settles (two-phase protocol; wait_ms bounds it, default 30 s).

  • #id CSS selectors with digit-leading ids are invalid (e.g. Ashby's UUID ids #56d78818-…). fx_click/fx_type auto-rewrite them to [id="…"] and report the rewrite (used); unsupported CSS (e.g. :has()) is caught in-page before the driver call with an actionable error.

  • DOM checked ≠ framework form state. Frameworks (notably Ashby) register a choice only on a real change. fx_answer therefore detects a stale pre-selected option (or an ineffective click) and runs a toggle cycle — click another option, then the target — on exclusive (radio/button) groups, re-verifying afterwards; fx_form aggregates radio/checkbox inputs into choice groups (question context + per-option state) so required groups can be audited in one call.

  • Marionette keeps a persistent session across reconnects; a crashed automation client can leave stale session state — relaunch the browser if commands queue forever.

  • A single command must always settle. Commands are serialized and the browser's main thread can stall (modal dialog, hung navigation), so send() bounds every command via FX_MCP_CMD_TIMEOUT_MS (default 120 s). On expiry the connection is poisoned (socket destroyed, session cleared) and the next command reconnects fresh — without that, one unanswered command wedges the entire server forever.

  • Socket events are per-socket. The error/close handlers only act when this.sock === s. A superseded socket (dropped during a command-timeout poison) can emit late events after we've reconnected; reacting to them would destroy the fresh, healthy socket.

  • The first trigger is a decision, not an error, and not a silent attach. The server process == one automation session. Its first browser call runs a non-invasive probe (open the endpoint, expect the Marionette hello, close — the probe never opens a session, so it cannot steal the active-client slot): a live browser → a browser-detected decision (attach via fx_connect / launch new via fx_launch / user-directed); nothing listening → the need_bootstrap decision (launch new / connect / user-directed; FX_MCP_AUTO_LAUNCH=1 collapses it to auto-launch); a dropped handshake → busy-other-client (Marionette serves one client per browser — a held instance drops a second client's socket cleanly, which is a distinct probe outcome, not a classified-away error). Committing an endpoint (user-chosen fx_connect, fx_launch, or auto-launch) makes later in-session reconnects silent. fx_status never opens a session — it only probes and reports.

  • The launch port lives in prefs, never on the command line. Firefox has no --marionette-port flag, so fx_launch creates a fresh profile and writes user_pref("marionette.port", N) + user_pref("marionette.enabled", true) to its user.js before firefox --marionette --no-remote -profile <dir>. The started pid is recorded (memory + <profile>/.firefox-mcp-marionette-launched.json) so fx_shutdown kills exactly that instance — a user-launched browser is never touched.

Tools

Tool

Purpose

fx_status

Connection, active endpoint vs configured, session, current page, navigator.webdriver. Never opens a session on its own: with no committed endpoint it probes non-invasively (connect → expect hello → close) and returns connected:false + probe result + options; with a committed endpoint it attaches/re-attaches and reports live state. Reports launched/launchedCurrent when this server started the instance

fx_launch

Bootstrap option 1: start a NEW dedicated Firefox — fresh profile (<root>/firefox-mcp-marionette-<port>), port written into user.js prefs (marionette.port; no CLI flag exists), firefox --marionette --no-remote -profile <dir>, wait for the listener, attach. Optional {port} (default: first free above the configured one) and {profile} dir. Reuse: re-calling with the same live port re-attaches, no second process

fx_shutdown

Stop an instance this server started via fx_launch (killed by the recorded pid; a user-launched browser is never touched). Defaults to the current endpoint

fx_connect

Bootstrap option 2 / detected-browser approval: (re-)point the MCP at an already-running loopback endpoint {host, port} and attach (env-configured default when omitted). This commits the endpoint for the session — later reconnects are silent. Returns the active endpoint, the configured one, and the session; a failed attach returns a structured decision payload (probe result + options) instead of a raw error

fx_navigate

Go to a URL

fx_page

Current URL + title

fx_snapshot

Interactive-element map with refs (incl. visible label text when present)

fx_click

Click (ref or selector; digit-leading #id auto-rewritten to [id="…"], unsupported CSS caught in-page). If the element is not clickable because another element obscures it and the obscuring element belongs to the same widget (Material button chrome, an li/label over a hidden input), the obscuring topmost is clicked instead and reported as via: "overlay-top:…"; a foreign blocker is reported with its identity

fx_type

Type text (clears first unless keep: true; same selector hardening)

fx_select

Set <select> by option value or label

fx_toggle

Set checkbox/radio state

fx_upload

Set file input (raw path, must be under FX_MCP_FILE_ROOTS)

fx_form

Dump visible form fields: index, type, label, name, context, value, options, files + aggregated choice groups (question context, per-option state); scopes to a CSS root

fx_field

Set a field by index (from fx_form), id, or label substring: real keystrokes for text, verified real click (with fallbacks) for checkbox/radio, option match for select

fx_answer

Answer a grouped choice question (Yes/No buttons, radio/checkbox options) by question text + option label; re-reads and reports the selection state; runs a toggle cycle on exclusive groups when a stale pre-selection (or ineffective click) is detected; self-heals to clicking the visible text-matching wrapper when option labels are unreadable (no-option, e.g. label-less li rows)

fx_scroll

Scroll an element into view (e.g. under a fixed header), wait, return its top coordinate

fx_gates

Consent/attestation gate audit (read-only): visible checkboxes with nearby text — flagging certify/understand/agree/consent/attest/terms/privacy wording — plus disabled buttons (a dead Submit/Apply) and visible alert banners. Run it whenever a submit click does nothing or a submit button stays disabled; the fix is usually an unchecked consent checkbox, not bot protection

fx_links

All hyperlinks of the current page: text + absolute href (optional selector filter; reads open shadow roots). Generic read — no JS needed

fx_extract

Structured page read ("scrape" without JS): one row per container selector; per-row fields {name: css|"text"}

fx_search

Search without JS: navigate to engine results (google/bing/duckduckgo presets; overridable container/title/snippet), return {title, link, snippet} rows; resolve:true follows each link in the browser and reports the real final URL/title (needed for redirect-wrapped hrefs, e.g. Google /goto)

fx_eval

Run JS in the page (function body; return your value — a returned Promise is awaited, default 30 s via wait_ms)

fx_wait

Wait for visible text or CSS selector (≤ 30 s)

fx_screenshot

Full-page PNG (not just the viewport) to a file under an allowed root

fx_windows / fx_window

List / switch windows

fx_alert_state / fx_alert_accept / fx_alert_dismiss

Native dialogs

fx_cookies

Current-origin cookies (names/domains only)

Environment

Variable

Default

Meaning

FX_MARIONETTE_HOST

127.0.0.1

Marionette endpoint (loopback only, by design)

FX_MARIONETTE_PORT

2828

Firefox's --marionette port (must match the browser you launch; fx_status shows the active endpoint). Override at runtime with fx_connect {port}

FX_MCP_FILE_ROOTS

/tmp

Comma-separated roots that fx_upload/fx_screenshot may touch

FX_MCP_CMD_TIMEOUT_MS

120000

Per-command bound. A command that never settles (modal dialog, hung page) poisons the connection and auto-reconnects on the next command, so one stuck page can't wedge the whole server

FX_MCP_AUTO_LAUNCH

off

When the first-call probe finds no reachable Firefox, fx_launch runs automatically instead of returning the bootstrap question. It never auto-attaches to a browser it detects — that always asks

FX_MCP_FIREFOX_BIN

auto-detect

Firefox binary for fx_launch / auto-launch: a path or "cmd args" string (e.g. node /path/standin.mjs); default looks up firefox/firefox-esr on PATH + common system paths

FX_MCP_PROFILE_DIR

~/.mozilla/firefox

Base directory for the per-port profiles fx_launch creates (firefox-mcp-marionette-<port>/)

Security

  • Loopback only. The client connects to 127.0.0.1 — there is deliberately no network path.

  • File access is rooted. Uploads and screenshots reject paths outside FX_MCP_FILE_ROOTS.

  • Use a dedicated profile for automation, and keep the browser visible: a human-in-the-loop is the expected model, not headless stealth. Native OS dialogs (e.g. the file picker) and CAPTCHAs are not automatable by design — stop and let the human handle them.

  • Launch is opt-in and self-contained. fx_launch/auto-launch only create a NEW profile under FX_MCP_PROFILE_DIR (never touching your daily profile), bind to 127.0.0.1 only, and record the started pid so fx_shutdown can stop exactly that instance — it never kills a browser the user launched.

  • Attach is confirmed, not automatic. A running Marionette Firefox found on the first call of a session is never attached to without your say-so: the tool returns a browser-detected decision (attach / launch new / other) instead of opening a session.

Testing

Zero-dependency test suite (built-in node:test):

npm test            # or: node --test test/*.test.mjs
  • test/protocol.test.mjs — frame codec, parser resilience, element-ref unwrapping (pure unit tests).

  • test/marionette.test.mjs — the real client against an in-process fake Marionette server that verifies every frame's byte integrity (non-ASCII payloads included).

  • test/server.test.mjs — spawns the real MCP server and drives it end-to-end (JSON-RPC plumbing, all tool paths, framing-safety under Unicode input, stdin-EOF shutdown); includes the fresh-session gate: a running browser is probed, tools ask before attaching, and fx_connect commits the endpoint.

  • test/bootstrap.test.mjs — first-trigger behavior against a stand-in "firefox" binary (test/helpers/fake_firefox.mjs): the no-browser decision payload, the running-browser decision (non-invasive probe → connect/launch question → fx_connect commits, then calls run), the busy-handshake classification (dropped connection → busy-other-client, not a raw error), the prefs-based launch (port flows only through user.js), attach/reuse, fx_shutdown pid lifecycle, and FX_MCP_AUTO_LAUNCH inline bootstrap.

Live-browser tests (start your own firefox --marionette first; note Marionette serves one active client at a time — no other firefox-mcp-marionette client may be attached):

  • npm run e2e:live — connection, navigation, screenshot through the real wire protocol.

  • npm run e2e:forms — the form primitives against a self-generated test page (labels, option/state round-trips, click fallbacks, negative cases).

CI: .github/workflows/ci.yml — syntax check + full test suite across Node 20/22/24.

License

GPL-2.0 (see LICENSE).

Available Tools

18 tools
fx_alert_acceptA

Accept (OK) the open native dialog

Inputs: no named parameters.

fx_alert_dismissA

Dismiss (Cancel) the open native dialog

Inputs: no named parameters.

fx_alert_stateB

Text of an open native dialog

Inputs: no named parameters.

fx_clickB

Click element by ref (from fx_snapshot) or CSS selector

Inputs: ref, selector.

fx_cookiesA

Cookies of the current origin (names/domains only)

Inputs: no named parameters.

fx_evalA

Execute JS in the page (script = function body, may return a value)

Inputs: js.

fx_navigateC

Navigate the active tab to a URL

Inputs: url.

fx_pageA

Current URL and title

Inputs: no named parameters.

fx_screenshotC

View PNG to a file under an allowed root (or explicit allowed path)

Inputs: path.

fx_selectB

Set by option value or visible label

Inputs: ref, label, value, selector.

fx_snapshotB

Interactive-element map with refs (use refs in fx_click/fx_type/...)

Inputs: no named parameters.

fx_statusB

Health: connection, session, current page, navigator.webdriver flag

Inputs: no named parameters.

fx_toggleC

Set checkbox/radio state

Inputs: on, ref, selector.

fx_typeA

Type text into element (clears first unless keep=true)

Inputs: ref, keep, text, selector.

fx_uploadB

Set a file input value (raw-path sendKeys); path must be under FX_MCP_FILE_ROOTS

Inputs: ref, path, selector.

fx_waitA

Wait until visible text (or CSS selector exists). Default 10s, max 30s

Inputs: text, selector, timeout_ms.

fx_windowC

Switch active window by handle

Inputs: handle.

fx_windowsB

List windows (focused marked)

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 18 tools

    Disambiguation5/5

    Each tool targets a distinct browser automation concern: navigation, page state, element actions, windows, alerts, cookies, and evaluation. There is minimal overlap, and similar-sounding tools like fx_window vs fx_windows are clearly differentiated by singular/plural usage and descriptions.

    Naming Consistency4/5

    All tools share the fx_ prefix and use lowercase names without mixed case or underscores, which is a predictable pattern. There is a slight mix of verb-style names like fx_navigate and fx_click with noun-style names like fx_page and fx_cookies, but the overall convention remains coherent.

    Tool Count4/5

    Eighteen tools is slightly above the typical 3-15 range, but each tool covers a distinct and necessary browser automation action. The count feels justified for the scope, though a few could be consolidated without much loss.

    Completeness4/5

    The set covers core browser automation workflows: navigation, interaction, snapshots, waiting, screenshots, window management, alerts, cookies, and JS evaluation. Minor gaps exist such as no dedicated back/forward/refresh or cookie mutation, but fx_eval can compensate for many advanced needs.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      MCP server for the Firefox you actually use: an add-on plus a small native helper, so an agent gets your real profile, tabs and logged-in sessions instead of a blank headless browser. 45 tools for tabs, accessibility snapshots with refs, clicking, typing, screenshots, bookmarks, history and downloads.
      8
      Mozilla Public 2.0
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables Claude Code to drive a local Firefox browser: opening and managing tabs, navigating, reading page structure and text, clicking, typing, filling forms, running JavaScript, taking screenshots, and inspecting console messages and network requests. Everything runs over a local Unix socket with no network traffic, and actions are restricted to tabs explicitly placed under Claude's control.
      8 npm
      GPL 3.0
    • A
      license
      B
      quality
      B
      maintenance
      Let AI agents use the Firefox, Zen, or LibreWolf browser you already have open, with your logins, cookies, and extensions intact. No Playwright, Chromium, or WebDriver. Agents work in background tabs while you keep browsing: read pages, click, fill forms, take screenshots, and capture network traffic. A WebExtension plus a local bridge lets several agents share one browser.
      27
      302 PyPI
      39
      MIT