camofox-browser MCP server
A standalone Model Context Protocol server that exposes camofox-browser to any MCP-compatible host — Claude Code, Cursor, etc. — without requiring OpenClaw.
It mirrors the existing OpenClaw plugin 1:1: same 11 tool names, identical JSON-Schema parameters, and the same REST routes. Whether an agent reaches camofox via OpenClaw or MCP, the behavior is identical.
The initial MCP server implementation was contributed by @epicsagas.
Architecture
The MCP server is a thin stdio client over the camofox REST server. Two pieces:
- REST server (
server.js) — launches Camoufox, exposes the HTTP API on:9377. Run once, it stays up. - MCP server (
mcp/server.mjs, exposed as thecamofox-browser-mcpbin) — translates MCP tool calls into REST calls. Claude Code spawns one per session.
Registering the MCP server does not require being inside the camofox-browser checkout. The examples below work from any directory.
mcp/ is also an independently installable package (@askjo/camofox-browser-mcp, its own package.json). It depends on nothing but @modelcontextprotocol/sdk — no camoufox-js, playwright-core, express, or the ~300MB browser binary download that the core server pulls in. Tool names, JSON-Schema parameters, REST routes, and response shaping are defined in mcp/lib/tool-contracts.mjs, which ships inside the standalone package. The OpenClaw plugin (plugin.ts) imports the same canonical module, so the two hosts cannot drift. See Option D below if you only want the MCP adapter (e.g. pointing CAMOFOX_BASE_URL at a REST server running elsewhere).
1. Start the REST server
Clone the repo and start the server (one-time binary download on first run):
git clone https://github.com/jo-inc/camofox-browser && cd camofox-browser
npm install # downloads Camoufox (~300MB) on first run
npm start # → http://localhost:9377
This stays running in the background. It does not need to be your cwd afterwards.
2. Install the camofox-browser-mcp bin
The MCP server is the same for every host — what differs is only the config file you paste into. First, make the camofox-browser-mcp bin available on your PATH. Pick one:
# Option A — npm link (if you have the source checkout; picks up local edits)
cd camofox-browser && npm link
# Option B — global install (no source checkout needed)
npm install -g @askjo/camofox-browser
# Option C — npx (no install; runs the published standalone adapter)
# Use this wherever a command is expected below.
npx -y @askjo/camofox-browser-mcp
# Option D — MCP adapter only, no core server deps (lightest footprint)
# Skips camoufox-js/playwright-core/express and the browser binary download —
# use this if a camofox-browser REST server is already running (locally or
# remotely) and you only need the stdio adapter.
git clone https://github.com/jo-inc/camofox-browser && cd camofox-browser/mcp
npm install # installs only @modelcontextprotocol/sdk (~24MB, no postinstall)
npm link # camofox-browser-mcp now resolves from any directory
Verify the bin resolves from any directory:
which camofox-browser-mcp # → .../bin/camofox-browser-mcp
3. Register with your host
All five hosts speak standard MCP, so they all run the same camofox-browser-mcp bin. Pick your host's config snippet below.
Claude Code
# CLI (user scope = available in every project)
claude mcp add camofox-browser -s user -- camofox-browser-mcp
Or in ~/.claude.json (user) / .mcp.json (project, checked in):
{
"mcpServers": {
"camofox-browser": {
"command": "camofox-browser-mcp"
}
}
}
Codex CLI
~/.codex/config.toml (user) or .codex/config.toml (project, trusted dirs only):
[mcp_servers.camofox-browser]
command = "camofox-browser-mcp"
env = { CAMOFOX_BASE_URL = "http://localhost:9377" }
Antigravity / agy
Global ~/.gemini/config/mcp_config.json or workspace .agents/mcp_config.json:
{
"mcpServers": {
"camofox-browser": {
"command": "camofox-browser-mcp"
}
}
}
Cursor
Global ~/.cursor/mcp.json or project .cursor/mcp.json (checked in):
{
"mcpServers": {
"camofox-browser": {
"command": "camofox-browser-mcp"
}
}
}
(Or via UI: Settings → Cursor Settings → MCP → Add New MCP Server.)
opencode
opencode.json in the project root, or global ~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"camofox-browser": {
"type": "local",
"command": ["camofox-browser-mcp"]
}
}
}
Common: env vars and from-source fallback
If you need cookie import or a non-default REST URL, add env. Example for Claude Code:
claude mcp add camofox-browser -s user \
--env CAMOFOX_BASE_URL=http://localhost:9377 \
--env CAMOFOX_API_KEY=<key> \
-- camofox-browser-mcp
For the other hosts, add the same keys to the env / environment field of that host's snippet.
From-source fallback (only if you're inside the checkout and haven't linked the bin):
# Replace `camofox-browser-mcp` with `node /absolute/path/to/mcp/server.mjs`
claude mcp add camofox-browser -- node /Users/you/src/camofox-browser/mcp/server.mjs
⚠️ Always prefer the
camofox-browser-mcpbin (options A/B/C/D above). Thenode ./mcp/server.mjsform is path-dependent — relative paths break outside the checkout.
4. Verify
| Host | How to verify |
|---|---|
| Claude Code | /mcp — camofox-browser shows connected |
| Codex CLI | codex then check MCP server list |
| agy | /mcp overlay in the agy CLI |
| Cursor | Settings → MCP — server shows green |
| opencode | opencode mcp list |
You should see 11 tools: camofox_create_tab, camofox_snapshot, camofox_click, camofox_type, camofox_navigate, camofox_scroll, camofox_screenshot, camofox_evaluate, camofox_list_tabs, camofox_close_tab, camofox_import_cookies.
Tools
| Tool | Purpose |
|---|---|
camofox_create_tab |
Open a URL → returns tabId |
camofox_snapshot |
Accessibility snapshot + element refs (e1, e2, ...) + screenshot |
camofox_navigate |
Go to a URL or use a search macro (@google_search, @reddit_search, ...) |
camofox_click |
Click by element ref (e1) or CSS selector |
camofox_type |
Type text into a ref/selector, optional pressEnter |
camofox_scroll |
Scroll by pixels (unreliable on lazy-load pages — prefer camofox_evaluate) |
camofox_screenshot |
Standalone screenshot |
camofox_evaluate |
Run JS in page context — extract data, call page APIs, scroll via window.scrollTo |
camofox_list_tabs |
List open tabs in this session |
camofox_close_tab |
Close a tab |
camofox_import_cookies |
Import a Netscape cookie file (needs CAMOFOX_API_KEY) |
Workflow
Every interaction follows the same shape — snapshot before you act:
create_tab({ url })→tabIdsnapshot({ tabId })→ element refs (e1,e2, ...)click/typeusing those refssnapshotagain to read the new stateclose_tabwhen done
Element refs are unambiguous and preferred over CSS selectors — a selector that matches multiple elements returns 422 strict mode violation, in which case re-snapshot and click by ref.
Environment variables
| Var | Default | Purpose |
|---|---|---|
CAMOFOX_BASE_URL |
http://localhost:9377 |
REST server URL |
CAMOFOX_USER_ID |
mcp-<random> |
Session isolation (one MCP server = one camofox session) |
CAMOFOX_SESSION_KEY |
default |
Tab partition within the user |
CAMOFOX_ACCESS_KEY |
(unset) | Global bearer token gating the REST server (superkey). If set, forwarded as Authorization: Bearer <key> on every tool call automatically — must match the REST server's own CAMOFOX_ACCESS_KEY. Without this, a globally-authenticated REST server rejects all tool calls except cookie import. |
CAMOFOX_API_KEY |
(unset) | Self-chosen secret gating cookie import. Optional on localhost; required on remote/production and must match the REST server's CAMOFOX_API_KEY. Only affects camofox_import_cookies. |
Troubleshooting
503 session_expired/tab create timed out— the REST server's browser session died (often after a prior failed call destabilized it). Restartnpm start.camofox_scrollreturns{ok:true}but the page doesn't move — expected on lazy-load / virtual-scroll pages; the server'smouse.wheelno-ops there. Usecamofox_evaluatewithwindow.scrollTo/scrollBy.422 strict mode violation ... resolved to N elementsonclick— CSS selector matched multiple elements. Re-snapshot and click by element ref.403 Forbiddenoncamofox_import_cookies— key mismatch between REST server and MCP server, or hitting a remote server withoutCAMOFOX_API_KEY.401/403on every tool call, not just cookie import — the REST server hasCAMOFOX_ACCESS_KEYset. Set the sameCAMOFOX_ACCESS_KEYin the MCP server's env (see table above) — it's forwarded automatically asAuthorization: Beareronce set.
Testing (developer)
Two layers, covering different things:
# 1. `test:mcp` installs the standalone adapter's locked dependencies, then
# runs the in-repository and packed-tarball handshake tests. Neither needs
# a REST server.
npm run test:mcp
# 2. Mock-HTTP contract tests — verifies the actual REST traffic each tool
# produces: routes, methods, request bodies, CAMOFOX_ACCESS_KEY/CAMOFOX_API_KEY
# auth headers, cookie parsing (Netscape file → `{ cookies }` body, never a
# path), screenshot decoding, and error handling. Mocked REST server — no
# live camofox-browser process needed. This is what makes the "OpenClaw and
# MCP send identical requests" claim verifiable, not just asserted.
NODE_OPTIONS='--experimental-vm-modules' npx jest tests/unit/mcp-contracts.test.js
The packed-tarball check installs the generated @askjo/camofox-browser-mcp tarball in an empty directory before its handshake. It catches imports that reach outside the standalone package.