claude-for-firefox
by rorsketch
README.md
<p align="center"><img src="docs/logo.svg" alt="Foxpilot logo" width="128"></p>
# Foxpilot for Claude Code (unofficial)
> **This is an unofficial community project. It is not affiliated with, endorsed by, or sponsored by Anthropic. Claude is a trademark of Anthropic.**
Lets [Claude Code](https://claude.com/claude-code) drive your **real Firefox**: your own tabs, your own
logins. Clicks and typing are performed with synthetic DOM events inside the page; **no system-level
input** is ever generated. Communication is over a local unix socket only: **no network port is opened**.
## How it looks
<p align="center"><img src="docs/screenshots/terminal-session.png" alt="A Claude Code session calling the Foxpilot tools tabs_create, find, click and type" width="760"></p>
*Claude Code calls the tools; the real output of each call is shown (tabs_create, find, click, type on a demo form). The prompt and the terminal frame are a re-creation for this README.*
<p align="center"><img src="docs/screenshots/cursor-in-action.png" alt="Firefox with a contact form being filled in; Claude's orange cursor is over the message field" width="760"></p>
*Claude's cursor (the orange arrow) shows where it is acting in your own Firefox tab, here typing in the message field.*
<p align="center"><img src="docs/screenshots/popup.png" alt="The extension popup: connected to the MCP server, with the allowed domains" width="420"></p>
*The toolbar popup: connection status and a one-click allow/revoke for the current site.*
<p align="center"><img src="docs/screenshots/options.png" alt="The options page with the allowed domains and the opt-in settings" width="600"></p>
*The options page: the domains Claude may act on, and the off-by-default switches.*
## Architecture
```
Claude Code ──stdio/MCP──> src/server.mjs ──unix socket (mode 600)──> src/host.mjs
│ native messaging
v
Firefox extension (background.js)
│
v
content script in the page (DOM events)
```
- `src/server.mjs`: MCP server (stdio), exposes the tools below, owns the socket
(`$XDG_RUNTIME_DIR/claude-in-firefox.sock`, override with `CLAUDE_IN_FIREFOX_SOCK`).
- `src/host.mjs`: native messaging host launched by Firefox, bridges the socket and the extension.
- `extension/`: Manifest V2 extension (background page + content script, popup, options page).
## Requirements
- **Linux.** The bridge socket lives in `$XDG_RUNTIME_DIR` and `bin/install-host.mjs` writes only to
`~/.mozilla/native-messaging-hosts`. macOS and Windows are not supported (the host manifest location
differs there and is not handled).
- **Firefox 128 or newer** (`strict_min_version` in the extension manifest), installed as a regular
package (deb or tarball). With Snap or Flatpak the native messaging directory is different and this
setup has not been tried.
- **Node.js** (tested with 22). It must stay installed: the native messaging host wrapper runs it.
## Install
```sh
npm install
node bin/install-host.mjs # writes the native messaging manifest in ~/.mozilla/native-messaging-hosts/
npm run build # produces dist/foxpilot-<version>.xpi
```
(`node bin/install-host.mjs --uninstall` removes the host manifest.) The host wrapper records the
absolute path of the `node` that ran `install-host.mjs`: re-run it if you change Node version.
Load the extension, either:
- **temporarily**: `about:debugging` → *This Firefox* → *Load Temporary Add-on…* → pick
`extension/manifest.json` (gone at the next restart); or
- **permanently**: Firefox release requires signed extensions. Sign it as *unlisted* with your own
addons.mozilla.org API keys:
```sh
export WEB_EXT_API_KEY=... # "JWT issuer" from the AMO API keys page
export WEB_EXT_API_SECRET=... # "JWT secret"
npm run sign # output in dist/signed/
```
Each signature needs a new `version` in `extension/manifest.json`. Open the signed `.xpi` in Firefox to install it.
The extension id is `claude-for-firefox@unofficial.extension`; it must match `allowed_extensions` in the
host manifest (done by `install-host.mjs`).
Register the MCP server with Claude Code:
```sh
claude mcp add claude-for-firefox -- node /path/to/src/server.mjs
```
### Quick check
1. `claude mcp list` should show `claude-for-firefox` as connected.
2. With Firefox open and the extension loaded, ask Claude to use `tabs_context`: it should list your tabs.
3. Before `navigate` or `read_page` on a site, allow its domain from the extension popup (or options page);
otherwise the call is refused with «dominio … non autorizzato».
## Permissions
Firefox asks for these when you install the extension:
- **`nativeMessaging`**: talk to the local host (`src/host.mjs`), which relays to the MCP server over the unix socket
- **`tabs`**: list tabs and read their url/title, open and close the tabs Claude creates
- **`storage`**: keep your allowed domains and settings
- **`sessions`**: mark the tabs Claude created, so `tabs_close` only closes those
- **`tabGroups`**: put the tabs Claude opens in a «Claude» group
- **`<all_urls>`**: inject the content script into the page you allowed; it can only be injected once the domain is allowed (see below)
The manifest also declares that the extension handles browsing activity and website content, as
Firefox requires. These install-time permissions are **not** the limit on what Claude can do: the real
limit is the per-site consent below.
## Per-site permissions
Claude acts **only on domains you allow** (and their subdomains). Manage them from the extension popup
(button for the current tab's domain) or the options page. Actions on a non-allowed domain are refused.
`tabs_context` and `tabs_create` are always allowed. `navigate`/`tabs_create` accept only `http(s)`
(and `about:blank` for `tabs_create`): never `file:`, `moz-extension:`, `javascript:`, `data:`.
## MCP tools
`tabs_context`, `tabs_create` (opens in background), `tabs_close` (only tabs created by Claude),
`navigate`, `read_page`, `find`, `click`, `form_input`, `type`, `press_key`, `scroll`, `screenshot`,
`file_upload`, `javascript_tool` (**off by default**, enable it in the options page),
`read_console_messages`.
## Safety guards
| Variable | Effect |
|---|---|
| `CIF_READ_ONLY=1` | only observation tools (`tabs_context`, `tabs_create`, `read_page`, `find`, `screenshot`, `scroll`, `read_console_messages`) |
| `CIF_MAX_ACTIONS_PER_MIN` | actions per minute cap (default 120) |
| `CIF_AUDIT_LOG` | JSONL audit log (default `~/.local/state/claude-in-firefox/audit.jsonl`, mode 600); `text`/`value`/`code` are redacted |
Typing into password fields is refused. Ideas for these guards come from
[nanogenomic/ClaudeCodeBrowser](https://github.com/nanogenomic/ClaudeCodeBrowser) (MIT); the code was rewritten, not copied.
## Known limits
- Synthetic events have `isTrusted=false`; some sites (payments, captchas, some logins) reject them.
- Screenshots of background tabs can occasionally come back blank; the extension retries and then reports an error.
- `javascript_tool` relies on page-level `eval`: a strict CSP may block it.
- Main frame only (no iframes). Several Claude Code sessions can share one Firefox: the first MCP server owns the socket, the others relay their calls through it.
## Privacy
The extension sends **nothing over the network** by itself. Page contents it reads go only to the local
Claude Code process, which in turn sends them to Anthropic like any other content of your conversation.
Enable it only on sites you are comfortable sharing with the model.
## Tests
`node test/e2e.mjs` starts a regular (non-headless) Firefox on its own Xvfb display, with a temporary profile
and HOME (your real Firefox and profile are not touched; it needs Firefox at `/usr/lib/firefox/firefox`,
`xvfb`, and `python3`).
Other benches: `test/guard.mjs`, `test/client-guard.mjs`, `test/multi.mjs`, `test/upload.mjs`, `test/cursor.mjs`.
## License
MIT, see `LICENSE`.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues