OpenChrome is an MCP server that enables multiple Claude Code sessions to control Chrome simultaneously without "Detached" errors.
- Node.js >= 18
- Google Chrome (stable or Chromium)
npm install -g openchrome-mcp- Start Chrome with remote debugging enabled:
google-chrome --remote-debugging-port=9222- Add OpenChrome to your Claude Code MCP config (
~/.claude/.mcp.json): A single-page walkthrough from installation to running your first verifiable contract and recalling your first skill. Targets v1.11+.
npm install -g openchrome-mcp@latest
openchrome --version # should print 1.11.1 or newer
openchrome setup # auto-configures your MCP client (Claude Code by default)For other MCP clients:
openchrome setup --client codex # Codex CLI
openchrome setup --client opencode # OpenCodeRestart your MCP client. That's it for the core install — the next sections are optional.
For manual Codex CLI setup, run openchrome config --client codex and add
the printed [mcp_servers.openchrome] block to ~/.codex/config.toml.
Open a fresh chat in your MCP client and ask it to navigate somewhere:
"oc navigate https://example.com and read the page title"
You should see the agent call navigate and read_page. The default v1.11
install brings four extra MCP tools beyond v1.10:
oc_assert— run a single contract assertion against page stateoc_evidence_bundle— capture DOM + screenshot + network + console snapshotoc_skill_record— store a reusable skill recordoc_skill_recall— retrieve skill records for a domain
…plus one new MCP resource: openchrome://skill-graph/<domain> (read-only).
A contract is a small JSON declaring what "success" looks like on a page.
Below is a minimal example you can pass inline to oc_assert:
{
"id": "homepage_loaded",
"domain": "example.com",
"assertions": [
{ "kind": "url", "equals": "https://example.com/" },
{ "kind": "dom_count", "selector": "h1", "gte": 1 },
{ "kind": "dom_text", "selector": "h1", "contains": "Example Domain" }
]
}Ask your agent:
"oc, navigate to https://example.com then oc_assert this contract"
Expected verdict: pass. If you change dom_text to look for
"Different Domain", the verdict is fail and failed_assertions shows
exactly which check failed and what was actually observed.
When you want a richer record of page state (for debugging or for replay):
"oc, run oc_evidence_bundle with include=['dom','screenshot','network','phash']"
Returns:
{
"bundle_id": "evb_2026-05-12T05-30-22_abc123",
"path": "/Users/<you>/.openchrome/evidence/evb_.../",
"size_bytes": 184320,
"parts": ["dom.txt", "screenshot.png", "network.jsonl", "phash.txt"]
}The bundle directory is a flat collection of files. No SQL or special tooling needed to inspect them.
Skills are reusable named recipes for accomplishing a goal on a domain:
"oc, on https://example.com, save a skill named 'open-homepage' with these steps:
1. navigate to https://example.com
2. wait for h1
3. oc_assert {<homepage_loaded contract>}"
The agent calls oc_skill_record({ domain, name, steps, contract_id }). Later:
"oc, oc_skill_recall for example.com"
Returns up to 20 skills for the domain, ordered by recency.
The skill state graph is a read-only MCP resource:
openchrome://skill-graph/example.com
Any MCP client that supports resources can subscribe / fetch the JSON snapshot. Useful for dashboards or for the agent to plan a multi-step recovery path.
Pilot adds: contract runtime with retry, handoff token + encrypted persistence, multi-model voting framework, and a background skill curator.
Edit your MCP client config to add --pilot:
{
"mcpServers": {
"openchrome": {
"command": "openchrome",
"args": ["serve"]
"args": ["serve", "--auto-launch", "--pilot"]
}
}
}- Or use auto-launch (Chrome starts automatically):
{
"mcpServers": {
"openchrome": {
"command": "openchrome",
"args": ["serve", "--auto-launch"]
}
}
}When OpenChrome fails to start or behaves unexpectedly, run the built-in diagnostic command:
openchrome doctorThis checks your entire environment in one pass and prints an actionable report:
=== openchrome doctor ===
[ ok ] Node.js version v20.11.0 (required: >=18.0.0)
[ ok ] ~/.openchrome/ writable /Users/you/.openchrome
[ fail ] Chrome binary Chrome executable not found on this system
Fix: Install Google Chrome, or set CHROME_PATH env var to the binary path
[ ok ] CDP port 9222 Port 9222 is free
[ ok ] PID lock file No PID file at /tmp/openchrome-9222.pid
[ ok ] Orphaned Chrome processes No orphaned openchrome-managed Chrome processes found
[ ok ] Chrome profile lock No lock files in /Users/you/Library/Application Support/Google/Chrome
[ ok ] Disk space 18432 MB free on /Users/you/.openchrome filesystem
[ skip ] macOS permissions (TCC) TCC.db not readable (Full Disk Access not granted — this is normal)
[ ok ] Local network (loopback) localhost → 127.0.0.1; loopback TCP ok
[ skip ] Optional native deps No optional dependencies declared
Doctor: 8 ok, 0 warn, 1 fail, 2 skip
Chrome binary not found
[ fail ] Chrome binary Chrome executable not found on this system
Fix: Install Google Chrome, or set CHROME_PATH env var to the binary path
Fix: Install Chrome from https://www.google.com/chrome, or point to your Chrome binary:
export CHROME_PATH=/path/to/chrome
openchrome doctorStale PID lock from a crashed session
[ fail ] PID lock file Stale PID(s) 99999 in /tmp/openchrome-9222.pid (no live process)
Fix: Remove stale lock: rm /tmp/openchrome-9222.pid (or run: openchrome reap)
Fix: Remove the stale lock file:
rm /tmp/openchrome-9222.pid
# or
openchrome reapCDP port already in use by another process
[ warn ] CDP port 9222 Port 9222 is in use (held by PID 12345) but no CDP endpoint found
Fix: Free port 9222 or set CHROME_PORT to a different port
Fix: Either stop the process holding the port, or use a different port:
# Kill the holder
kill 12345
# Or use a different port
CHROME_PORT=9223 openchrome serveFor scripting or CI, use --json to get a structured DoctorReport:
openchrome doctor --json | jq '.summary'Use --check <id> (repeatable) to run only specific checks:
openchrome doctor --check chrome-binary --check chrome-portTo also test outbound connectivity (disabled by default to avoid implicit outbound requests):
openchrome doctor --remote| ID | What it checks |
|---|---|
node-version |
Node.js meets engines.node requirement |
home-writable |
~/.openchrome/ exists and is writable |
chrome-binary |
Chrome binary found and reports a supported version |
chrome-port |
CDP port 9222 is free or hosts a live CDP endpoint |
pid-lock |
PID lock file is absent or owned by a live process |
orphan-chrome |
No openchrome-managed Chrome processes are orphaned |
profile-lock |
Chrome profile directory is not locked by another Chrome |
disk-space |
Free space on ~/.openchrome/ filesystem ≥ 500 MB |
macos-perms |
(macOS) Screen Recording / Accessibility hint for headed Chrome |
network-local |
Loopback DNS and TCP work correctly |
network-remote |
(opt-in) Outbound HTTPS to googleapis.com works |
optional-deps |
Optional native modules load correctly |
Exit codes: 0 = all ok, 1 = warnings only, 2 = at least one failure.
Restart your MCP client. When the server boots, stderr prints:
[harness] core+pilot enabled (trace,state_graph,contract_runtime,handoff_persist,perception_voting,skill_curator)
You can turn an individual family off without losing the rest:
OPENCHROME_PERCEPTION_VOTING=0 openchrome serve --pilotBy default, the pilot handoff persistence layer uses an ephemeral key generated at boot. Persisted tokens are invalidated on every restart, which is the safer default for laptops and CI.
For long-running servers where cross-restart persistence matters, point at a key file you manage:
# Generate a 32-byte key once
openssl rand -out ~/.openchrome/handoff-key.bin 32
chmod 600 ~/.openchrome/handoff-key.bin
# Tell the server to use it
export OPENCHROME_HANDOFF_KEY_FILE=~/.openchrome/handoff-key.bin
openchrome serve --pilotThe file is never logged and never embedded in audit records. If the file size is anything other than exactly 32 bytes, the server falls back to ephemeral mode and prints a warning to stderr.
openchrome updateThis runs npm install -g openchrome-mcp@latest and re-runs setup against
your MCP client config so the runtime path stays in sync.
docs/architecture.md— one-page overview of the core / pilot tier split and where each subsystem livesdocs/roadmap/portability-harness-contract.md— the durable design contract every future PR must satisfydocs/releases/v1.11.1.md— full release notes with the cumulative v1.10.4 → v1.11.1 diffopenchrome doctor— diagnose installation issues