Inari MCP for Firefox
Allows an MCP client to read and operate a running Firefox browser, including navigation, clicking, typing, scrolling, taking screenshots, finding elements, managing tabs, and waiting for page conditions.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Inari MCP for Firefoxtake a screenshot of the page I'm on in Firefox"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Inari MCP for Firefox
English | 日本語
Let an MCP client such as Claude Code read and operate your everyday Firefox. Your real profile and your real logins, no throwaway browser. Clicking, typing and navigating always wait for your approval.
What it sends. When the MCP client calls a tool, the extension reads what that tool needs from your tabs (page text and elements, form values, the URL and title, the list of open tabs, screenshots) and sends it to the native host on your computer. From there it goes to the MCP client you connected, and that client sends it to its AI provider. Nothing is sent to the developer. Password, hidden, one-time-code and credit card fields are masked; cookies are not read. Details: Privacy and the privacy policy.
What you need. Firefox 142 or later with the extension from addons.mozilla.org; Node.js 20 or later and the native host from this repository (the extension does nothing without it); an MCP client such as Claude Code. See Quick Start.
What is this?
Inari MCP for Firefox is a Firefox extension, a native messaging host and an MCP server that let Claude Code (or another MCP client) drive your actual Firefox browser — the one with your cookies, your sessions, your extensions already installed. Instead of spinning up a throwaway profile, it talks to your running Firefox through a WebExtension and a native messaging bridge, so it can read pages, find elements, take screenshots, switch tabs, and click and type on the tab you're already looking at, each action after you approve it.
It's a fork of nanogenomic/ClaudeCodeBrowser (MIT), ported from Python to TypeScript and narrowed to Firefox only.
Inari MCP and KitsuneMCP
Inari MCP for Firefox is the addons.mozilla.org edition of KitsuneMCP (private), split off from KitsuneMCP 0.2.0 on 2026-10-14 and numbered from 0.1.0. It runs no code that comes from the MCP client or the AI in pages, records no console or network logs, and asks before every action on a page or the browser (reading, waiting, scrolling, highlighting an element and switching tabs do not ask). It has its own add-on ID (inari-mcp@elys0912.github.io), native messaging host name (inari_mcp) and state directory (~/.inari-mcp), so both can be installed in the same Firefox. Its default ports (8785 / 8786) differ from KitsuneMCP's (8765 / 8766), so both can also run at the same time.
Related MCP server: firefox-bridge
Why?
Chrome already has an official Claude browser extension. Firefox doesn't — not yet. Inari MCP exists to fill that gap until Anthropic (or Mozilla) ships something official. When that happens, this project's job is basically done.
Not for you?
Inari MCP is deliberately narrow. If your use case is one of these, you'll be happier elsewhere:
Chrome/Edge user → use the official Claude browser extension or Chrome DevTools MCP.
CI / automated testing, cross-browser → Playwright MCP. It supports Chromium/Firefox/WebKit and disposable profiles — the right tool for that job.
Large-scale scraping → browser-use or Browserbase are built for that.
Debugging Firefox itself (DevTools protocol level) → mozilla/firefox-devtools-mcp is the real deal for that.
CAPTCHA solving → Inari MCP won't do this. It detects a CAPTCHA and hands control back to you — on purpose.
Running scripts in pages → Inari MCP has no tool for it, and never executes code it receives.
Private windows → the extension does not run there.
Features
17
fox_*tools covering navigation, click/type/scroll, screenshots (visible/full page/all tabs), reading and finding, tab management and wait helpers. Related operations share one tool with a mode argument (fox_waitwithfor: "url",fox_tabwithaction: "close", …). Prefixedfox_instead ofbrowser_so it doesn't collide with Playwright MCP if you run both. Not all of them work on every backend yet: see Tool availability.Tool profiles (
FOX_CAPS) — registering every tool costs real context tokens. Pick profiles (core,human,wait, …) to only load what you need. SeeFOX_CAPSprofiles.Your approval for every action — clicking, typing, pressing keys, setting field values, hovering, navigating and opening or closing tabs wait until you approve them, call by call and on every site, either in Claude Code's own confirmation dialog (MCP elicitation) or in the Inari MCP toolbar popup in Firefox. Nothing inside the page can approve it. Reading, waiting, scrolling, highlighting an element and switching tabs do not ask, except that reading a page and screenshots ask on the sites you list in
confirm_read_sites. See Human approval.Permission, pause and badge — on first start the extension asks for your permission and connects to nothing until you allow it. The toolbar popup has a pause switch that refuses every call, and a badge shows while a tool is working.
Safety guard — container scope, rate limit, URL scheme check, block/allow lists and read-only mode, in front of every call. Every decision is written to a JSONL audit log on your computer.
Container isolation (opt-in) — using Firefox's
contextualIdentities, you can restrict the agent to a dedicated container so your banking/email sessions in other tabs stay invisible to it. This is a Firefox-only capability; Chromium has nothing equivalent.Headless mode (advanced) — a Playwright-driven, Firefox-only headless backend for when you don't want the extension path. Many tools and parameters are not supported there (see Tool availability).
Firefox-native APIs —
browser.findfor token-cheap in-page text search,tabs.captureTabfor screenshots, including full-page and all-tabs ones (a background tab is captured without switching to it; a discarded tab is an error until you switch to it).
Quick Start
Three steps: 1. install the extension from AMO, 2. install the native host, 3. register it with your MCP client.
Prerequisites
Firefox 142 or later (
strict_min_versionof the extension). Developer Edition and Nightly read the same Native Messaging registration as the release buildNode.js 20 or later (to run the native host and the MCP server; the reproducible build for the AMO review uses Node.js 24.x and npm 11.x, see Development)
An MCP client, such as Claude Code
(Headless mode only) Playwright and its Firefox.
playwrightis an optional dependency ofpackages/mcp-server:npm ciinstalls it unless optional dependencies are omitted (--omit=optional). Its Firefox is a separate download:npx playwright install firefox
PowerShell is not needed: the installer and uninstaller are Node.js scripts.
Install the extension from AMO
Open Inari MCP for Firefox on addons.mozilla.org and click "Add to Firefox".
Firefox shows the permissions and the data the extension sends (website content, website activity and browsing activity). Accept to install.
The extension opens its permission page. Read it and allow it; until you do, it does not connect to the native host, and if you decline it stays disconnected.
The Inari MCP button appears in the toolbar. Its popup shows the connection state (see Check the connection). Firefox updates the extension by itself; keep the native host at the same version (see Update).
To try your own changes to the extension, load it as a temporary add-on instead:
Open
about:debugging#/runtime/this-firefoxin Firefox."Load Temporary Add-on..." → select
packages/extension/manifest.json(it loads the bundles inpackages/extension/dist/, so runnpm run buildfirst).It's removed when Firefox restarts, so you'll need to load it again after each restart.
Install the native host
The extension only talks to a native messaging host on your computer, which passes the calls on to the MCP server. Both come from this repository and are not on AMO. Check out the version that matches the extension (shown in about:addons):
git clone https://github.com/elys0912/InariMCP.git
cd InariMCP
git checkout v<version>
npm ci
npm run build
npm run setupThe installer works the same way on Windows, macOS and Linux; macOS is untested on real hardware (see Status). On Linux, a Firefox from a distribution package (deb/rpm), Mozilla's APT repository or the tarball from mozilla.org is the reliable choice; Snap and Flatpak builds need the desktop portal (see Platform notes).
npm run setup (node scripts/install.mjs):
checks Node.js (>= 20) and the build output (
packages/*/dist), and looks for Firefox (warning only),writes the Native Messaging host wrapper (
run-host.cmd/run-host.sh) to~/.inari-mcp/native-host/,writes the host manifest
inari_mcp.jsonwhere Firefox reads it (Windows: next to the wrapper, withHKCU\Software\Mozilla\NativeMessagingHosts\inari_mcppointing to it, for every edition; macOS:~/Library/Application Support/Mozilla/NativeMessagingHosts/; Linux:~/.mozilla/native-messaging-hosts/, plus the XDG, Snap and Flatpak directories when those builds seem to be present),prints the
claude mcp addcommand, or runs it with--register-claude(see Register with your MCP client).
It is safe to run again: unchanged files and registrations are left alone. It does not build unless asked (--build runs npm install and npm run build first). Options go after -- when going through npm (npm run setup -- --register-claude), or call the script directly (node scripts/install.mjs --register-claude):
Option | Effect |
| Show what would change; change nothing |
| Answer yes to every question (no prompts) |
| Register the MCP server with Claude Code (default: print the command) |
| Scope for |
| Node executable to register (default: the one running the installer) |
| Run |
| Print the manual installation steps (Markdown) and exit |
| Show the usage, plus the manual steps for this machine |
Questions are asked only when stdin is a terminal; otherwise the default answer is used. --skip-build and --no-register-claude are still accepted. The old npm run install:windows / install:unix remain as aliases of node scripts/install.mjs --build.
If you ever swap out or upgrade Node.js, re-run the installer — the generated wrapper embeds the absolute path to the node binary that ran it (it falls back to the node on Firefox's PATH when that binary is gone). The installer warns when that node comes from a version manager, whose paths change when you switch versions; --node pins a stable one.
Register with your MCP client
The MCP server is a stdio MCP server: node <INSTALL_PATH>/packages/mcp-server/dist/index.js. Register it with your MCP client in the client's own way; for Claude Code, the installer can do it.
npm run setup -- --register-claude registers Inari MCP with Claude Code (claude mcp add, scope user by default — available from any project), with the absolute path of node. A registration that already points to this installation is left unchanged; one that points elsewhere is replaced after asking. Without --register-claude, or when claude is not on your PATH, the installer prints the command; run it yourself:
claude mcp add --scope user inari-mcp -- node <INSTALL_PATH>/packages/mcp-server/dist/index.jsEnvironment variables for the server go before the name, e.g. claude mcp add --scope user --env FOX_CAPS=core,human inari-mcp -- node … (but read Whichever side starts the daemon first, its config wins before relying on them).
Or add it manually to your MCP config (config/mcp_config.json is the same template):
{
"mcpServers": {
"inari-mcp": {
"command": "node",
"args": ["<INSTALL_PATH>/packages/mcp-server/dist/index.js"],
"env": {
"FOX_CAPS": "core,human"
}
}
}
}Replace <INSTALL_PATH> with the absolute path of your clone.
First-run permission
Right after installation the extension opens a permission page in a new tab. It says what the add-on does, what it sends (page content, screenshots, URLs and the tab list, through the native host to the MCP client) and what it needs, and links to the privacy policy. Until you press Allow, the extension does not connect to the native host and does nothing; with Don't allow it stays that way, and the badge shows OFF. To allow it later, open the toolbar popup and press "Open the permission page". To withdraw it, press "Withdraw the permission" under the pause switch of the popup and then Don't allow: the extension disconnects at once. The page is not shown again on updates.
Pausing the agent
The toolbar popup has a Pause agent actions switch. While it is on, the extension refuses every command from the MCP client (the error starts with paused), withdraws the approval requests still waiting, and the badge shows II. A command still running when you turn it on gets the same answer at once, and its typing, waiting or scrolling in the page stops at the next step; an operation done by the browser alone (a screenshot of all tabs, a wait for a tab to load) runs to its end and its result is dropped. Turn it off to let the agent work again. The switch is kept across restarts; if Firefox cannot save it, the popup says so, and after a restart the last saved state is back.
Restart and check the connection
Restart Claude Code (a running session does not pick up a new MCP server).
Make sure the extension is enabled in
about:addons, that you allowed it on its permission page, and that it is not paused in its popup.Look at the Inari MCP toolbar button:
Badge / popup | Meaning | What to do |
no badge, popup "Connected" / "Connected (N)" | Firefox and N Claude Code sessions are connected | nothing |
red | Firefox is ready, but no Claude Code session uses Inari MCP | register it (the popup shows the command) and restart Claude Code; a running session appears within about 30 s |
red | Firefox cannot find the Native Messaging host | build and run the installer (the popup shows the commands), then press Recheck (Snap / Flatpak Firefox: see Platform notes) |
red | the host is registered but does not start, or the daemon does not answer | re-run the installer, |
red | the clone is older than the extension | check out the extension's version, rebuild and re-run the installer (see Update), restart Firefox |
amber number | approval requests waiting for you | open the popup and approve or deny |
grey | not allowed yet: the extension does not connect | open the popup → "Open the permission page" → Allow (First-run permission) |
blue | paused: commands from the MCP client are refused | turn off the pause switch in the popup (Pausing the agent) |
green | the MCP client is operating Firefox right now | nothing (it goes away when the command ends) |
The badge shows one state at a time, the first that applies: OFF, II, the approval count, ●, then the setup state (! or nothing).
Run the diagnostics:
node packages/mcp-server/dist/index.js --doctor(see Command line). It checks the build, the Firefox registration, the Claude Code registration, the daemon and the logs, and prints a fix for each problem.Ask Claude Code to call
fox_safety_status. It shows the active safety policy (which calls ask for approval, read-only mode, URL patterns, rate limit, audit log path) and warns when the running daemon was started with different settings than this session. It does not report whether a browser is connected.For a low-level check, query the daemon:
curl --noproxy '*' -sS http://127.0.0.1:8785/health # Windows PowerShell: curl.exe --noproxy "*" -sS …(--noproxy keeps a configured HTTP proxy out of a local request.) "status":"ok" means the daemon runs; browsers_connected should be 1 once the extension is connected (a few seconds after loading it). If nothing answers ("Connection refused"), no daemon is running yet: it is started by the first Claude Code session or by Firefox with the extension loaded, and it exits after 10 minutes unused. Then look at the logs.
Logs
All under ~/.inari-mcp/logs/:
File | Written by |
| the Native Host (override with |
| the shared daemon's stderr |
| the safety guard's audit log ( |
Each line of native-host.log and daemon.log starts with <ISO 8601 time> [<pid>] . Each file is rotated to <name>.old once it grows past 5 MB (one generation kept): native-host.log and audit.jsonl are renamed; daemon.log, which a running daemon keeps open, is copied to daemon.log.old and then truncated (copy-truncate; lines written in between are lost).
Command line
node packages/mcp-server/dist/index.js [option]Option | Effect |
(none) | stdio MCP relay — what Claude Code starts. Starts the shared daemon when none is running |
| the shared daemon. Normally started automatically by a relay or by the Native Host |
| check the installation and the running daemon |
| stop the running daemon gracefully |
| show the usage |
| print the version |
--help and --version print and exit before anything else happens: no daemon is started, no API token or log file is created. An unknown argument (or two modes together, or --force without --stop-daemon) exits with code 2.
--doctor prints one line per item — OK, WARN, FAIL or SKIP — with a fix for each problem, and exits with code 1 when an item FAILs (else 0). A missing native-messaging registration is a WARN: the extension route (Firefox) needs it, headless alone does not. The 14 items: node, versions (all packages and the extension manifest have the same version), build (dist newer than the sources), native-messaging (the Firefox registration and the manifest's name, type, allowed extension and path), nm-wrapper (the wrapper points to this checkout and its node exists), claude (the Claude Code registration, read from ~/.claude.json — $CLAUDE_CONFIG_DIR/.claude.json when CLAUDE_CONFIG_DIR is set — and .mcp.json), powershell (Windows: whether the Native Host can use it), daemon (/health: protocol, proof, version), token, settings (FOX_* here vs. the running daemon), browsers (connected Native Hosts), safety-config (safety.json parses and is valid), screenshots (FOX_SCREENSHOTS_DIR) and logs (sizes, Native Host start-up limit). It is read-only: it creates no token, starts no daemon, writes no log, and does not run claude mcp get (that would start the server to test it).
--stop-daemon asks the daemon to stop over a signed POST /daemon/shutdown (it needs ~/.inari-mcp/api_token): running tool calls get up to 10 s to finish (--force: none), new tool calls are answered with HTTP 503 meanwhile, headless Firefox is closed, and the daemon exits. A daemon from an older version that predates --stop-daemon cannot be stopped this way: end its process by PID. --stop-daemon exits with code 0 when the daemon is gone (or none was running), else 1. Every Claude Code session loses the browser tools until its next call starts a daemon again, and a running Native Host (Firefox with the extension) starts one within about 20 s — quit Firefox first to keep it stopped.
scripts/e2e/nm-probe.mjs [manifest.json] starts the Native Messaging host the way Firefox does (from the installed manifest by default), sends ping and status, and checks that it reaches the daemon (exit code 0 = pass, 1 = fail, 2 = bad arguments). It starts a daemon if none is running.
Headless mode
If you don't want to install the extension, set FOX_HEADLESS=1 for the daemon and it drives a Playwright-managed Firefox instance directly instead of talking to your real browser. The relay passes its environment to the daemon it starts, so setting it for the relay (the command Claude Code runs) is enough when no daemon is running yet:
FOX_HEADLESS=1 node packages/mcp-server/dist/index.js(or claude mcp add --env FOX_HEADLESS=1 …). 1, true and yes (any case) turn it on; any other value leaves it off, and a value that is not a known "off" (0, false, no, off), such as on, makes the daemon log a warning. FOX_HEADLESS_STARTUP_TIMEOUT is the Firefox launch timeout in milliseconds (default 45000). Without playwright installed, the daemon exits at start with FOX_HEADLESS needs the optional dependency playwright, which is not installed (…) in daemon.log; install it (npm ci without --omit=optional, or npm install playwright in packages/mcp-server, as the message says) and its Firefox (npx playwright install firefox). The settings only apply when this session is the one that starts the daemon (see below).
Note this loses your real sessions/cookies/logged-in state — it's a disposable profile, same tradeoff as Playwright MCP. Many tools and parameters are not supported there (ignored parameters are listed in the result's warnings), and nobody can be asked in a browser popup, so acting tools can only be approved through MCP elicitation. Headless mode has no Firefox containers: with container_scope.enabled in safety.json, every browser command is refused; give the headless daemon its own safety.json with container scope off (FOX_SAFETY_CONFIG). Use the extension path if you want your actual logged-in Firefox.
Update
Firefox updates the extension from addons.mozilla.org by itself. The native host and the MCP server are not updated with it: when the extension's version (in about:addons) changes, bring your clone to the same version:
git fetch --tags
git checkout v<version>
npm ci
npm run build
npm run setupThen restart Firefox and restart Claude Code (with a temporary add-on, reload it in about:debugging first). A daemon from the old version keeps running until it is stopped: when the new relay or Native Host reports that the running daemon "speaks protocol …" / "is from another version", stop that daemon process (and restart Firefox if the Native Host started it); a matching one starts on the next tool call. A daemon that predates --stop-daemon has to be ended by PID (--doctor prints the port to look for); a newer one can be stopped with node packages/mcp-server/dist/index.js --stop-daemon.
Notes on updating
re:URL patterns now match the whole URL, in every list (they are anchored as^(?:<regex>)$; seesafety.json). An existingre:pattern written as a prefix, such asre:^https://example\.com/, no longer matches longer URLs: inallowed_url_patternsthose URLs are now refused, and inblocked_url_patterns/confirm_read_sitesthey are no longer blocked or asked about. No warning is logged for this. Append.*to keep the prefix meaning (re:^https://example\.com/.*). Patterns withoutre:are not affected. Check the result withfox_safety_statusafter a daemon restart.
Uninstall
npm run teardownnpm run teardown (node scripts/uninstall.mjs) removes things in an order that keeps them removed:
the Firefox Native Messaging registration (registry key / manifest files), so the extension cannot start the Native Host again,
~/.inari-mcp/native-host/(the wrapper),the Claude Code registration, when you confirm or pass
--unregister-claude(it only finds registrations visible from the repository directory — alocal-scope registration made in another directory is listed, and has to be removed there),running Native Host processes of this repository, then the daemon (after checking that the process on the port is an Inari MCP daemon; over its signed
POST /daemon/shutdownwhen the API token is present, else — or when that fails — SIGTERM, then SIGKILL after the grace period),with
--purge, the rest of~/.inari-mcp/(API token,safety.json, logs, screenshots); otherwise what is left is listed, not deleted.
Claude Code sessions that still run Inari MCP start the daemon again on their next browser tool call; the uninstaller names them — restart them. The repository and the Firefox extension are not touched (remove the extension in Firefox). A Claude Code configuration file (~/.claude.json, .mcp.json) that cannot be read or parsed is reported as a problem (exit code 1), not as "not registered"; check with claude mcp list. A Firefox registration that points to another checkout's Native Host is reported with a warning and removed as well.
Options (after -- with npm): -n/--dry-run, -y/--yes (also removes the Claude Code registration), --unregister-claude, --keep-claude, --no-stop (stop nothing), --grace SECONDS (0 to 60, default 5; anything else is a usage error), --purge, --manual, -h/--help. --stop-daemon, --no-stop-daemon and --no-unregister-claude are still accepted, and npm run uninstall:windows / uninstall:unix remain as aliases.
Manual setup
Where the installer cannot run, set things up by hand. node scripts/install.mjs --manual and node scripts/uninstall.mjs --manual print the steps for every platform, and --help prints them for this machine with your paths filled in. On Windows, without the installer:
Build:
npm ciandnpm run buildin the repository.Create the folder
%USERPROFILE%\.inari-mcp\native-host.Create
%USERPROFILE%\.inari-mcp\native-host\run-host.cmd(use full paths; write%%for a%in a path):@echo off cd /d "C:\path\to\InariMCP\packages\native-host" "C:\Program Files\nodejs\node.exe" "C:\path\to\InariMCP\packages\native-host\dist\index.js" %*Copy
packages\native-host\inari_mcp.jsonto%USERPROFILE%\.inari-mcp\native-host\inari_mcp.jsonand set"path"to the full path ofrun-host.cmd(JSON: write each\as\\).Register it for Firefox (every edition reads this key):
reg add "HKCU\Software\Mozilla\NativeMessagingHosts\inari_mcp" /ve /t REG_SZ /d "%USERPROFILE%\.inari-mcp\native-host\inari_mcp.json" /fOptional, Claude Code:
claude mcp add --scope user inari-mcp -- "C:\Program Files\nodejs\node.exe" "C:\path\to\InariMCP\packages\mcp-server\dist\index.js"
To uninstall by hand on Windows (order matters: while the host is registered and Firefox runs the extension, the Native Host starts the daemon again): reg delete "HKCU\Software\Mozilla\NativeMessagingHosts\inari_mcp" /f; rmdir /s /q "%USERPROFILE%\.inari-mcp\native-host"; claude mcp remove --scope user inari-mcp; remove the extension in Firefox or quit Firefox; find the daemon with netstat -ano | findstr :8785 and stop it with taskkill /PID <pid> /F; optionally rmdir /s /q "%USERPROFILE%\.inari-mcp".
On Windows the Native Host starts the daemon outside Firefox's job object (otherwise closing Firefox kills the daemon and every Claude Code session using it). It tries PowerShell first, then WMI (cscript) and a one-off Task Scheduler task (schtasks); only if all of them fail is the daemon started inside the job, with a warning in native-host.log.
Platform notes
Snap Firefox (Ubuntu's default
firefox) starts Native Messaging hosts through the xdg-desktop-portal WebExtensions portal, which may not work depending on the system. The installer writes the manifest to~/.mozilla/native-messaging-hosts/and into the snap. Where it does not work, install the deb from Mozilla's APT repository.Flatpak Firefox cannot run node from the host inside its sandbox. The installer writes the manifest into
~/.var/app/org.mozilla.firefox/, but Firefox reaches the host only through the portal: setwidget.use-xdg-desktop-portal.native-messagingto1inabout:config(needs an xdg-desktop-portal with WebExtensions support). Without that, re-running the installer does not help, although the popup says "Setup needed"; a non-Flatpak Firefox is the reliable choice.WSL: run Claude Code and Firefox on the same side. With Claude Code inside WSL and Firefox on Windows, each side starts its own daemon with its own
~/.inari-mcp/(and API token), so the WSL session never reaches the Windows Firefox. Either run Claude Code on Windows, or Firefox inside WSL (WSLg) with the installer run there. The installer warns when it runs inside WSL.Several Firefox profiles running at the same time all connect to the one daemon; commands go to the one that connected first, and nothing shows which one that is.
Permissions
The extension asks for ten permissions. Paths are under packages/extension/src/.
Permission | What for | Where |
| The URL and title of every tab, |
|
| The connection to the Native Host ( |
|
| An OS notification when an approval request waits in the popup, when a CAPTCHA needs a human ( |
|
|
|
|
| The content script on every page (reading and acting on the page), and |
|
| One item of the page context menu: take a screenshot (only while the extension is allowed to connect) |
|
| Firefox containers: listing them ( |
|
| No cookie is read or written ( |
|
|
|
|
| Waiting for a tab's navigation to finish ( |
|
The agent can create a Firefox container: fox_tab with action: "create" and a container name that does not exist creates it, and with container_scope on, opening a new tab creates the agent container when it does not exist yet. Creating a container is not one of the actions that ask a human by themselves.
The extension does not run in private windows ("incognito": "not_allowed"), and the install prompt declares the data it sends: website content, website activity and browsing activity.
Privacy
The extension reads your tabs only when the MCP client calls a tool, and sends the result to the native host on your computer. The MCP client you connected receives it and sends it to its AI provider; Inari MCP sends nothing to the developer.
Password, hidden, one-time-code and credit card (
cc-*) fields, and the options of such a select, are masked in what the tools read, and the reading tools refuse selectors that could test them; screenshots are images and are not masked. Cookies are not read.Every action on a page or the browser (clicking, typing, hovering, navigating, opening or closing tabs, …) waits for your approval; reading, waiting, scrolling, highlighting an element and switching tabs do not. The toolbar popup can pause every call (a running one is answered
pausedat once: its work in the page stops at the next step, an operation done by the browser alone runs to its end and its result is dropped) and reopen the permission page to withdraw it.Screenshots taken through the extension stay in
~/.inari-mcp/screenshots/for 30 days (those of headless mode until you delete them).~/.inari-mcp/logs/audit.jsonlrecords the time, tool name and URL of each call, not the page content.npm run teardown -- --purgedeletes everything.Private windows are not supported.
The full text: privacy policy (日本語).
Architecture
A monorepo of four TypeScript packages:
Package | Role |
| Shared zod schemas, action names, and constants used by the other three packages. The subpath |
| Two roles in one executable. By default it is a thin stdio relay: the process Claude Code starts, one per session, which lists the |
| A Native Messaging bridge process between the daemon and the Firefox extension. Starts the daemon if none is running and restarts it only if its process is gone (a slow daemon is never killed). After 10 failed starts it stops trying until a usable daemon appears (for example one a Claude Code session started). |
| The Firefox WebExtension (Manifest V2) that actually clicks, types, and reads the DOM, and shows approval requests in its toolbar popup. Built with esbuild into four bundles ( |
Command flow: Claude Code → stdio relay → HTTP → shared daemon → safety guard → WebSocket → Native Host → Native Messaging → extension background script → content script → page DOM, and the result flows back the same way.
The claude mcp add command stays the same: the relay starts the daemon on first use (and the Native Host does the same when Firefox starts first), and the daemon exits by itself after 10 minutes unused (FOX_DAEMON_IDLE_TIMEOUT). Because every session gets its own relay and they all share one daemon, you can run several Claude Code sessions at the same time without port conflicts. They share the safety guard state too (rate limit, audit log, the last known URL of each tab).
The relay and the Native Host authenticate the daemon, and the daemon them, with HMAC challenge–response over the API token in ~/.inari-mcp/api_token (the token itself never travels): a program that takes over the port cannot pose as the daemon. GET /health needs no authentication; with a nonce it adds a proof, and it reports the protocol version the relay and Native Host check. The relay asks for that proof before every tool call, approval answer and --stop-daemon, and sends the signed request (tool name, arguments, approval token; the approval request ID and the human's answer; the stop request) only when it is valid, so a program on the port does not receive them either. The X-API-Key: <token> header is still accepted by the HTTP API, for manual debugging only (such answers are not signed), except by POST /daemon/shutdown, which takes signed requests only. The daemon refuses to start when FOX_HOST is not a loopback address (127.0.0.0/8, ::1, localhost): its HTTP and WebSocket ports are plain text and must not be reachable from other machines.
Configuration
Environment variables
The names below are the ones the code reads. The daemon takes an empty or blank FOX_HEADLESS, FOX_HEADLESS_STARTUP_TIMEOUT, FOX_EXECUTABLE, FOX_SAFETY_CONFIG, FOX_SCREENSHOTS_DIR, FOX_READ_ONLY, FOX_APPROVAL_ELICITATION or FOX_DAEMON_IDLE_TIMEOUT as not set (the default applies); the Native Host does the same with FOX_SCREENSHOTS_DIR, FOX_SCREENSHOT_RETENTION_DAYS and FOX_NATIVE_HOST_LOG.
Variable | Default | Read by | Description |
|
| daemon, relay, Native Host, uninstaller | Address of the HTTP/WS server. Loopback addresses only (127.0.0.0/8, |
|
| daemon, relay, Native Host, uninstaller | HTTP port (also the daemon's singleton lock). An empty or blank value counts as unset (the default applies) |
|
| daemon, Native Host | WebSocket port the Native Host connects to. An empty or blank value counts as unset (the default applies) |
| unset (all tools) | relay | Tool profiles, comma-separated (see below). Not a safety boundary — it only trims which tool schemas this session registers, to save context tokens. The daemon accepts every tool from every caller; actual restrictions (read-only, URL limits, container scope) come from |
| unset | daemon |
|
|
| daemon | Launch timeout of the headless Firefox in milliseconds (not seconds) |
| Playwright's Firefox | daemon (headless only) | Path to the Firefox binary Playwright launches |
|
| Native Host (extension path), daemon (headless path, | Where screenshots are saved; a relative path is taken from the working directory of the process |
|
| Native Host | At each Native Host start, the screenshots it saved that are older than this many days are deleted, those saved under a |
| unset | daemon |
|
|
| daemon | Alternate path of the safety policy file |
| on | relay, daemon |
|
|
| daemon | Seconds without a relay, a Native Host or a running call before the daemon exits ( |
|
| Native Host | Log file path; |
FOX_CAPS profiles
Profile | Tools |
|
|
|
|
|
|
|
|
| every tool (same as leaving |
fox_safety_status is in no profile: it is always registered. Every other tool is in at least one profile. Unknown profile names are ignored with a warning on stderr; when every name given is unknown, only fox_safety_status is registered.
safety.json
Auto-generated on first run at ~/.inari-mcp/safety.json (or FOX_SAFETY_CONFIG). A key left out of the file, or out of container_scope, takes its default; the camelCase spelling of a key (readOnly) is accepted too; a key not in this table (a typo such as readonly) makes the file unusable (see below):
Key | Default | Meaning |
|
| Safety guard on: the checks of URLs and the URL lists, |
|
| Refuse the tools that change page or browser state (input, navigation, tabs); observation, waiting and UI-only tools ( |
|
| Allow |
|
| Rate limit, shared by all sessions ( |
|
| Write |
|
| URLs to refuse |
|
| When not empty, only these URLs are allowed |
|
| Sites where reading tools and screenshots also ask a human first (acting tools always do, on every site; see Human approval) |
|
| Layer 0: restrict the agent to one Firefox container |
URL patterns are matched against the parsed URL, not the raw string, so example.com.attacker.net or a query string that mentions a host does not match:
Pattern | Matches |
| the host and every subdomain |
| subdomains only |
| ... whose path starts with |
| ... over that scheme only |
| ... on that port only (a URL without a port has its scheme's default: |
| an IPv6 address, in brackets |
| a case-insensitive regex over the normalised URL ( |
Host and scheme compare case-insensitively. In allowed_url_patterns a path matches as written (at a segment boundary: /admin does not cover /administrator); in blocked_url_patterns and confirm_read_sites it matches any continuation after decoding %xx escapes, collapsing repeated slashes and folding case. Any other entry, such as a bare regular expression (paypal\.com), is an error in every list: write it as a host pattern, or as re: for a whole-URL match.
If safety.json cannot be used (unreadable, invalid JSON, an unknown key — protected_url_patterns and confirm_protected_actions from a KitsuneMCP safety.json included —, a wrong type, an unusable URL pattern), the daemon logs the error to stderr (daemon.log) and refuses every tool except fox_safety_status, which shows the error, until the file is fixed and the daemon restarted. A URL pattern is unusable when it is empty, an invalid re: regex, a host pattern with an invalid port (1–65535) or IPv6 address, *. before an IP address, * in a path other than a trailing /*, ? / # (or %3F / %23) in a path, or an entry that is not a host pattern or re:. A dot-less host pattern in blocked_url_patterns or confirm_read_sites (paypal matches only the host paypal and its subdomains) is used with a warning, shown in fox_safety_status (patternWarnings). Navigation targets are checked as well: only http://, https:// and about:blank are allowed, URLs with a user name or password before the host are refused, and a result whose final URL (after a redirect or a click) is blocked is withheld. A tab on a page without a host (view-source:, blob:, data:, …; about: pages and file: excepted) matches no URL pattern, so tools that act on it are refused there; reading tools still work.
Human approval
Every tool that changes a page or the browser asks you first, on every site — there is no list of sites where it does not: fox_click, fox_type, fox_press_key, fox_set_value (input and select), fox_navigate (every action), fox_tab with action: "create" (with or without a container) or "close", fox_interact with action: "hover", and fox_handle_dialog. The call is not run; the daemon opens a one-time approval request, and your Claude Code session asks you whether to allow this operation — the tool, the site and the arguments:
in Claude Code's own confirmation dialog (MCP elicitation), when the client supports it and
FOX_APPROVAL_ELICITATIONis not turned off;otherwise in Firefox: the Inari MCP toolbar button shows an amber count (plus an OS notification), and you approve or deny in its popup. The popup is also used when the elicitation request fails (the client does not know the method, a transport error); when the dialog times out, the call ends unapproved.
Reading, waiting and UI-only tools run without asking: fox_read, fox_find, fox_screenshot, fox_snapshot, fox_tabs, fox_wait, fox_interact with action: "scroll" or "highlight", fox_tab with action: "switch", fox_detect_captcha, fox_safety_status. On a site listed in confirm_read_sites the tools that read a page (fox_read, fox_find, fox_screenshot, fox_snapshot) ask as well; fox_screenshot with scope: "all_tabs" leaves such tabs out instead. At most 32 approval requests can wait at a time.
The site is the host name of the page the call acts on (for fox_navigate with a url and for fox_tab create, of the URL it opens; a new tab without a URL is about:, a local file file:). When the daemon has not yet seen which page the target tab is on (right after it started, or a tab it has never listed), the call cannot be approved yet, and the model is told to call fox_tabs first. The Firefox extension checks the approval again against the tab's real URL and refuses an acting command that was not approved for that tab's site, whatever the daemon sends; the headless backend does the same right before the command runs. If the tab moved to another site meanwhile, you are asked again for that site.
An approval covers one call of that tool with exactly those arguments on that site, must be given before the request expires (120 s; the Firefox popup waits at most 60 s), and is carried by a one-time token that only the relay sees (the model never does). Every acting call is asked separately, also when the same tool repeats on the same tab; there is no "allow for a while". You are shown the arguments in full: invisible and text-direction characters (bidi controls, zero-width and tag characters, U+2028 / U+2029) appear as \u escapes under a warning line, and a call whose arguments do not fit (more than 160 in one value, or 800 for all the name: value lines; counted in UTF-16 code units with each value as JSON — quotes and escapes included, so a newline in a value counts 2 and an emoji 2) is not offered for approval at all — the model is told so. This means long arguments cannot be approved: a long URL for fox_navigate or long text for fox_type. Text can sometimes be split into shorter calls; a URL cannot. Nothing in the page — buttons or fox_click — can approve it. One risk is accepted while elicitation approval is on: the answer reaches the daemon over local HTTP, so a local process that can read ~/.inari-mcp/api_token could approve its own requests. Set FOX_APPROVAL_ELICITATION=0 to require the Firefox popup (headless mode then cannot approve at all).
fox_request_approval is a separate, plain yes/no question to you in the same popup; its answer approves no tool call.
Whichever side starts the daemon first, its config wins
The daemon isn't started fresh per session — whoever gets there first (your Claude Code relay, or Firefox via the Native Host) starts it, and everyone else just talks to that one running daemon. That matters for env vars: FOX_READ_ONLY, FOX_HEADLESS, etc. are only read from the environment of whichever process actually launched the daemon. (FOX_APPROVAL_ELICITATION is read on both sides: each relay from its own environment, the daemon from the one it was started with; see Environment variables.) A daemon started by the Native Host gets Firefox's environment, not that of any Claude Code session. If Firefox starts the daemon first, a later Claude Code session's FOX_READ_ONLY=1 in its MCP config does nothing — you'll see a warning (at session start and in fox_safety_status), but the existing daemon keeps running with its original settings.
safety.json doesn't have this problem: it's read from the same fixed path (~/.inari-mcp/safety.json by default) no matter which side starts the daemon. If you need read_only or similar to reliably take effect, set it in safety.json rather than relying on the env var.
Status
Inari MCP for Firefox 0.1.0 is the first version listed on addons.mozilla.org. The list below is the history it shares with KitsuneMCP.
Done:
Design and multi-angle review
Monorepo scaffolding (P1)
Safety guard, 8 layers (P2)
MCP server core + the tool schemas and handlers (P3)
Headless Playwright backend (P4)
Native messaging host (P5)
Extension ported to TypeScript (P6)
Firefox-native APIs: container tabs,
browser.find,tabs.captureTab(P7)Extension path wired end-to-end, plus the relay + shared-daemon split so several Claude Code sessions can drive Firefox at the same time without fighting over ports (P7.5)
Real-device E2E on Windows 11 + Firefox (2026-09-27)
Installer — first as PowerShell / bash scripts, verified end-to-end on real Windows 11 hardware and on Linux (WSL2 Ubuntu 24.04 with the deb build of Firefox 156, 2026-09-28); since replaced by one Node.js installer and uninstaller for every platform (
scripts/install.mjs/scripts/uninstall.mjs,npm run setup/npm run teardown) that needs no PowerShell. The macOS paths haven't been run on real macOS yet.scripts/e2e/has the smoke tests used for the Linux checkDiagnostics (
--doctor),--help/--version, and one version (0.1.4) for every package and the extensionDistribution prep: the real Extension ID is confirmed (
inari-mcp@elys0912.github.io), AMO'sdata_collection_permissionsis set, andweb-ext lintpasses with 0 errors/warnings (P8)Human approval moved out of the page (toolbar popup / MCP elicitation, one-time approval tokens), and HMAC mutual authentication between the daemon, the relays and the Native Host
Prepared for addons.mozilla.org as Inari MCP for Firefox: no script execution, no logging, approval for every action, a permission page, private browsing not allowed (0.1.0)
Not done yet:
Running the installer on real macOS
Tool availability
Tools | Extension path | Headless |
| not implemented | returns the accessibility tree as YAML text |
| not implemented | works |
| work | not supported |
| work | not supported (no containers) |
| works | always refused (nobody to ask) |
everything else | works | works, but many parameters are ignored (listed in the result's |
A tool that is not implemented returns an error (Not implemented in extension: … / Unsupported headless action: …) rather than doing nothing silently.
Current limitations are listed in docs/known-issues.md (日本語).
Development
npm ci
npm run build # every package; the Extension is bundled by scripts/build-extension.mjs
npm run typecheck # type check of the Extension (tsc --noEmit), then of the tests (typecheck:tests)
npm run lint:install && npm run lint # ESLint (TypeScript 6 in tools/lint; the build uses TypeScript 7)
npm test # Vitest, without e2e
npm run lint:ext # web-ext lint of packages/extension
npm run pack:ext # ESLint, then the Extension zip for AMO + SHA-256 of its files + web-ext lint (see docs/BUILD.md)
npm run pack:source # the source zip for AMO (needs a clean working tree)
npm run e2e:extension # the Extension loads into Firefox (needs Playwright's Firefox)Build, packages and checks in detail: docs/development.md (日本語).
npm audit reports 11 vulnerabilities, all in the dependency tree of web-ext (a devDependency); they are accepted. Audit with npm audit --omit=dev (0 vulnerabilities): the distributed parts (mcp-server, native-host, the extension .xpi) do not contain web-ext.
npm test runs the Vitest suites (unit tests per package, contract tests in tests/contract/, installer tests in tests/scripts/). Each test file gets a temporary home directory, so the tests never touch your ~/.inari-mcp/. Running Inari MCP needs Node.js 20 or later; the reproducible build for the AMO review uses Node.js 24.x and npm 11.x (.nvmrc, docs/BUILD.md).
End-to-end checks, kept out of npm test:
Command | What it checks |
| relay → daemon → headless Firefox over MCP ( |
| the extension loads into Firefox as a temporary add-on ( |
| daemon → Native Host → extension in your real Firefox (installer run, extension loaded; local test pages on 127.0.0.1; approve each acting call in the Inari MCP popup) |
| the basic features as an MCP client against a local test page, with each action (opening the tab, the click, closing the tab) approved in the popup ( |
| the installed Native Messaging host starts and reaches the daemon |
Links
Issues — questions and bug reports
KitsuneMCP (private) — the edition this one split off from
Credits
Forked from nanogenomic/ClaudeCodeBrowser, originally written in Python. This project ports it to TypeScript, narrows it to Firefox, and extends the safety model (container isolation, human approval outside the page with one-time tokens, HMAC mutual authentication between its processes). Thanks to the original author for doing the hard part first.
License
MIT — see LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Remote streamable-HTTP MCP server running on a single Cloudflare Worker. Your assistant gets live Airbnb, Amazon, Booking.com, Google Flights, Maps and Reddit data, social search on X, Instagram and TikTok, the Meta Ad Library, and image/video generation without any keys. Connect your own accounts to let it send WhatsApp or Telegram messages, work an IMAP inbox, manage Meta Ads campaigns and publish to X and LinkedIn. OAuth 2.1 with PKCE; stored credentials are AES-256-GCM encrypted.
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
A paid remote MCP for AI agent browser approval MCP, built to return verdicts, receipts, usage logs,
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceEnables Claude Code to control a real browser using AI for web scraping, competitive intelligence, and UX auditing through the MCP protocol.-
- AlicenseNot gradedqualityAmaintenanceEnables 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.1MIT
- AlicenseBqualityCmaintenanceEnables browser control through your existing signed-in Chrome profile, letting MCP clients like Claude Code manage tabs, navigate, inspect pages, and perform real user interactions via tool calls. Works with any model backend without needing Playwright or a fresh browser profile.21MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to see and control the user's real Chrome/Brave/Edge profile over MCP, so they can read pages, click, type, take screenshots, audit layouts, debug CSS, and scrape paginated or infinite-scroll data. Because it drives the normal browser via the DevTools protocol with real input events, it works on modern JavaScript apps and on sites where the user is logged in.19 npm1MIT