# Chrome MCP 完整运维 Skill — 持久代理/防抢焦点/多 Agent 隔离 > Chrome DevTools MCP 生产级运维手册:cdp-proxy v3 持久 WebSocket 连接(解决 Chrome 146 远程调试弹窗)、防抢用户焦点、多 Agent 请求 ID 重映射与 sessionId 事件路由隔离、npx hang 排障、不直连 Chrome 的铁律与完整故障排查流程。 ## Install Merge the JSON below into your `.mcp.json`: # Chrome MCP 完整运维 Skill ## 架构总览 ``` Claude Code (stdio) → chrome-mcp-proxy.sh → chrome-devtools-mcp (连接 proxy 的 WebSocket) → cdp-proxy.mjs v3 (port 9401) ├── 持久 WebSocket 连接到 Chrome(弹窗只出现一次) ├── 请求 ID 重映射(防多客户端冲突) ├── 事件按 sessionId 路由(防多 Agent 互扰) └── 拦截抢焦点命令 → 真实 Chrome (通过 DevToolsActivePort 文件连接) ``` **核心原则:必须通过 proxy 中间层,绝不直连 Chrome。** ## 铁律(绝不可违反) ### 1. 必须经过 proxy - proxy (`cdp-proxy.mjs`) 拦截 `Target.activateTarget` 和 `Page.bringToFront`,防止浏览器抢用户焦点 - 强制 `createTarget` 在后台创建 tab(`background: true`) - `.mcp.json` 中 chrome 配置必须用 `chrome-mcp-proxy.sh`,不能用 `chrome-devtools-mcp` 直连 ### 2. 必须连接真实浏览器 - 用户日常使用的 Chrome(有登录态、插件、书签) - Chrome 需在 `chrome://inspect/#remote-debugging` 中开启远程调试(一次性设置) - proxy 通过读取 `~/Library/Application Support/Google/Chrome/DevToolsActivePort` 文件自动连接 - **绝对禁止**用 `--user-data-dir=/tmp/xxx` 或 `~/.cache/chrome-devtools-mcp/chrome-profile` 启动无状态 Chrome ### 3. 不走网络代理 - `.mcp.json` 中 chrome 服务**不配 env 代理变量** - `chrome-mcp-proxy.sh` 脚本内部已 `unset` 所有代理环境变量 - 本机 zshrc 有全局代理 (http_proxy=127.0.0.1:7897),会拦截本地连接,所以必须 unset ### 4. 绝不杀当前 session 的 MCP 进程 - Chrome MCP 是 Claude Code 通过 stdio 管道启动的子进程 - 进程一死,管道断裂,当前 session 永远无法恢复 - 清理多实例**只用安全脚本**: `bash ~/scripts/kill-old-chrome-mcp.sh` ### 5. proxy 是持久服务 - proxy 用 `nohup` + `disown` 启动,不随 Claude Code 退出而死 - 启动时立即建立到 Chrome 的持久 WebSocket 连接 - 所有 Claude Code 会话共用这一条连接 - Chrome 断开后自动每 5 秒重连 ### 6. 绝不在 MCP server 配置里用 `npx -y chrome-devtools-mcp@xxx`(2026-05-26 新增) - 本机的 `npx -y` 在 MCP host stdio spawn 环境下会**永久 hang**,根本不 spawn binary,导致 30s timeout - 同样症状跨所有版本:`@latest` (1.0.x) / `@0.19.0` 都中招 - 触发条件不明,疑为 `~/.npm/_locks/` stale lock 或 npm registry metadata 探测被某状态阻塞 - **正确写法**:3 个 proxy 脚本末尾的 fallback 模板 ```bash CDP_MCP_BIN="$HOME/.npm/_npx/a44f1c2f3c68e710/node_modules/.bin/chrome-devtools-mcp" if [ -x "$CDP_MCP_BIN" ]; then exec "$CDP_MCP_BIN" --browserUrl "http://127.0.0.1:${PROXY_PORT}" else exec npx -y chrome-devtools-mcp@0.19.0 --browserUrl "http://127.0.0.1:${PROXY_PORT}" fi ``` - **任何新 Agent 的 MCP 配置(Codex / Cursor / Cline / 别的)都要走 proxy 脚本,不要直接配 `command="npx"`** ## Chrome 146+ 远程调试弹窗问题 ### 问题 Chrome 146 开始,外部程序通过 WebSocket 连接调试端口时,Chrome 会弹出"要允许远程调试吗?"授权窗口。每次**新建** WebSocket 连接都会弹一次。 ### 解决方案:持久连接 cdp-proxy.mjs v3 在**启动时就建立一条持久连接**到 Chrome,所有客户端共用。弹窗只在以下情况出现: - proxy 首次启动时(点一次"允许") - Chrome 重启后 proxy 重连时(点一次"允许") 之后新开 Claude Code 窗口**不再弹窗**,因为复用已有连接。 ### 前置条件 Chrome 地址栏打开 `chrome://inspect/#remote-debugging`,勾选 **Allow remote debugging for this browser instance**(一次性设置)。 ### 无效方案(已验证不可行) | 方案 | 结果 | |------|------| | `--remote-debugging-port=9222` | Chrome 146 要求 `--user-data-dir` 为非默认目录,丢失登录态 | | `--silent-debugger-extension-api` | 对远程调试弹窗无效 | | `--disable-features=AutomationControlled` | 对弹窗无效 | | `defaults write com.google.Chrome DevToolsRemoteDebuggingAllowed` | Chrome 不读取 | | macOS managed preferences plist | Chrome 不读取 | | `.mobileconfig` 配置描述文件 | Chrome 不读取 | | `devtools.remote_debugging.allowed` in Local State | 无效 | | `chrome-devtools-mcp --autoConnect` | 仍然弹窗 | | 锁定 chrome-devtools-mcp 旧版本 (0.19.0) | 无效 | ## 多 Agent 隔离(v3 新增) 多个 Agent(或 sub-agent)同时操作 Chrome 时,proxy v3 保证互不干扰: | 机制 | 说明 | |------|------| | **请求 ID 重映射** | 每个客户端的请求 ID 统一映射为全局唯一 ID,响应精准路由回原客户端 | | **事件按 sessionId 路由** | `Target.attachToTarget` 的 session 归属记录到对应客户端,后续该 session 的事件只发给该客户端 | | **Tab 归属追踪** | `Target.createTarget` 的响应记录 tab 归属,Target 域的全局事件按 targetId 路由 | | **客户端断开清理** | 客户端断开时清理其 session 归属、未完成请求、tab 记录 | | **空闲自动回收** | 客户端 idle 超 5min(`--idle-timeout 300000`)→ 对其全部 session 发 `Target.detachFromTarget`,WebSocket 保留,下次操作时 chrome-devtools-mcp 自动重新 attach(无感) | 架构上,所有 Agent 共享同一个 Chrome、同一个 proxy,各自创建后台 tab,通过不同的 sessionId/targetId 操作,互不干扰。 ## 配置文件 ### ~/.mcp.json(chrome 部分) ```json "chrome": { "command": "bash", "args": [ "/Users/wangkai/scripts/chrome-mcp-proxy.sh", "9401", "9222" ] } ``` **注意:不要加 env 代理变量!** ### ~/.claude/settings.json(权限) MCP 工具权限不能用通配符 `mcp__chrome__*`(会导致整个 settings 文件被跳过),必须逐一列出: ```json "permissions": { "allow": [ "mcp__chrome__click", "mcp__chrome__close_page", "mcp__chrome__take_screenshot", ... ] } ``` ### ~/.claude/settings.local.json(MCP 启用) 同样需要逐一列出 MCP 工具名,不能用通配符。 ## 关键脚本 | 脚本 | 路径 | 用途 | |------|------|------| | chrome-mcp-proxy.sh | `~/scripts/chrome-mcp-proxy.sh` | MCP 启动入口(普通 Chrome 9222 ↔ proxy 9401):unset 代理 → 启动持久 proxy(nohup+disown) → **优先调 cache binary,fallback npx**(2026-05-26 改) | | chrome-beta-mcp-proxy.sh | `~/scripts/chrome-beta-mcp-proxy.sh` | 同上,Chrome Beta ↔ proxy 9402 | | arc-mcp-proxy.sh | `~/scripts/arc-mcp-proxy.sh` | 同上,Arc 9223 ↔ proxy 9501(Arc 必须用 `open -a Arc --args --remote-debugging-port=9223` 启动) | | cdp-proxy.mjs | `~/scripts/cdp-proxy.mjs` | CDP 持久代理 v3:持久连接 + ID 重映射 + 事件路由 + 拦截抢焦点 + 自动重连 | | kill-old-chrome-mcp.sh | `~/scripts/kill-old-chrome-mcp.sh` | ⚠️ **macOS BSD `head -n -1` bug,等效 no-op**(2026-05-26 发现,待修)。临时用 inline `ps + sort + tail` 替代 | ## 关键文件 | 文件 | 路径 | 用途 | |------|------|------| | DevToolsActivePort | `~/Library/Application Support/Google/Chrome/DevToolsActivePort` | Chrome 自动写入,包含调试端口和 WebSocket 路径 | | proxy 日志 | `~/chrome-profiles/logs/proxy-9401.log` | proxy 运行日志 | | proxy PID | `~/chrome-profiles/pids/proxy-9401.pid` | proxy 进程 PID | ## 排障流程 ### 症状:Chrome MCP 工具报错 "Could not connect to Chrome" #### 步骤1: 检查 Chrome 是否在运行 ```bash pgrep -x "Google Chrome" && echo "running" || echo "not running" ``` 没运行就启动:`open -a "Google Chrome"` #### 步骤2: 检查 DevToolsActivePort 文件 ```bash cat ~/Library/Application\ Support/Google/Chrome/DevToolsActivePort ``` #### 步骤3: 检查 proxy 状态 ```bash curl -s --noproxy '*' http://127.0.0.1:9401/proxy/status ``` 应该看到 `chromeConnected: true`。如果是 `false`,Chrome 可能需要点一次"允许"弹窗。 #### 步骤4: 用 `/mcp` 重连 #### 步骤5: 安全清理旧进程 ```bash bash ~/scripts/kill-old-chrome-mcp.sh ``` #### 步骤6: 最后手段 — 退出重启 Claude Code ### 症状:每次开新 Claude Code 都弹远程调试弹窗 检查 proxy 是否在持久运行: ```bash curl -s --noproxy '*' http://127.0.0.1:9401/proxy/status ``` 如果 proxy 没在运行,说明上次退出时被杀了。手动启动: ```bash unset http_proxy HTTP_PROXY https_proxy HTTPS_PROXY all_proxy ALL_PROXY nohup node ~/scripts/cdp-proxy.mjs --port 9401 --chrome-port 9222 &>~/chrome-profiles/logs/proxy-9401.log & disown ``` 然后在 Chrome 点一次"允许",之后就不再弹了。 ### 症状:感觉 "DevTools 协议被占满"(命令变慢/超时) `/proxy/status` 看 `sessions` 数,如果远大于 `tabs` 总数,说明有客户端 session 泄漏(老 Claude Code 窗口关的时候 chrome-devtools-mcp 没 detach 干净)。 - v3.1 后 proxy 自带空闲回收:idle > 5min 自动 detach 全部 session - 想立即清理:关掉闲置 Claude Code 窗口(触发 `clientDisconnect`),或在闲置窗口里 `/mcp` disable chrome - 重启 proxy 是最暴力的清理(但所有窗口都要重连) ### 症状:多 Agent 操作时互相干扰 检查 proxy 版本是否为 v3: ```bash curl -s --noproxy '*' http://127.0.0.1:9401/proxy/status ``` v3 会返回 `sessions` 和 `clientDetails` 字段。如果没有,需要重启 proxy 加载新版 cdp-proxy.mjs。 ### 症状:`/mcp reconnect` 30 秒 timeout,但 `proxy/status` 显示 `chromeConnected: true`(2026-05-26 新增 — `npx -y` hang) **特征**: - Claude Code 交互窗口 `/mcp` 重连 chrome / beta / arc 任一 server → `connection timed out after 30000ms` - proxy `/proxy/status` 显示 `chromeConnected: true, clients: 0` - proxy 日志没有任何"客户端连接"事件,新 MCP client 从未到达 proxy - chrome-devtools-mcp 孤儿进程数量正常 / 全清也不解决 - Codex 那边 chrome MCP 同样症状 **根因**:`npx -y chrome-devtools-mcp@xxx` 在 MCP host stdio spawn 环境下永久 hang,根本不 spawn binary。`npx` 卡在某个 metadata / lock 阶段不返回。 **快速判定(30 秒)**: ```bash # 直接调 cache binary 测 handshake BIN=$(find ~/.npm/_npx -name "chrome-devtools-mcp" -type l -path "*/node_modules/.bin/*" 2>/dev/null | head -1) cat <<'EOF' > /tmp/mcp-init.txt {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}} {"jsonrpc":"2.0","method":"notifications/initialized"} EOF timeout 10 bash -c "(cat /tmp/mcp-init.txt; sleep 8) | $BIN --browserUrl http://127.0.0.1:9402" | head -3 # 秒回 initialize response = binary OK,proxy OK,问题在 npx # 同样测 `npx -y chrome-devtools-mcp@0.19.0` 30s 没输出 = npx hang 确诊 ``` **修复**:3 个 proxy 脚本末尾改为"优先 cache binary,fallback npx"(见铁律 §6 模板)。改完 `/mcp reconnect` 立即秒连。 **禁忌**: - 不要再尝试杀 zombie / 重启 proxy / 改 chrome-devtools-mcp 版本 — 这些都不是根因 - 不要直接配 `command="npx"` 在任何 agent 的 mcp config 里 — 同坑 ### 症状:所有工具都报 "No page selected"(chrome-devtools-mcp 0.24.0 init 死锁) **特征**: - proxy `/proxy/status` 显示 `chromeConnected: true`,`clients ≥ 1`,但对应 client `sessions: 0, tabs: 0` - Chrome `curl http://127.0.0.1:9402/json/list` 能看到真实 page targets - 但 `list_pages` / `new_page` / `select_page` 全部报 `No page selected` **根因**(chrome-devtools-mcp 0.24.0 代码逻辑): - `index.js:210` 每个 tool call 先调 `getSelectedMcpPage()`,没选中就抛 `No page selected` - 唯一会 auto-select 的路径是 `McpContext.#init()` → `createPagesSnapshot()`,但只有 puppeteer init 当时能看到 page 才会 select - Chrome 重启 + chrome-devtools-mcp spawn 时序冲突 → puppeteer init 时 0 page → 永久死锁 - 没有事件监听器(`browser.on('targetcreated')` 不存在),后续 Chrome 开 tab 也不会触发刷新 **修复**:必须重启 chrome-devtools-mcp 让 puppeteer 在 Chrome 已有 page 时重 init 1. 确认 Chrome 已有真实 page:`curl -s http://127.0.0.1:9402/json/list | jq '.[] | select(.type=="page")'` 2. Claude Code 里 `/mcp` 把对应 server(如 `beta`)disable 再 enable 3. 验证:调 `list_pages` 应返回 `[selected]` 标记的 page **禁忌**: - 不要直接 kill chrome-devtools-mcp 进程,stdio 管道断了 Claude Code 不会自动 respawn - /mcp 是唯一干净的重启路径 ## 历史事故教训 | 日期 | 事故 | 教训 | |------|------|------| | 2026-03-15 | 手动 kill 全部进程导致 session 失效 | 只用安全脚本清理 | | 2026-03-24 | Chrome 更新到 146,远程调试开始弹窗 | 改用持久连接 proxy,弹窗只出现一次 | | 2026-03-24 | settings.json 用 `mcp__chrome__*` 通配符导致整个文件被跳过 | MCP 权限必须逐一列出工具名 | | 2026-03-24 | `--remote-debugging-port` 在 Chrome 146 要求非默认 user-data-dir | 不能用此方式,会丢失登录态 | | 2026-03-24 | macOS managed preferences / mobileconfig 设置 Chrome 策略 | Chrome 不读取这些策略,无效 | | 2026-03-24 | proxy v2 多客户端共用连接时请求 ID 冲突 | v3 加入 ID 重映射 + sessionId 事件路由 | | 2026-05-05 | Chrome Beta 重启 + chrome-devtools-mcp 0.24.0 spawn 同时发生,puppeteer init 时 0 page → 所有工具永久报 `No page selected` | 必须 `/mcp` disable+enable 重启 MCP server,让 puppeteer 在 Chrome 已稳定有 page 时重 init | | 2026-05-26 | `/mcp reconnect beta` 永久 30s timeout,proxy 健康。误诊 4 轮(zombie / Codex @latest / proxy 状态污染 / 都不是)。真因:**`npx -y chrome-devtools-mcp@0.19.0` 在本机会永久 hang,根本不 spawn binary** | 3 个 proxy 脚本末尾全部改为"优先调 cache binary,fallback npx"(铁律 §6);同步建立"Codex 控普通 Chrome 9401 / Claude Code 控 Chrome Beta 9402"的物理隔离方案。诊断教训:**proxy 健康 + 进程清干净后仍 timeout,第一时间剥层测试 bash → npx → binary 三层 spawn,别在 proxy/Codex 层耗时间** | ## 多 Agent 物理隔离方案(2026-05-26 推荐) 为避免多 agent(Claude Code / Codex / Cursor / ...)抢同一个 proxy 撞 init 死锁、版本冲突、CDP session 池竞争,采用**物理隔离**: | Agent | 控制的浏览器 | proxy 端口 | proxy 脚本 | server 名(在该 agent 的 mcp config 里) | |---|---|---|---|---| | **Claude Code** | Chrome Beta | 9402 | `chrome-beta-mcp-proxy.sh 9402 9222` | `beta` | | **Claude Code(可选)** | 普通 Chrome | 9401 | `chrome-mcp-proxy.sh 9401 9222` | `chrome` | | **Codex** | 普通 Chrome | 9401 | `chrome-mcp-proxy.sh 9401 9222` | `chrome` | | **任何新 Agent** | 给它分一个 proxy 端口 + Chrome 实例 | 9403 / 9404 / ... | 复制并改端口 | 自起 | **原则**: 1. 一个 proxy 端口对应一个浏览器实例 2. 多个 agent 可以共用同一 proxy(proxy v3 多 client 设计),但仅当确定它们用同一版本 chrome-devtools-mcp 且都走 binary(不走 `npx -y`) 3. 业务场景天然区分时(如 Codex 跑业务页 / Claude Code 跑社媒页),直接物理隔离最稳 **配置示例**(Codex `~/.codex/config.toml`): ```toml [mcp_servers.chrome] # 通过脚本启动,绝不直接 npx command = "bash" args = ["/Users/wangkai/scripts/chrome-mcp-proxy.sh", "9401", "9222"] ``` **反例**(永远不要这么写): ```toml [mcp_servers.chrome] command = "npx" args = ["-y", "chrome-devtools-mcp@latest", "--browserUrl", "http://127.0.0.1:9402"] # ❌ 1. npx -y 在本机 hang(铁律 §6) # ❌ 2. @latest 是 1.0.x,有 0.24.0+ init 死锁(§历史事故 2026-05-05) # ❌ 3. 共享 9402 跟 Claude Code beta 抢资源 ``` --- Source: https://tokrepo.com/en/workflows/chrome-mcp-skill-agent-39feb487 Author: henuwangkai