Camoufox Canary MCP
Click on "Install 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., "@Camoufox Canary MCPcreate a new Camoufox browser session"
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.
Camoufox Canary MCP
An experimental, single-container MCP service for creating isolated Camoufox browser sessions and connecting Playwright to them through one published port. It is intended for a trusted HomeLAN with fewer than ten users.
The service exposes:
Streamable HTTP MCP at
http://HOST:8000/mcpproxied browser WebSockets at
ws://HOST:8000/browser/SESSION_IDan unauthenticated health check at
http://HOST:8000/health
Quickstart
Docker Compose is the shortest supported path. Port 8000 is intentionally fixed in Compose so the published mapping, healthcheck, and service listener agree.
Copy the fully commented example configuration, then edit at least the public URL for access from another machine. Setting a bearer token is recommended:
cp .env.example .env
${EDITOR:-vi} .envdocker compose up --build --detach --wait
curl --fail http://127.0.0.1:8000/healthThe expected health response is {"status":"ok"}. Stop and remove the service
with:
docker compose downTo build the image used by the end-to-end test:
docker build -t camoufox-canary-mcp:test .For another machine on the LAN, set the public URL to the address its clients can reach before starting Compose:
CAMOUFOX_MCP_PUBLIC_URL=ws://192.168.1.20:8000 docker compose up --build --detach --waitThe image runs as the unprivileged app user (UID/GID 10001) and publishes
only the MCP service port. Browser ports 9100–9109 remain private inside the
container.
Related MCP server: camofox-browser-mcp
Manual Docker deployment
Build the image and create a local environment file from the documented template:
docker build -t camoufox-canary-mcp:local .
cp .env.example .env
${EDITOR:-vi} .envIn .env, replace localhost in CAMOUFOX_MCP_PUBLIC_URL with the Docker
host address reachable by MCP clients, for example 192.168.1.20.
Start the container with an init process and 2 GiB of shared memory:
docker run --detach \
--name camoufox-canary-mcp \
--init \
--shm-size=2g \
--publish 8000:8000 \
--env-file .env \
--health-cmd="python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3)\"" \
--health-interval=30s \
--health-timeout=5s \
--health-retries=3 \
camoufox-canary-mcp:localCheck startup, health, and logs:
docker ps --filter name=camoufox-canary-mcp
curl --fail http://127.0.0.1:8000/health
docker logs --follow camoufox-canary-mcpStop and remove the manual deployment with:
docker stop camoufox-canary-mcp
docker rm camoufox-canary-mcpTo publish a different host port, change only the left side of the mapping,
for example --publish 18000:8000, and set the public URL to
ws://HOST:18000. Keep CAMOUFOX_MCP_LISTEN_PORT=8000 inside the container.
Do not publish the private browser port range.
Configuration
All variables are optional. Compose deliberately does not pass through
CAMOUFOX_MCP_LISTEN_PORT; its container-side value must remain 8000.
See .env.example for a copy-ready configuration containing
every variable, valid values, examples, and operational notes.
Variable | Default | Valid values and meaning |
|
| Non-empty Uvicorn bind address. |
|
| Integer |
|
| Client-reachable |
| unset | Non-blank optional bearer token protecting MCP and browser WebSocket requests. |
|
| Positive integer maximum for tracked browser sessions. Effective capacity cannot exceed the private port count. |
|
| Positive integer seconds since the last proxied WebSocket traffic before cleanup. |
|
| Positive integer seconds allowed for the private browser server to bind. |
|
| Positive integer seconds between idle-session cleanup passes. |
|
| Integer |
|
| Integer |
|
| Exactly |
|
| Comma-separated MCP |
| empty | Comma-separated MCP |
The private port range is inclusive. Configure at least as many ports as the
desired session count; otherwise the port range becomes the effective limit.
All integer settings reject zero, negative, and non-integer values. Port values
outside 1–65535 are also rejected. Unknown environment variables are ignored.
Example authenticated Compose start:
CAMOUFOX_MCP_AUTH_TOKEN='replace-this-token' \
CAMOUFOX_MCP_PUBLIC_URL='ws://192.168.1.20:8000' \
docker compose up --build --detach --waitSend Authorization: Bearer replace-this-token on both MCP HTTP requests and
the Playwright WebSocket connection. The token and any proxy credentials are
never returned in session metadata.
MCP tools
create_session accepts this input shape; every field is optional:
{
"proxy_server": "http://proxy.example:8080",
"proxy_username": "user",
"proxy_password": "password",
"os": "linux",
"locale": "en-AU",
"timezone": "Australia/Sydney",
"screen": {
"min_width": 1280,
"max_width": 1920,
"min_height": 720,
"max_height": 1080
}
}proxy_serveracceptshttp,https, orsocks5. Username and password must be supplied together and separately from the URL.osiswindows,macos, orlinux.localemust be a registered language tag such asen-AU,en-US, orde-DE.timezonemust be an IANA timezone available in the image, such asAustralia/Sydney,America/New_York, orEtc/UTC.Every screen bound is a positive integer; each minimum must not exceed its maximum.
Unknown fields are rejected. Supplying a proxy username without a password, or vice versa, is rejected. Proxy credentials embedded in the URL are also rejected; use the separate credential fields.
It returns {id, websocket_url, created_at, expires_at, request}. The other
tool schemas are:
Tool | Input | Result |
|
|
|
|
| one session object |
|
|
|
Connect MCP clients
Replace 192.168.1.20 with the Docker host address reachable from the client.
When CAMOUFOX_MCP_AUTH_TOKEN is set on the server, export the same value in
the shell that starts your MCP client:
export CAMOUFOX_MCP_AUTH_TOKEN='replace-this-token'Do not commit the real token. Omit the authorization setting entirely when
server authentication is disabled. Use an https:// MCP URL when TLS is
terminated by a reverse proxy.
Claude Code
Add the server for the current project without authentication:
claude mcp add --transport http --scope project camoufox \
http://192.168.1.20:8000/mcpFor bearer authentication, create .mcp.json in the project root. Claude Code
expands ${CAMOUFOX_MCP_AUTH_TOKEN} from its environment:
{
"mcpServers": {
"camoufox": {
"type": "http",
"url": "http://192.168.1.20:8000/mcp",
"headers": {
"Authorization": "Bearer ${CAMOUFOX_MCP_AUTH_TOKEN}"
}
}
}
}Run claude mcp list, or use /mcp inside Claude Code, to check the
connection. See the official Claude Code MCP documentation.
Codex CLI
Add an unauthenticated server:
codex mcp add camoufox --url http://192.168.1.20:8000/mcpFor bearer authentication, tell Codex which environment variable contains the token:
codex mcp add camoufox \
--url http://192.168.1.20:8000/mcp \
--bearer-token-env-var CAMOUFOX_MCP_AUTH_TOKENThe equivalent ~/.codex/config.toml entry is:
[mcp_servers.camoufox]
url = "http://192.168.1.20:8000/mcp"
bearer_token_env_var = "CAMOUFOX_MCP_AUTH_TOKEN"Remove bearer_token_env_var when authentication is disabled. Run
codex mcp list, or use /mcp inside Codex, to check the connection. See the
official Codex MCP documentation.
OpenCode
Add this entry to opencode.json for an authenticated connection. OpenCode
uses {env:NAME} for environment-variable substitution:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"camoufox": {
"type": "remote",
"url": "http://192.168.1.20:8000/mcp",
"enabled": true,
"oauth": false,
"headers": {
"Authorization": "Bearer {env:CAMOUFOX_MCP_AUTH_TOKEN}"
}
}
}
}Remove the headers object and oauth when authentication is disabled. Run
opencode mcp list to check the connection. See the official
OpenCode MCP documentation.
These integrations expose the session-management tools listed above. After
create_session returns a websocket_url, browser automation connects to that
URL with Playwright as shown in the following examples.
Python client
Install the client packages at the same versions as the service, then initialize MCP, create a session, and connect Playwright Firefox to the returned URL:
python -m pip install 'mcp==2.0.0' 'playwright==1.60.0'import asyncio
import httpx2
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
from playwright.async_api import async_playwright
async def main() -> None:
token = "replace-this-token" # Use None when authentication is disabled.
headers = {"Authorization": f"Bearer {token}"} if token else {}
async with httpx2.AsyncClient(headers=headers) as http:
async with streamable_http_client(
"http://127.0.0.1:8000/mcp", http_client=http
) as (read, write):
async with ClientSession(read, write) as mcp:
await mcp.initialize()
result = await mcp.call_tool("create_session", {})
session = result.structured_content
assert session is not None
try:
async with async_playwright() as playwright:
browser = await playwright.firefox.connect(
session["websocket_url"], headers=headers
)
page = await browser.new_page()
await page.goto("data:text/html,<title>Camoufox</title>")
print(await page.title())
await browser.close()
finally:
await mcp.call_tool(
"close_session", {"session_id": session["id"]}
)
asyncio.run(main())Connecting does not require downloading a local Firefox binary; the browser runs in the container.
Node Playwright connection
Use the same Playwright version as the image:
npm install 'playwright@1.60.0'After an MCP create_session call has supplied CAMOUFOX_WS_URL:
const { firefox } = require("playwright");
(async () => {
const token = process.env.CAMOUFOX_MCP_AUTH_TOKEN;
const headers = token ? { Authorization: `Bearer ${token}` } : {};
const browser = await firefox.connect(process.env.CAMOUFOX_WS_URL, { headers });
const page = await browser.newPage();
await page.goto("data:text/html,<title>Camoufox</title>");
console.log(await page.title());
await browser.close();
})();Closing the Playwright client is not a substitute for close_session; call the
MCP tool to release the managed session and private port.
Session lifecycle and errors
Each successful create_session starts one private Camoufox process and
reserves one private port. A session remains tracked until close_session, idle
cleanup, application shutdown, or container termination closes that process.
WebSocket traffic refreshes its idle timer; MCP metadata reads do not.
Creation fails when
MAX_SESSIONSis reached or no configured private port remains. Failed startups release their reservation.Unknown session IDs fail
get_session,close_session, and WebSocket connection attempts.If browser termination fails, the session remains tracked so cleanup can retry and its port cannot be assigned unsafely.
Session state is memory-only. Restarting the container closes and forgets all sessions.
Invalid session options are rejected before browser startup. Proxy-related errors are intentionally generic so submitted credentials cannot leak.
HTTP
401means a bearer credential is absent or malformed;403means the supplied token does not match. WebSocket upgrades are rejected under the equivalent conditions.
TLS reverse proxy
The service does not terminate TLS. A reverse proxy must forward normal HTTP, Streamable HTTP responses, and WebSocket upgrades to the same port. For example, with Caddy:
camoufox.example.lan {
tls internal
reverse_proxy 127.0.0.1:8000
}Run the container with:
CAMOUFOX_MCP_PUBLIC_URL=wss://camoufox.example.lan \
CAMOUFOX_MCP_ALLOWED_HOSTS=camoufox.example.lan \
CAMOUFOX_MCP_ALLOWED_ORIGINS=https://camoufox.example.lan \
docker compose up --build --detach --waitClients then use https://camoufox.example.lan/mcp for MCP and receive
wss://camoufox.example.lan/browser/SESSION_ID from create_session. Keep
bearer authentication enabled unless the proxy and network independently
restrict every client. Certificate trust for Caddy's internal CA must be
installed on each client.
Virtual-headless behavior
The default virtual mode gives each session its own Xvfb display while Firefox
runs in headful mode against that display. This preserves the virtual-headless
behavior of the pinned canary without exposing an X11 port. Camoufox generates
a coherent fingerprint automatically, enables humanized input, uses its bundled
uBlock Origin only, and enables GeoIP alignment when a proxy is supplied.
Set CAMOUFOX_MCP_HEADLESS=native only when Firefox's native headless mode is
preferred over the stealth-oriented default.
Verification
Install the locked test environment and run the unit suite (the Docker E2E is collected but skipped by default):
uv sync --group test
.venv/bin/python -m pytest -q
docker compose configBuild the named image and opt in to the real non-root, virtual-headless MCP and Playwright WebSocket test:
docker build -t camoufox-canary-mcp:test .
CAMOUFOX_MCP_RUN_E2E=1 \
CAMOUFOX_MCP_E2E_IMAGE=camoufox-canary-mcp:test \
.venv/bin/python -m pytest tests/e2e/test_container.py -vThe E2E always removes its test container, including after failures.
Upstream pins and updates
The build checks out Camoufox PR #707
at d83cbb373b63ffc063e068d89efcf78620ba1a53, then cherry-picks the integrity
verification change from PR #716
at 65cda21b4fc10d8eeaec46d8f82c1b2a9fbb0791.
To update, first verify the candidate revisions are compatible. Then change the
two constants in scripts/build-camoufox.sh, the matching OCI labels in the
Dockerfile, and the assertions in tests/test_build_pins.py. Rebuild the test
image and run the unit suite, docker compose config, and the opt-in E2E command
above before using the new image.
Limitations and troubleshooting
This is a canary build from unmerged upstream revisions. State is in memory; restarting the single process closes all sessions. There is no built-in TLS, persistence, multi-host coordination, or browser-action MCP tool set.
A healthy service with a failing
create_sessionusually needs more startup time or memory, or the requested fingerprint constraints may be incompatible. Keep the 2 GiB shared-memory setting and inspect the child-process diagnostic withdocker compose logs --tail=200 camoufox-canary-mcp.HTTP 401 means the bearer header is missing or malformed; 403 means the token is wrong. The same header is required in Playwright
connectoptions.A Playwright protocol/version error usually means the client differs from
1.60.0. Match the pinned version shown above.If session URLs point at
localhostfrom another computer, setCAMOUFOX_MCP_PUBLIC_URLto the LAN-reachablews://or reverse-proxiedwss://address.For non-local MCP deployments, set explicit comma-separated
CAMOUFOX_MCP_ALLOWED_HOSTSandCAMOUFOX_MCP_ALLOWED_ORIGINSvalues that match the client-visible host and origin.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityAmaintenanceAnti-detection browser automation MCP server. 18 tools wrapping CamoFox REST API with stealth fingerprinting that passes bot detection.4730998MIT
- Alicense-qualityBmaintenanceMCP server for controlling a local camofox-browser instance, enabling LLM agents to perform web automation tasks such as navigation, interaction, snapshotting, and content extraction.132MIT
- AlicenseAqualityAmaintenanceAn MCP server that runs concurrent, session-isolated Playwright browser contexts, so many agents can each drive their own browser at the same time without colliding.23171MIT
- Flicense-qualityCmaintenanceA local MCP server for authorized browser automation using Camoufox, managing multiple tabs and providing 21 browser tools for interaction.
Related MCP Connectors
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
MCP (Model Context Protocol) server for Appwrite
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/midnightphreaker/CamouFox-Canary-MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server