From 62bf2b29105b93b6a3e3ef27ab15d96e74da3bb1 Mon Sep 17 00:00:00 2001 From: ritms42 Date: Thu, 24 Sep 2026 21:48:57 +0200 Subject: [PATCH 1/2] fix(mcp): pin chrome-devtools-mcp to 1.10.1 instead of @latest The default chrome-devtools connector is launched with `npx -y` and `@latest`, so every session start can silently install whatever version was most recently published to npm. Pinning makes the default connector reproducible and removes the unpinned-npx finding that /security-scan (AgentShield) reports against ECC's own .mcp.json. The same spec is pinned in the Codex merge script so both harnesses stay in sync, and the Codex merge test is updated to match. Co-Authored-By: Claude Opus 5.5 --- .mcp.json | 2 +- scripts/codex/merge-mcp-config.js | 2 +- tests/scripts/codex-hooks.test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.mcp.json b/.mcp.json index 045baea18..9860a4dbe 100644 --- a/.mcp.json +++ b/.mcp.json @@ -2,7 +2,7 @@ "mcpServers": { "chrome-devtools": { "command": "npx", - "args": ["-y", "chrome-devtools-mcp@latest"] + "args": ["-y", "chrome-devtools-mcp@1.10.1"] } } } diff --git a/scripts/codex/merge-mcp-config.js b/scripts/codex/merge-mcp-config.js index 721e3c29f..64f42d333 100644 --- a/scripts/codex/merge-mcp-config.js +++ b/scripts/codex/merge-mcp-config.js @@ -94,7 +94,7 @@ const DEFAULT_MCP_STARTUP_TIMEOUT_TOML = `startup_timeout_sec = ${DEFAULT_MCP_ST // mcp-configs/mcp-servers.json. Existing user-managed entries are never // touched by the merge (add-only), except the known-invalid repair below. const ECC_SERVERS = { - 'chrome-devtools': dlxServer('chrome-devtools', 'chrome-devtools-mcp@latest', { startup_timeout_sec: DEFAULT_MCP_STARTUP_TIMEOUT_SEC }, DEFAULT_MCP_STARTUP_TIMEOUT_TOML) + 'chrome-devtools': dlxServer('chrome-devtools', 'chrome-devtools-mcp@1.10.1', { startup_timeout_sec: DEFAULT_MCP_STARTUP_TIMEOUT_SEC }, DEFAULT_MCP_STARTUP_TIMEOUT_TOML) }; // ECC <= 2.0.0 emitted [mcp_servers.exa] with a `url` key. Codex rejects diff --git a/tests/scripts/codex-hooks.test.js b/tests/scripts/codex-hooks.test.js index c47f6d986..ef4934e6b 100644 --- a/tests/scripts/codex-hooks.test.js +++ b/tests/scripts/codex-hooks.test.js @@ -916,7 +916,7 @@ if ( const merged = fs.readFileSync(configPath, 'utf8'); const parsed = TOML.parse(merged); assert.strictEqual(parsed.mcp_servers['chrome-devtools'].command, 'npx'); - assert.deepStrictEqual(parsed.mcp_servers['chrome-devtools'].args, ['chrome-devtools-mcp@latest']); + assert.deepStrictEqual(parsed.mcp_servers['chrome-devtools'].args, ['chrome-devtools-mcp@1.10.1']); assert.strictEqual(parsed.mcp_servers['chrome-devtools'].startup_timeout_sec, 30); // No retired server may be (re-)emitted — exa's url form broke Codex (#2224). assert.strictEqual(parsed.mcp_servers.exa, undefined); From 1c41fa7c0077b3355cae1ed95f1c9e25f611b1c2 Mon Sep 17 00:00:00 2001 From: affaan-m <124439313+affaan-m@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:08:43 -0400 Subject: [PATCH 2/2] docs(codex): clarify explicit MCP pin refresh and verify shared recommendation --- README.md | 11 +++++-- tests/scripts/codex-hooks.test.js | 51 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 117552c2b..a49497089 100644 --- a/README.md +++ b/README.md @@ -1515,9 +1515,14 @@ npm install && bash scripts/sync-ecc-to-codex.sh cp .codex/config.toml ~/.codex/config.toml ``` -The sync script safely merges ECC MCP servers into your existing `~/.codex/config.toml` using an **add-only** strategy: it never removes or modifies your existing servers. Run with `--dry-run` to preview changes, or `--update-mcp` to force-refresh ECC servers to the latest recommended config. +Normal MCP sync preserves existing server settings and warns when they differ from ECC's recommendation. An existing `chrome-devtools-mcp@latest` entry therefore stays unchanged; updating the repository alone does not adopt the recommended `chrome-devtools-mcp@1.10.1` pin. Existing legacy-sync users can preview and explicitly apply the refresh from the updated ECC checkout: -For Context7, ECC uses the canonical Codex section name `[mcp_servers.context7]` while still launching the `@upstash/context7-mcp` package. If you already have a legacy `[mcp_servers.context7-mcp]` entry, `--update-mcp` migrates it to the canonical section name. +```bash +bash scripts/sync-ecc-to-codex.sh --dry-run --update-mcp +bash scripts/sync-ecc-to-codex.sh --update-mcp +``` + +Review the preview before applying: `--update-mcp` replaces the entire recommended `chrome-devtools` server section, including custom command arguments and nested settings. Unrelated user-managed servers remain in place. Retired defaults such as Context7 are not refreshed or migrated by this flag. Codex macOS app: - Open this repository as your workspace. @@ -1533,7 +1538,7 @@ Codex macOS app: | Config | 1 | `.codex/config.toml`: top-level approvals/sandbox/web_search, MCP servers, notifications, profiles | | AGENTS.md | 2 | Root (universal) + `.codex/AGENTS.md` (Codex-specific supplement) | | Skills | 32 | `.agents/skills/`: SKILL.md + agents/openai.yaml per skill | -| MCP Servers | 6 | GitHub, Context7, Exa, Memory, Playwright, Sequential Thinking (7 with Supabase via `--update-mcp` sync) | +| MCP Servers | 6 legacy reference entries | GitHub, Context7, Exa, Memory, Playwright, Sequential Thinking. Current managed sync recommends `chrome-devtools`; see the explicit refresh instructions above. | | Profiles | 2 | `strict` (read-only sandbox) and `yolo` (full auto-approve) | | Agent Roles | 3 | `.codex/agents/`: explorer, reviewer, docs-researcher | diff --git a/tests/scripts/codex-hooks.test.js b/tests/scripts/codex-hooks.test.js index 920e4d3eb..2844e80d0 100644 --- a/tests/scripts/codex-hooks.test.js +++ b/tests/scripts/codex-hooks.test.js @@ -918,6 +918,12 @@ if ( const parsed = TOML.parse(merged); assert.strictEqual(parsed.mcp_servers['chrome-devtools'].command, 'npx'); assert.deepStrictEqual(parsed.mcp_servers['chrome-devtools'].args, ['chrome-devtools-mcp@1.10.1']); + const rootMcp = JSON.parse(fs.readFileSync(path.join(repoRoot, '.mcp.json'), 'utf8')); + const rootPackages = rootMcp.mcpServers['chrome-devtools'].args.filter(arg => + arg.startsWith('chrome-devtools-mcp@')); + assert.deepStrictEqual(rootPackages, parsed.mcp_servers['chrome-devtools'].args, + 'root MCP and generated Codex TOML must use the same connector pin'); + assert.deepStrictEqual(rootPackages, ['chrome-devtools-mcp@1.10.1']); assert.strictEqual(parsed.mcp_servers['chrome-devtools'].startup_timeout_sec, 30); // No retired server may be (re-)emitted — exa's url form broke Codex (#2224). assert.strictEqual(parsed.mcp_servers.exa, undefined); @@ -937,6 +943,51 @@ if ( passed++; else failed++; +if ( + test('merge-mcp-config preserves an existing latest entry until explicit recommendation refresh', () => { + const tempDir = createTempDir('mcp-merge-explicit-refresh-'); + const configPath = path.join(tempDir, 'config.toml'); + const original = [ + '# User-maintained configuration', + '[mcp_servers.chrome-devtools]', + 'command = "npx"', + 'args = ["chrome-devtools-mcp@latest", "--custom-browser-argument"]', + 'startup_timeout_sec = 75', + '[mcp_servers.chrome-devtools.env]', + 'CUSTOM_BROWSER_SETTING = "preserve-until-explicit-refresh"', + '', + '[mcp_servers.user_tool]', + 'command = "custom-launcher"', + 'args = ["--user-setting"]', + '', + ].join('\n'); + + try { + fs.writeFileSync(configPath, original); + const preserved = runNode(mergeMcpConfigScript, [configPath], deterministicPackageEnv); + assert.strictEqual(preserved.status, 0, `${preserved.stdout}\n${preserved.stderr}`); + assert.match(preserved.stderr, /chrome-devtools differs from ECC recommendation/); + assert.match(preserved.stderr, /--update-mcp to refresh/); + assert.strictEqual(fs.readFileSync(configPath, 'utf8'), original, + 'normal sync must preserve existing latest and customized settings byte-for-byte'); + + const refreshed = runNode(mergeMcpConfigScript, [configPath, '--update-mcp'], deterministicPackageEnv); + assert.strictEqual(refreshed.status, 0, `${refreshed.stdout}\n${refreshed.stderr}`); + assert.match(refreshed.stdout, /\[update\] mcp_servers\.chrome-devtools/); + const updated = TOML.parse(fs.readFileSync(configPath, 'utf8')); + assert.deepStrictEqual(updated.mcp_servers['chrome-devtools'], { + command: 'npx', args: ['chrome-devtools-mcp@1.10.1'], startup_timeout_sec: 30, + }, 'explicit refresh replaces the whole recommended section, including custom subsettings'); + assert.deepStrictEqual(updated.mcp_servers.user_tool, TOML.parse(original).mcp_servers.user_tool, + 'unrelated user-managed server settings remain untouched'); + } finally { + cleanup(tempDir); + } + }) +) + passed++; +else failed++; + if ( test('merge-mcp-config repairs the invalid exa url entry from earlier ECC versions (#2224)', () => { const tempDir = createTempDir('mcp-merge-exa-repair-');