codex-control-chrome-mcp 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,7 +6,17 @@ This project follows semantic versioning.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- ## [1.3.0] - 2026-07-03
9
+ ## [1.3.2] - 2026-07-04
10
+
11
+ ### Added
12
+
13
+ - `codex-control-chrome-mcp --version` (also `-v` and `version`) prints the package name and version. Previously the CLI had no way to report its own version, so `-v`/`--version` fell through to `Unknown command`.
14
+
15
+ ## [1.3.1] - 2026-07-04
16
+
17
+ ### Fixed
18
+
19
+ - `chrome_evaluate`, `chrome_navigate`, and `chrome_screenshot` now self-heal a stale debugger attachment instead of dead-ending on `Debugger is not attached`. Each tool already attaches before sending CDP commands, but the extension tracks attachments in memory: when Chrome tears an attachment down out-of-band (a competing debugger client, DevTools, or a renderer/process swap), that flag can go stale, so a fresh `attach` short-circuits and reports success without actually re-attaching and the next command fails with `Debugger is not attached`. The tab then stayed wedged — `attach` kept reporting `ok` while every command failed — and only a new tab recovered. These tools now retry once on that specific error by issuing a `detach` (which clears the stale flag; it may itself report "not attached", which is expected and ignored) followed by a real `attach`, so a previously wedged tab recovers automatically. The self-heal wrapper is covered by regression tests. This addresses recovery in the bridge only; the underlying churn comes from multiple debugger clients contending for the same tab, so reducing competing browser-control stacks still helps.
10
20
 
11
21
  ### Changed
12
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-control-chrome-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "MCP server and native host bridge for controlling Chrome through the Codex Chrome extension.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  import { DEFAULT_BROWSER, SUPPORTED_BROWSERS } from "./constants.js";
11
11
  import { listBridgeSockets } from "./bridge-client.js";
12
12
  import { probeLengthPrefixedSocket } from "./probe.js";
13
+ import { PACKAGE_NAME, PACKAGE_VERSION } from "./package-meta.js";
13
14
 
14
15
  export async function runCli(argv) {
15
16
  const args = argv.slice(2);
@@ -65,6 +66,11 @@ export async function runCli(argv) {
65
66
  printJson(await probeLengthPrefixedSocket(socketPath));
66
67
  return;
67
68
  }
69
+ case "version":
70
+ case "--version":
71
+ case "-v":
72
+ process.stdout.write(`${PACKAGE_NAME} ${PACKAGE_VERSION}\n`);
73
+ return;
68
74
  case "help":
69
75
  case "--help":
70
76
  case "-h":
@@ -157,6 +163,7 @@ Usage:
157
163
  codex-control-chrome-mcp uninstall-native-host [--force] [--browser <chrome|edge|all>]
158
164
  codex-control-chrome-mcp status [--browser <chrome|edge|all>]
159
165
  codex-control-chrome-mcp probe-socket <socket-path>
166
+ codex-control-chrome-mcp --version
160
167
 
161
168
  Notes:
162
169
  --stdio is the MCP server mode for Agent tools.
package/src/mcp-server.js CHANGED
@@ -229,15 +229,15 @@ function registerTools(server, runtime) {
229
229
  url: z.string().url(),
230
230
  },
231
231
  },
232
- async ({ tabId, url }) => {
233
- await runtime.request("attach", { tabId });
234
- await executeCdp(runtime, { tabId, method: "Page.enable", params: {} });
235
- return toolResult(await executeCdp(runtime, {
236
- tabId,
237
- method: "Page.navigate",
238
- params: { url },
239
- }));
240
- },
232
+ async ({ tabId, url }) =>
233
+ toolResult(await withReattach(runtime, tabId, async () => {
234
+ await executeCdp(runtime, { tabId, method: "Page.enable", params: {} });
235
+ return await executeCdp(runtime, {
236
+ tabId,
237
+ method: "Page.navigate",
238
+ params: { url },
239
+ });
240
+ })),
241
241
  );
242
242
 
243
243
  server.registerTool(
@@ -250,19 +250,19 @@ function registerTools(server, runtime) {
250
250
  awaitPromise: z.boolean().default(true),
251
251
  },
252
252
  },
253
- async ({ tabId, expression, awaitPromise }) => {
254
- await runtime.request("attach", { tabId });
255
- await executeCdp(runtime, { tabId, method: "Runtime.enable", params: {} });
256
- return toolResult(await executeCdp(runtime, {
257
- tabId,
258
- method: "Runtime.evaluate",
259
- params: {
260
- expression,
261
- awaitPromise,
262
- returnByValue: true,
263
- },
264
- }));
265
- },
253
+ async ({ tabId, expression, awaitPromise }) =>
254
+ toolResult(await withReattach(runtime, tabId, async () => {
255
+ await executeCdp(runtime, { tabId, method: "Runtime.enable", params: {} });
256
+ return await executeCdp(runtime, {
257
+ tabId,
258
+ method: "Runtime.evaluate",
259
+ params: {
260
+ expression,
261
+ awaitPromise,
262
+ returnByValue: true,
263
+ },
264
+ });
265
+ })),
266
266
  );
267
267
 
268
268
  server.registerTool(
@@ -274,16 +274,16 @@ function registerTools(server, runtime) {
274
274
  format: z.enum(["png", "jpeg", "webp"]).default("png"),
275
275
  },
276
276
  },
277
- async ({ tabId, format }) => {
278
- await runtime.request("attach", { tabId });
279
- await executeCdp(runtime, { tabId, method: "Page.enable", params: {} });
280
- return toolResult(await executeCdp(runtime, {
281
- tabId,
282
- method: "Page.captureScreenshot",
283
- params: { format, captureBeyondViewport: true },
284
- timeoutMs: 30_000,
285
- }));
286
- },
277
+ async ({ tabId, format }) =>
278
+ toolResult(await withReattach(runtime, tabId, async () => {
279
+ await executeCdp(runtime, { tabId, method: "Page.enable", params: {} });
280
+ return await executeCdp(runtime, {
281
+ tabId,
282
+ method: "Page.captureScreenshot",
283
+ params: { format, captureBeyondViewport: true },
284
+ timeoutMs: 30_000,
285
+ });
286
+ })),
287
287
  );
288
288
 
289
289
  server.registerTool(
@@ -319,6 +319,38 @@ function registerTools(server, runtime) {
319
319
  );
320
320
  }
321
321
 
322
+ /**
323
+ * Attach the debugger, run a CDP sequence, and self-heal a stale-attach state.
324
+ *
325
+ * The Codex Chrome extension tracks debugger attachments in memory. When Chrome
326
+ * tears an attachment down out-of-band (a competing debugger client, DevTools, a
327
+ * renderer/process swap), that flag can go stale: a fresh `attach` short-circuits
328
+ * and reports success without actually re-attaching, so the next CDP command
329
+ * fails with "Debugger is not attached". A `detach` clears the stale flag (it may
330
+ * itself throw "not attached" — expected, and ignored), after which `attach`
331
+ * re-attaches for real. We retry the sequence once on that specific error.
332
+ */
333
+ export async function withReattach(runtime, tabId, run) {
334
+ await runtime.request("attach", { tabId });
335
+ try {
336
+ return await run();
337
+ } catch (error) {
338
+ if (!isNotAttachedError(error)) throw error;
339
+ try {
340
+ await runtime.request("detach", { tabId });
341
+ } catch {
342
+ // Detach throws when the debugger is already gone; we only need its side
343
+ // effect of clearing the extension's stale attach flag.
344
+ }
345
+ await runtime.request("attach", { tabId });
346
+ return await run();
347
+ }
348
+ }
349
+
350
+ export function isNotAttachedError(error) {
351
+ return /not attached/i.test(error?.message ?? String(error ?? ""));
352
+ }
353
+
322
354
  async function executeCdp(runtime, { tabId, method, params = {}, timeoutMs }) {
323
355
  return await runtime.request(
324
356
  "executeCdp",