Esta página se muestra en inglés. Una traducción al español está en curso.
ScriptsMay 25, 2026·1 min de lectura

Codex Chrome MCP Proxy v3

Sanitized public Chrome MCP bundle for Codex: persistent CDP proxy, real Chrome login-state control, background tabs, focus protection, multi-agent isolation, and cached chrome-devtools-mcp startup fallback.

Listo para agents

Staging seguro para este activo

Este activo primero queda en staging. El prompt copiado pide inspeccionar los archivos staged antes de activar scripts, config MCP o config global.

Stage only · 19/100Política: staging
Superficie agent
Cualquier agent MCP/CLI
Tipo
Mcp Config
Instalación
Stage only
Confianza
Publisher verificado
Entrada
README.md
Comando de staging seguro
npx -y tokrepo@latest install 5846fbcf-238f-4730-9553-e36d111a6619 --target codex

Primero deja archivos en staging; la activación requiere revisar el README y el plan staged.

Browser Control Rule

Any browser-related operation must use Chrome MCP through the persistent proxy connected to the user's real Chrome.

  • Use Chrome MCP for opening pages, navigation, clicking, typing, screenshots, DOM/page inspection, network/console/performance checks, authenticated pages, existing Chrome tabs, file uploads, and visual verification.
  • Do not use Codex Browser, Codex Chrome plugin, Computer Use, AppleScript, Playwright against a temporary browser profile, or shell-based UI automation as a browser fallback.
  • The required path is Chrome MCP proxy v3 -> 127.0.0.1:9401 -> real Chrome.
  • Use scripts/chrome-mcp-proxy.sh for the chrome MCP server.
  • Never connect directly to chrome-devtools-mcp without the proxy.
  • Never launch a stateless Chrome profile with a temporary --user-data-dir or chrome-devtools-mcp cache profile.
  • Keep Codex's bundled browser, bundled Chrome, and computer-use backends disabled when strict real-Chrome routing is required.
  • If npx -y chrome-devtools-mcp hangs during MCP stdio startup, use the wrapper's cached-binary fallback or set CHROME_DEVTOOLS_MCP_BIN to an executable chrome-devtools-mcp binary.
  • Do not kill the current Chrome MCP session processes. If cleanup is needed, use scripts/kill-old-chrome-mcp.sh only.
  • Keep browser work in background tabs unless the user explicitly asks to show a page.

Non-browser tasks such as reading files, editing code, running tests, using CLIs, or calling non-browser APIs should use the appropriate local tool directly.


name: codex-chrome-mcp-proxy-v3 description: Codex-ready Chrome MCP proxy bundle for controlling the user's real Chrome through a persistent CDP proxy with focus protection and multi-agent isolation.

Codex Chrome MCP Proxy v3

Use this asset when Codex browser work must go through the user's real Google Chrome instead of Codex Browser, a temporary Playwright profile, or a browser extension bridge.

This bundle packages a public, sanitized version of the Chrome MCP setup:

  • scripts/cdp-proxy.mjs - persistent CDP proxy with request-id remapping, session routing, background tabs, timeout cleanup, and reconnect handling.
  • scripts/chrome-mcp-proxy.sh - MCP stdio entrypoint that starts/reuses the persistent proxy and launches chrome-devtools-mcp, preferring an explicit or cached binary before falling back to npx -y.
  • scripts/kill-old-chrome-mcp.sh - conservative cleanup helper for stale chrome-devtools-mcp processes.
  • templates/codex-config.toml - Codex MCP server config snippet.
  • templates/mcp.json - generic MCP config snippet.
  • AGENTS.md - agent rule that forces browser operations through Chrome MCP.

Current Codex Delta

This version reflects the May 26, 2026 Codex setup:

  • the chrome MCP server uses scripts/chrome-mcp-proxy.sh on proxy port 9401 and Chrome DevTools port 9222;
  • bundled Codex browser, bundled Chrome, and computer-use plugins should stay disabled for strict real-Chrome routing;
  • the wrapper checks CHROME_DEVTOOLS_MCP_BIN and existing npm cache binaries first, because npx -y chrome-devtools-mcp@0.19.0 can hang during MCP stdio startup on some hosts;
  • if a Codex notify hook launches a computer-use app, comment it out before relying on Chrome MCP as the sole browser path.

What It Solves

Codex's built-in browser tools are useful for ordinary page testing, but they are not enough when a task requires:

  • the user's real Chrome login state, cookies, extensions, bookmarks, and existing tabs;
  • CDP-level network, console, runtime, performance, and target control;
  • background tabs that do not steal focus from the user;
  • multiple agents sharing one Chrome without request or event collisions;
  • a persistent connection that avoids repeated Chrome remote-debugging prompts.

The required path is:

Codex MCP client
  -> scripts/chrome-mcp-proxy.sh
  -> chrome-devtools-mcp
  -> scripts/cdp-proxy.mjs on 127.0.0.1:9401
  -> real Google Chrome DevTools endpoint

Install

  1. Copy the scripts to a stable local directory, for example:
mkdir -p "$HOME/scripts"
cp scripts/cdp-proxy.mjs scripts/chrome-mcp-proxy.sh scripts/kill-old-chrome-mcp.sh "$HOME/scripts/"
chmod +x "$HOME/scripts/chrome-mcp-proxy.sh" "$HOME/scripts/kill-old-chrome-mcp.sh"
  1. Install Node dependencies next to cdp-proxy.mjs:
cd "$HOME/scripts"
npm install ws

If npx -y chrome-devtools-mcp@0.19.0 hangs during MCP startup, either run it once in an interactive shell so npm creates a cache binary, or set CHROME_DEVTOOLS_MCP_BIN to a known executable:

export CHROME_DEVTOOLS_MCP_BIN="$HOME/.npm/_npx/<cache-id>/node_modules/.bin/chrome-devtools-mcp"
  1. Enable remote debugging for the current Chrome profile:
Open chrome://inspect/#remote-debugging
Enable "Allow remote debugging for this browser instance"
  1. Add the Codex MCP server config from templates/codex-config.toml to ~/.codex/config.toml.

  2. Restart Codex so the new MCP server is loaded.

Codex Routing Rule

For browser-related tasks, route through Chrome MCP only:

  • page open/navigation/click/type;
  • screenshots and visual verification;
  • DOM/page inspection;
  • authenticated pages and existing tabs;
  • network, console, runtime, performance, memory checks;
  • file upload flows.

Do not fall back to Codex Browser, a temporary browser profile, AppleScript, shell UI automation, or a direct chrome-devtools-mcp connection.

Non-browser work such as code edits, tests, CLI commands, and API calls can still use the normal local tools.

Health Check

curl -s --noproxy '*' http://127.0.0.1:9401/proxy/status

Expected:

{
  "chromeConnected": true,
  "pendingRequests": 0
}

If Chrome restarted, the proxy should reconnect. Chrome may ask for remote-debugging permission once after a restart.

Safety Rules

  • Always go through cdp-proxy.mjs; never connect chrome-devtools-mcp directly to Chrome.
  • Do not launch a throwaway Chrome profile for authenticated work.
  • Do not include proxy environment variables in the MCP server config.
  • Keep Codex browser/computer-use backends disabled when this asset is the required browser-control path.
  • Do not kill the active MCP process from the current session.
  • Use scripts/kill-old-chrome-mcp.sh only for stale process cleanup.
  • Treat cookies, local storage, passwords, tokens, and private page data as sensitive.
  • Prefer background tabs unless the user explicitly wants a visible page.

Why stage_only

This asset can modify a user's browser session, inspect authenticated pages, and run JavaScript in pages. It should be installed as a staged bundle and reviewed before activation.

{ "name": "codex-chrome-mcp-proxy-v3", "type": "module", "dependencies": { "ws": "^8.18.0" } }

#!/usr/bin/env node /**

  • CDP Background Proxy v3 — 持久连接 + 多 Agent 隔离
    1. 持久 WebSocket 连接到 Chrome(弹窗只出现一次)
    1. 拦截 Target.activateTarget / Page.bringToFront(防抢焦点)
    1. 强制 createTarget background=true
    1. 请求 ID 重映射(防多客户端 ID 冲突)
    1. 事件按 sessionId 路由到对应客户端(防多 Agent 互扰)
    1. 自动重连
  • 用法: node cdp-proxy.mjs [--port 9401] [--chrome-port 9222] */

import http from 'http'; import fs from 'fs'; import path from 'path'; import os from 'os'; import { WebSocketServer, WebSocket } from 'ws';

const args = process.argv.slice(2); const getArg = (name, def) => { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : def; };

const PROXY_PORT = parseInt(getArg('--port', '9401')); const CHROME_PORT = parseInt(getArg('--chrome-port', '9222')); const CHROME_HOST = getArg('--chrome-host', '127.0.0.1'); const IDLE_TIMEOUT_MS = parseInt(getArg('--idle-timeout', '300000')); const IDLE_CHECK_INTERVAL_MS = parseInt(getArg('--idle-check', '60000'));

const DEVTOOLS_PORT_FILE = getArg('--devtools-port-file', path.join(os.homedir(), 'Library/Application Support/Google/Chrome/DevToolsActivePort'));

// ═══════════════════════════════════════════ // 持久 Chrome 连接 // ═══════════════════════════════════════════

let chromeWs = null; let chromeReady = false; let reconnecting = false;

const BLOCKED_METHODS = new Set([ 'Target.activateTarget', 'Page.bringToFront', ]);

function safeSend(ws, data) { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(typeof data === 'string' ? data : JSON.stringify(data)); } }

function readDevToolsActivePort() { try { const content = fs.readFileSync(DEVTOOLS_PORT_FILE, 'utf-8').trim(); const lines = content.split('\n'); if (lines.length >= 2) { return { port: parseInt(lines[0]), wsPath: lines[1] }; } } catch (e) { /* ignore */ } return null; }

function getChromeWsUrl() { const portInfo = readDevToolsActivePort(); if (portInfo) { return ws://${CHROME_HOST}:${portInfo.port}${portInfo.wsPath}; } return null; }

// ═══════════════════════════════════════════ // 多客户端隔离 // ═══════════════════════════════════════════

// 全局自增 ID,保证唯一 let globalIdCounter = 1;

// proxyId → { clientWs, originalId, method, createdAt } const pendingRequests = new Map();

// 每30秒清理超过60秒未响应的 pending requests setInterval(() => { const now = Date.now(); let cleaned = 0; for (const [proxyId, req] of pendingRequests) { if (now - req.createdAt > 60000) { // 给客户端返回超时错误,避免它也卡住 safeSend(req.clientWs, { id: req.originalId, error: { code: -32000, message: 'CDP request timeout (60s)' } }); pendingRequests.delete(proxyId); const state = clientState.get(req.clientWs); if (state) state.proxyIds.delete(proxyId); cleaned++; } } if (cleaned > 0) { console.log([Proxy] 清理 ${cleaned} 个超时请求 (剩 ${pendingRequests.size} 个)); } }, 30000);

// sessionId → clientWs(哪个客户端 attach 了这个 session) const sessionOwners = new Map();

// clientWs → { tabs: Set, sessions: Set, proxyIds: Set } const clientState = new Map();

function getOrCreateState(clientWs) { if (!clientState.has(clientWs)) { clientState.set(clientWs, { tabs: new Set(), sessions: new Set(), proxyIds: new Set(), lastActivityAt: Date.now(), }); } return clientState.get(clientWs); }

// 空闲客户端自动回收:idle 超阈值则 detach 其全部 session(WebSocket 保留) setInterval(() => { if (!chromeReady) return; const now = Date.now(); for (const [, state] of clientState) { const idleMs = now - state.lastActivityAt; if (idleMs < IDLE_TIMEOUT_MS || state.sessions.size === 0) continue;

    const sids = Array.from(state.sessions);
    console.log(`[Proxy] 空闲回收: idle=${Math.floor(idleMs / 1000)}s, detach ${sids.length} sessions`);

    for (const sid of sids) {
        cdpRequest('Target.detachFromTarget', { sessionId: sid }).catch(() => {});
        sessionOwners.delete(sid);
        state.sessions.delete(sid);
    }
}

}, IDLE_CHECK_INTERVAL_MS);

// ═══════════════════════════════════════════ // Chrome 连接管理 // ═══════════════════════════════════════════

function connectToChrome() { if (reconnecting) return; reconnecting = true;

const wsUrl = getChromeWsUrl();
if (!wsUrl) {
    console.error('[Proxy] Chrome 未运行,5秒后重试...');
    setTimeout(() => { reconnecting = false; connectToChrome(); }, 5000);
    return;
}

console.log(`[Proxy] 连接 Chrome: ${wsUrl}`);
chromeWs = new WebSocket(wsUrl);

chromeWs.on('open', () => {
    console.log('[Proxy] ✓ Chrome 已连接');
    chromeReady = true;
    reconnecting = false;
});

chromeWs.on('message', (rawData) => {
    try {
        const msg = JSON.parse(rawData.toString());

        // ── 响应消息(有 id)→ 路由到发起者 ──
        if (msg.id !== undefined && pendingRequests.has(msg.id)) {
            const { clientWs, originalId, method } = pendingRequests.get(msg.id);
            pendingRequests.delete(msg.id);

            const state = clientState.get(clientWs);

            // 追踪 createTarget
            if (method === 'Target.createTarget' && msg.result?.targetId) {
                if (state) state.tabs.add(msg.result.targetId);
                console.log(`[Proxy] 新 tab: ${msg.result.targetId}`);
            }

            // 追踪 attachToTarget → 记录 session 归属
            if (method === 'Target.attachToTarget' && msg.result?.sessionId) {
                const sid = msg.result.sessionId;
                sessionOwners.set(sid, clientWs);
                if (state) state.sessions.add(sid);
                console.log(`[Proxy] session ${sid.slice(0, 8)}... → 客户端`);
            }

            // 还原原始 ID
            msg.id = originalId;
            safeSend(clientWs, msg);
            return;
        }

        // ── 事件消息(无 id)→ 按 sessionId 路由 ──
        if (msg.method) {
            // 带 sessionId 的事件:发给对应客户端
            if (msg.sessionId && sessionOwners.has(msg.sessionId)) {
                safeSend(sessionOwners.get(msg.sessionId), msg);
                return;
            }

            // Target 域的全局事件:按 targetId 路由或广播
            if (msg.method.startsWith('Target.')) {
                const targetId = msg.params?.targetInfo?.targetId || msg.params?.targetId;
                if (targetId) {
                    // 找到拥有这个 tab 的客户端
                    for (const [ws, state] of clientState) {
                        if (state.tabs.has(targetId)) {
                            safeSend(ws, msg);
                            return;
                        }
                    }
                }
                // 找不到归属,广播
                for (const [ws] of clientState) {
                    safeSend(ws, msg);
                }
                return;
            }

            // 其他无 sessionId 的事件,广播
            for (const [ws] of clientState) {
                safeSend(ws, msg);
            }
            return;
        }

        // 其他消息,广播
        for (const [ws] of clientState) {
            safeSend(ws, rawData);
        }
    } catch (e) {
        for (const [ws] of clientState) {
            safeSend(ws, rawData);
        }
    }
});

const handleChromeDisconnect = (reason) => {
    if (!chromeReady && chromeWs === null) return; // 已处理过
    console.log(`[Proxy] Chrome 断开 (${reason}),立即释放 ${pendingRequests.size} 个 pending 请求,5秒后重连...`);
    chromeReady = false;
    chromeWs = null;
    reconnecting = false;

    // 关键修复:给所有 pending 请求立即发 error,让客户端 Promise 立即 reject
    // 否则 chrome-devtools-mcp 的 Promise 永远悬空 → 后续命令排队僵死
    for (const [, req] of pendingRequests) {
        safeSend(req.clientWs, {
            id: req.originalId,
            error: { code: -32000, message: `Chrome disconnected (${reason}), request aborted` },
        });
    }
    pendingRequests.clear();

    // Chrome 重启后旧 session / tab 全部失效,清理客户端 state(保留 WebSocket)
    sessionOwners.clear();
    for (const [, state] of clientState) {
        state.sessions.clear();
        state.tabs.clear();
        state.proxyIds.clear();
    }

    setTimeout(() => connectToChrome(), 5000);
};

chromeWs.on('close', () => handleChromeDisconnect('close'));

chromeWs.on('error', (err) => {
    console.error('[Proxy] Chrome 错误:', err.message);
    handleChromeDisconnect('error');
});

}

// ═══════════════════════════════════════════ // HTTP 端点 // ═══════════════════════════════════════════

function cdpRequest(method, params = {}) { return new Promise((resolve, reject) => { if (!chromeReady) return reject(new Error('Chrome not connected')); const proxyId = globalIdCounter++; const timeout = setTimeout(() => { pendingRequests.delete(proxyId); reject(new Error('timeout')); }, 5000); const fakeClient = { send: (data) => { clearTimeout(timeout); resolve(typeof data === 'string' ? JSON.parse(data) : data); }, readyState: WebSocket.OPEN, }; pendingRequests.set(proxyId, { clientWs: fakeClient, originalId: proxyId, method }); chromeWs.send(JSON.stringify({ id: proxyId, method, params })); }); }

const server = http.createServer(async (req, res) => { try { if (req.url === '/json/version') { const portInfo = readDevToolsActivePort(); const wsUrl = portInfo ? ws://${CHROME_HOST}:${PROXY_PORT}${portInfo.wsPath} : ws://${CHROME_HOST}:${PROXY_PORT}/devtools/browser/proxy; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ Browser: 'Chrome (via CDP Proxy v3)', webSocketDebuggerUrl: wsUrl })); return; }

    if (req.url === '/json/list' || req.url === '/json') {
        if (!chromeReady) { res.writeHead(503); res.end('Chrome not connected'); return; }
        try {
            const result = await cdpRequest('Target.getTargets');
            const targets = (result.result?.targetInfos || []).map(t => ({
                ...t,
                webSocketDebuggerUrl: `ws://${CHROME_HOST}:${PROXY_PORT}/devtools/page/${t.targetId}`,
            }));
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify(targets));
        } catch (e) { res.writeHead(500); res.end(e.message); }
        return;
    }

    if (req.url === '/json/new') {
        if (!chromeReady) { res.writeHead(503); res.end('Chrome not connected'); return; }
        try {
            const result = await cdpRequest('Target.createTarget', { url: 'about:blank', background: true });
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify(result.result || {}));
        } catch (e) { res.writeHead(500); res.end(e.message); }
        return;
    }

    if (req.url === '/proxy/status') {
        const clientList = [];
        const now = Date.now();
        for (const [, state] of clientState) {
            clientList.push({
                tabs: state.tabs.size,
                sessions: state.sessions.size,
                idleMs: now - state.lastActivityAt,
            });
        }
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({
            chromeConnected: chromeReady,
            clients: clientState.size,
            sessions: sessionOwners.size,
            pendingRequests: pendingRequests.size,
            clientDetails: clientList,
        }, null, 2));
        return;
    }

    res.writeHead(404);
    res.end('Not found');
} catch (e) { res.writeHead(500); res.end(e.message); }

});

// ═══════════════════════════════════════════ // WebSocket 客户端处理 // ═══════════════════════════════════════════

const wss = new WebSocketServer({ server });

wss.on('connection', (clientWs, req) => { const state = getOrCreateState(clientWs); console.log([Proxy] 客户端连接 (共 ${clientState.size} 个));

clientWs.on('message', (data) => {
    state.lastActivityAt = Date.now();
    if (!chromeReady) {
        try {
            const msg = JSON.parse(data.toString());
            if (msg.id !== undefined) {
                safeSend(clientWs, { id: msg.id, error: { code: -1, message: 'Chrome not connected' } });
            }
        } catch (e) { /* ignore */ }
        return;
    }

    try {
        const msg = JSON.parse(data.toString());

        // 拦截抢焦点命令
        if (BLOCKED_METHODS.has(msg.method)) {
            console.log(`[Proxy] 拦截: ${msg.method}`);
            const reply = { id: msg.id, result: {} };
            if (msg.sessionId) reply.sessionId = msg.sessionId;
            safeSend(clientWs, reply);
            return;
        }

        // 强制后台创建 tab
        if (msg.method === 'Target.createTarget') {
            if (!msg.params) msg.params = {};
            msg.params.background = true;
            console.log(`[Proxy] 后台创建 tab: ${msg.params.url || 'about:blank'}`);
        }

        // ── ID 重映射 ──
        if (msg.id !== undefined) {
            const proxyId = globalIdCounter++;
            pendingRequests.set(proxyId, {
                clientWs,
                originalId: msg.id,
                method: msg.method,
                createdAt: Date.now(),
            });
            state.proxyIds.add(proxyId);
            msg.id = proxyId;
        }

        safeSend(chromeWs, msg);
    } catch (e) {
        safeSend(chromeWs, data);
    }
});

const cleanup = () => {
    // 清理这个客户端的 session 归属
    for (const sid of state.sessions) {
        sessionOwners.delete(sid);
    }
    // 清理未完成的请求
    for (const pid of state.proxyIds) {
        pendingRequests.delete(pid);
    }
    clientState.delete(clientWs);
    console.log(`[Proxy] 客户端断开 (剩 ${clientState.size} 个, 释放 ${state.tabs.size} tab, ${state.sessions.size} session)`);
};

clientWs.on('close', cleanup);
clientWs.on('error', cleanup);

});

// ═══════════════════════════════════════════ // 启动 // ═══════════════════════════════════════════

process.on('uncaughtException', (err) => { console.error('[Proxy] 异常(已恢复):', err.message); }); process.on('unhandledRejection', (reason) => { console.error('[Proxy] Promise 拒绝(已恢复):', reason); });

server.listen(PROXY_PORT, () => { console.log(╔══════════════════════════════════════════════════════╗ ║ CDP Background Proxy v3 — 持久连接 + 多Agent隔离 ║ ║ ║ ║ Proxy: http://127.0.0.1:${PROXY_PORT} ║ ║ ║ ║ ✓ 持久连接(弹窗只出现一次) ║ ║ ✓ 拦截 activateTarget / bringToFront ║ ║ ✓ 强制 createTarget background=true ║ ║ ✓ 请求 ID 重映射(防多客户端冲突) ║ ║ ✓ 事件按 sessionId 路由(防多 Agent 互扰) ║ ║ ✓ 自动重连 ║ ║ ✓ 空闲客户端自动回收 session ║ ║ ║ ║ 状态: http://127.0.0.1:${PROXY_PORT}/proxy/status ║ ╚══════════════════════════════════════════════════════╝); console.log([Proxy] 空闲回收阈值: ${IDLE_TIMEOUT_MS / 1000}s, 检查间隔: ${IDLE_CHECK_INTERVAL_MS / 1000}s); connectToChrome(); });

#!/bin/bash

Chrome MCP + persistent CDP proxy.

Usage: chrome-mcp-proxy.sh [proxy_port] [chrome_port]

set -euo pipefail

PROXY_PORT="${1:-9401}" CHROME_PORT="${2:-9222}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROXY_SCRIPT="${SCRIPT_DIR}/cdp-proxy.mjs" PID_DIR="${HOME}/chrome-profiles/pids" LOG_DIR="${HOME}/chrome-profiles/logs" MCP_PACKAGE="${CHROME_DEVTOOLS_MCP_PACKAGE:-chrome-devtools-mcp@0.19.0}"

mkdir -p "$PID_DIR" "$LOG_DIR"

Local CDP traffic must not be intercepted by user-level HTTP proxies.

unset http_proxy HTTP_PROXY https_proxy HTTPS_PROXY all_proxy ALL_PROXY export no_proxy="127.0.0.1,localhost" export NO_PROXY="127.0.0.1,localhost"

if ! lsof -i :"${PROXY_PORT}" >/dev/null 2>&1; then nohup node "${PROXY_SCRIPT}"
--port "${PROXY_PORT}"
--chrome-port "${CHROME_PORT}"
>"${LOG_DIR}/proxy-${PROXY_PORT}.log" 2>&1 & echo $! > "${PID_DIR}/proxy-${PROXY_PORT}.pid" disown || true sleep 2 fi

Prefer an explicit or cached binary when available. On some Codex hosts,

npx -y ... can hang during stdio startup; the cache path avoids that.

if [ -n "${CHROME_DEVTOOLS_MCP_BIN:-}" ] && [ -x "${CHROME_DEVTOOLS_MCP_BIN}" ]; then exec "${CHROME_DEVTOOLS_MCP_BIN}" --browserUrl "http://127.0.0.1:${PROXY_PORT}" fi

shopt -s nullglob for candidate in "${HOME}"/.npm/_npx/*/node_modules/.bin/chrome-devtools-mcp; do if [ -x "$candidate" ]; then exec "$candidate" --browserUrl "http://127.0.0.1:${PROXY_PORT}" fi done shopt -u nullglob

exec npx -y "${MCP_PACKAGE}" --browserUrl "http://127.0.0.1:${PROXY_PORT}"

#!/bin/bash

Conservative cleanup helper for stale chrome-devtools-mcp processes.

It keeps the newest chrome-devtools-mcp process and terminates older ones.

set -euo pipefail

PIDS="$(ps aux | grep "[c]hrome-devtools-mcp" | grep -v watchdog | grep -v "npm exec" | awk '{print $2}' || true)"

if [ -z "$PIDS" ]; then echo "No chrome-devtools-mcp process found." exit 0 fi

COUNT="$(echo "$PIDS" | wc -l | tr -d ' ')"

if [ "$COUNT" -le 1 ]; then echo "Only one Chrome MCP process exists (PID: $PIDS). Nothing to clean." exit 0 fi

NEWEST="$(echo "$PIDS" | sort -n | tail -1)" OLD_PIDS="$(echo "$PIDS" | sort -n | head -n -1)"

echo "Found $COUNT Chrome MCP processes." echo "Keeping newest PID: $NEWEST" echo "Stopping older PIDs: $OLD_PIDS"

for pid in $OLD_PIDS; do RELATED_PIDS="$(ps aux | grep -E "npm exec.*chrome-devtools|watchdog.*parent-pid=$pid" | grep -v grep | awk '{print $2}' || true)" kill "$pid" $RELATED_PIDS 2>/dev/null || true echo "Stopped PID $pid and related processes." done

echo "Cleanup complete. Kept PID $NEWEST."

[mcp_servers.chrome] command = "bash" args = ["$HOME/scripts/chrome-mcp-proxy.sh", "9401", "9222"]

Keep Codex browser work routed through Chrome MCP instead of bundled browser

or computer-use backends. Do not add proxy env vars to this MCP server.

[plugins."computer-use@openai-bundled"] enabled = false

[plugins."browser@openai-bundled"] enabled = false

[plugins."chrome@openai-bundled"] enabled = false

If your config has a notify hook that launches a computer-use app, comment it

out before relying on strict Chrome MCP routing.

{ "mcpServers": { "chrome": { "command": "bash", "args": [ "$HOME/scripts/chrome-mcp-proxy.sh", "9401", "9222" ] } } }

Discusión

Inicia sesión para unirte a la discusión.
Aún no hay comentarios. Sé el primero en compartir tus ideas.

Activos relacionados