Skip to main content
Glama

ff-mcp

ff-mcp connects a local MCP client to Firefox. It gives the client only the browser access that you approve.

The project has two parts:

  • A Firefox Manifest V3 extension controls browser access.

  • A Python Native Messaging host serves MCP on 127.0.0.1.

The Firefox extension makes the final access decision. The local MCP server cannot bypass this decision. A client can list tab metadata without page access. The client needs separate grants to read a page, interact with a page, run a script, or take a screenshot.

Features

  • List tab metadata.

  • Request READ, INTERACT, SCRIPT, or SCREENSHOT access for one tab.

  • Grant access once, for one document, for one tab session, or for a host until you remove the rule.

  • Read bounded snapshots with stable document-scoped element references and CSS query results.

  • Batch up to 20 known actions and return a snapshot in one tool call.

  • Click, type, scroll, and navigate with structured operations.

  • Run JavaScript only after a separate SCRIPT grant.

  • Build persistent rules with a visual editor.

  • Audit access decisions and sensitive operations in Firefox.

  • Listen automatically while enabled on a loopback-only MCP server.

Related MCP server: gecko-mcp

Requirements

  • Firefox 150 or newer.

  • Python 3.14.

  • uv.

One-time SCRIPT execution needs Firefox 153 or newer. Firefox 153 added userScripts.execute(). Other features support Firefox 150 or newer.

Install

Read the user setup guide for complete instructions.

You can also give this repository to a local coding agent. The agent will read AGENTS.md and guide you through setup. You must still select a Firefox profile and approve the add-on in Firefox.

On Windows, follow Windows installation and repair. It uses explicit uv directories outside AppData and registration from ordinary PowerShell outside Codex. This avoids an installation visible to a packaged agent but unavailable to Firefox.

The main commands on other platforms are:

uv tool install --force .
ff-mcp profiles --json
ff-mcp setup --profile "/path/to/your/profile" --install-addon

Confirm the installation in Firefox. The enabled extension starts listening automatically.

For extension development, open about:debugging. Select This Firefox. Select Load Temporary Add-on. Then select extension/manifest.json.

Connect an MCP client

Run ff-mcp connection for the local URL. No token or connection approval is needed. Remove bearer-token settings from older client entries. Each MCP instance uses a separate stateful HTTP session. Browser-origin HTTP requests are rejected.

See the user setup guide for Codex, Claude Code, and generic MCP client examples.

Permission flow

  1. Call browser_tabs and select a tab ID.

  2. Call browser_request_access with capabilities, agent name, model, harness, and a brief task reason.

  3. Approve or reject the request in Firefox.

  4. Use the approved browser tool.

  5. Revoke the grant in Firefox or with browser_revoke.

READ access is enabled by default for these local addresses:

  • localhost

  • Subdomains of .localhost

  • 127.0.0.0/8

  • ::1

This default does not grant INTERACT, SCRIPT, or SCREENSHOT access.

Efficient browser use

Request the needed capabilities together with lifetime: "tab_session" to keep approval through navigation. The popup still lets the user choose a shorter lifetime. Request only what the task needs. Use browser_snapshot to get visible controls with @ref selectors; pass these directly to click, type, or batch actions. References become invalid when the document changes. CSS selectors also work. The default text limit is 12,000 characters; increase max_chars when needed.

Snapshots are compact by default, including those returned by waits and action batches. Visible controls appear once in elements, with stable references, short names (ARIA labels, associated labels, or visible text), link targets, and relevant state. Compact snapshots omit the duplicate forms and links lists and empty attributes. They retain page text and visible headings for context. truncation reports text clipping and omitted controls or headings; individual shortened names and link targets carry truncation flags. Controls are limited to 200. Use browser_query for controls beyond that limit. Use browser_snapshot(compact=false) for full attributes, form structure, and the separate links list; include_links controls that list only in full mode. Input values are not included in either mode.

Use browser_actions for a known sequence such as filling several fields. It requires READ and INTERACT, executes at most 20 click/type/scroll actions in order, and returns a snapshot. It stops on failure and reports completed; inspect that result before retrying. A page navigation or transport failure can interrupt the response after actions have run. Async UI updates can need another snapshot.

Wait for page readiness

browser_navigate sends navigation once, then waits for the destination content script by default. Its wait result reports ready, timeout, or error; a timeout does not undo the navigation. Set wait_until: "none" for the previous immediate-return behavior.

Use browser_wait or browser_snapshot(wait_for=...) for asynchronous UI updates. Conditions are combined with AND. An empty condition waits for an accessible document. Supported fields are:

  • selector: CSS selector or a current-document @ref.

  • state: attached, detached, visible (default), hidden, or enabled; requires a selector.

  • text: case-sensitive substring in the element's rendered text, or the page text without a selector.

  • url: exact absolute HTTP(S) URL, including query and fragment.

For example, after opening a review dialog:

{
  "tab_id": 22,
  "wait_for": {"selector": "[role=dialog] button[type=submit]", "state": "enabled"},
  "timeout_ms": 10000
}

browser_wait returns {status: "ready", snapshot: ...} or {status: "timeout", timeoutMs: ...}. A waiting browser_snapshot returns its normal snapshot on success or the same timeout object. Both require READ access, recheck consent during waiting, and consume a one-use grant only on success. Use a tab-session grant and CSS selectors for waits that span navigation.

Timeouts default to 10 seconds and accept 1–20,000 milliseconds. Polling is bounded even when a content script does not answer. Waits do not retry actions, wait for network-idle, or guarantee that all asynchronous work is finished. Use a condition that represents the next action's prerequisite. Batches remain synchronous; split a batch at an async boundary, wait for the condition, then continue.

Policy rules

Open the extension options page to use the visual policy editor. Each new rule has one main AND group. The main group contains an OR allow group and a NAND exclusion group.

Add host, URL pattern, regular expression, or scheme conditions. You can nest AND, OR, NAND, and NOR groups. Then select the capabilities that the rule grants.

An empty positive group matches nothing. An empty negated group matches everything. Therefore, a new rule stays inactive until you add a condition to its allow group.

The default localhost rule starts with READ access. You can edit or disable it, but you cannot delete it. A persistent approval is automatically added to the main allow group of a new rule.

Regular expressions have a length limit. They cannot use backreferences, lookarounds, or clear nested quantifiers. This is a safety subset. It is not a complete RE2 implementation.

Development

UV_CACHE_DIR=/tmp/ff-mcp-uv-cache uv sync --locked --group dev
UV_CACHE_DIR=/tmp/ff-mcp-uv-cache uv run ruff check .
UV_CACHE_DIR=/tmp/ff-mcp-uv-cache uv run ruff format --check .
UV_CACHE_DIR=/tmp/ff-mcp-uv-cache uv run pytest -q
node --test tests/background.test.js tests/content.test.js tests/policy.test.js tests/rule-model.test.js tests/popup.test.js tests/waits.test.js

Run the Firefox integration test only when you want to start Firefox with a temporary profile:

FF_MCP_RUN_FIREFOX_TESTS=1 \
  UV_CACHE_DIR=/tmp/ff-mcp-uv-cache \
  uv run pytest -q tests/test_firefox_integration.py

Set FIREFOX_BINARY if Selenium cannot find Firefox. The test can find the standard Linux Snap installation without this variable.

The Firefox extension has no runtime third-party dependencies. It also has no build step.

Releases

Check the four synchronized version sources, or bump all of them with one command:

python3 scripts/version.py check
python3 scripts/version.py bump patch  # also accepts minor, major, or an exact X.Y.Z

A tag in the form vX.Y.Z starts the release workflow. CI checks that pyproject.toml, the Python package, the Firefox manifest, and uv.lock all have the same version. The release additionally requires the tag to match that version.

The workflow sends the extension to Mozilla Add-ons for unlisted signing. It verifies the returned XPI. It then attaches the signed XPI and its SHA-256 file to a GitHub release. These generated files stay untracked. It also publishes updates.json at a stable latest-release URL. Signed versions that contain this update URL use the file for automatic self-distributed updates.

Set these secrets in the GitHub release environment:

  • AMO_JWT_ISSUER

  • AMO_JWT_SECRET

Create the credentials on the AMO API keys page. Unlisted signing does not add the extension to AMO search results.

Security limits

  • The extension requests broad site access because it must support user-approved access to many sites. Its internal capability checks are critical.

  • Approved page data and browser activity go to the local native host and MCP client.

  • Firefox blocks content scripts on restricted pages such as about: pages and the add-ons store.

  • A tab-session grant stays active after navigation in that tab. A document grant does not.

  • Temporary grants are bound to one MCP session. A reconnect with a new session needs new grants.

  • Persistent rules (including default localhost READ) apply to every session and local client.

  • Agent/model/harness labels are supplied by the client, not verified identities.

  • Local clients can list tab metadata without approval. Approvals gate page operations.

  • A click or input operation can cause page actions. Grant INTERACT access with care.

  • SCRIPT gives full page control. A main-world script can read and change page-owned JavaScript state. Grant it only to clients and sites that you trust.

See firefox_mcp_extension_findings.html for the design research.

License

MIT. See LICENSE and THIRD_PARTY.md.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server paired with a Firefox extension that enables LLM clients to control the user's browser, supporting tab management, history search, and content reading.
    8 npm
    322
    MIT
  • 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
    46 npm
    2
    MIT
  • A
    license
    B
    quality
    C
    maintenance
    A local Firefox bridge for MCP agents that enables tab control, DOM interaction, mouse/keyboard actions, and screenshots.
    24
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A local MCP server for Firefox that gives AI agents full control over the browser via a Unix socket, enabling automation of tabs, pages, cookies, and more without exposing any network ports.
    8 npm
    1
    MIT