# Chrome MCP Background Proxy — Fix Popups, Focus Stealing & Multi-Agent Conflicts > Persistent CDP proxy + entry script that lets chrome-devtools-mcp run against your real, logged-in Chrome without the Chrome 146+ consent popup spamming on every connection, without focus stealing (Target.activateTarget / Page.bringToFront are intercepted, createTarget is forced to background), and without request-ID / event collisions when multiple Claude Code windows or sub-agents share one Chrome. Includes the proxy core (cdp-proxy.mjs v3), entry script, safe cleanup, pre-flight healthcheck, and a launchd-style self-healing watchdog with Feishu alerts. ## Install Merge the JSON below into your `.mcp.json`: # Chrome MCP Background Proxy Fixes three breaking issues that hit anyone running `chrome-devtools-mcp` against their real, logged-in Chrome: 1. **Chrome 146+ remote-debugging consent popup** — appears every time a fresh WebSocket attaches to the debug port. The persistent proxy holds one long-lived connection so the popup only ever fires once (and once again after a Chrome restart). 2. **Focus stealing** — `chrome-devtools-mcp` aggressively calls `Target.activateTarget` and `Page.bringToFront`, yanking the foreground tab away while you're typing. The proxy intercepts both methods and forces `Target.createTarget` to run with `background: true`. 3. **Multi-agent / multi-window conflicts** — when several Claude Code windows (or sub-agents) share one Chrome, request IDs collide and CDP events get delivered to the wrong client. The proxy rewrites every request ID and routes events back to the correct client by `sessionId` / `targetId`. ## Architecture ``` Claude Code (stdio) → chrome-mcp-proxy.sh → chrome-devtools-mcp (connects to the proxy's WebSocket) → cdp-proxy.mjs v3 (port 9401) ├─ Persistent WebSocket to Chrome (popup only fires once) ├─ Request-ID remapping (no cross-client collisions) ├─ Event routing by sessionId (multi-agent isolation) └─ Blocks Target.activateTarget / Page.bringToFront → real Chrome (auto-discovered via DevToolsActivePort) ``` ## Files | File | Role | |------|------| | `chrome-mcp-proxy.sh` | MCP entry script — unsets local proxy env vars, ensures the persistent proxy is running (`nohup` + `disown`), then `exec`s `chrome-devtools-mcp@latest` against the proxy. Wire this into `~/.mcp.json`. | | `cdp-proxy.mjs` | The persistent CDP proxy (Node 18+, only dep is `ws`). Holds one WebSocket to Chrome and multiplexes any number of MCP clients on top. | | `kill-old-chrome-mcp.sh` | Safe cleanup — keeps the newest `chrome-devtools-mcp` process and kills the rest. **Read the iron rules below before running.** | | `chrome-mcp-healthcheck.sh` | Pre-flight check (Chrome up? proxy up? CDP connected? process count sane?). Returns 0/1 and posts a Feishu card on failure. Wire it as a pre-step before any MCP-driven automation. | | `chrome-mcp-watchdog.sh` | launchd-friendly self-healing loop with a 10-min Feishu alert cooldown. Auto-restarts the proxy and runs cleanup when it drifts. | ## Setup ```bash # 1. Drop the scripts into ~/scripts/ and chmod +x them chmod +x ~/scripts/chrome-mcp-proxy.sh ~/scripts/kill-old-chrome-mcp.sh \ ~/scripts/chrome-mcp-healthcheck.sh ~/scripts/chrome-mcp-watchdog.sh # 2. One-time Chrome setup: open chrome://inspect/#remote-debugging # and tick "Allow remote debugging for this browser instance" # 3. Wire it into ~/.mcp.json (NO env proxy vars — the script unsets them): # "chrome": { # "command": "bash", # "args": ["/Users//scripts/chrome-mcp-proxy.sh", "9401", "9222"] # } # 4. Sanity check curl -s --noproxy '*' http://127.0.0.1:9401/proxy/status | jq ``` You should see `"chromeConnected": true` and the popup-once promise comes true: new Claude Code windows reuse the existing proxy connection. ## Iron rules (do not violate) 1. **Always go through the proxy.** Never point `chrome-devtools-mcp` at port 9222 directly — you lose popup suppression and focus protection. 2. **Connect to the real Chrome** (the one with your logins, extensions, bookmarks). Never start an empty `--user-data-dir=/tmp/x` Chrome — Chrome 146+ requires a custom data dir for `--remote-debugging-port`, which kills your session state. 3. **No HTTP_PROXY env vars** anywhere near the MCP config. The script `unset`s them; if your shell rc forces them back you'll fail to connect to `127.0.0.1:9401`. 4. **Never kill the current session's MCP processes.** - The Claude Code → MCP pipe is stdio; killing the child = permanent loss of `mcp__chrome__*` tools for the running session. - When you need to clean up, check `lsof -nP -p ` for ESTABLISHED stdio first. Only kill processes with no live client. - "Newest = keep" is **not** safe — the active session is not always the newest PID. Run `lsof -i :9401 -i :9402 | grep ESTABLISHED` first. 5. **The proxy is a long-lived service.** Started via `nohup` + `disown` — it survives Claude Code exits and reconnects to Chrome every 5s. Don't kill it casually. ## Troubleshooting | Symptom | Likely cause | Fix | |---------|--------------|-----| | `Could not connect to Chrome` | Chrome not running OR `DevToolsActivePort` missing | `open -a "Google Chrome"`, then check `~/Library/Application Support/Google/Chrome/DevToolsActivePort` exists | | `chromeConnected: false` in `/proxy/status` | Chrome restarted and the consent popup is waiting | Click the popup once — proxy auto-reattaches | | Popup appears on every new Claude Code window | Proxy died last time | Restart it: `unset http_proxy; nohup node ~/scripts/cdp-proxy.mjs --port 9401 --chrome-port 9222 &>~/chrome-profiles/logs/proxy-9401.log & disown` | | Commands slow / timing out | Session leak — old `chrome-devtools-mcp` clients didn't detach | Idle clients auto-detach after 5 min (`--idle-timeout 300000`); to force, close the idle Claude Code window or `/mcp disable chrome` there | | Multi-agent runs interfering | Proxy is < v3 | Check `/proxy/status` for `sessions` and `clientDetails` fields — if missing, restart the proxy to load v3 | | `evaluate_script` times out but `/json/version` returns 200 | MCP CDP session is hung (heavy `take_snapshot` or large `evaluate_script` poisoned the channel) | The proxy can't fix this — only `/mcp` reconnect or restart the Claude Code window helps | ## Tested environment - macOS 14+, Chrome 146/147 stable, Node 18+ - `chrome-devtools-mcp@latest` from npm - Single Chrome + 3-5 concurrent Claude Code windows / sub-agents ## License MIT — copy, fork, modify freely. --- Source: https://tokrepo.com/en/workflows/chrome-mcp-background-proxy-fix-popups-focus-stealing-multi-29944683 Author: henuwangkai