diff --git a/docs/tools/browser/existing-session.md b/docs/tools/browser/existing-session.md
index 5ce93b63cb81..bc13698c6234 100644
--- a/docs/tools/browser/existing-session.md
+++ b/docs/tools/browser/existing-session.md
@@ -144,7 +144,7 @@ Notes:
### Custom Chrome MCP launch
-OpenClaw includes an exact-pinned Chrome DevTools MCP 1.9.0 dependency with a
+OpenClaw includes an exact-pinned Chrome DevTools MCP 1.10.1 dependency with a
temporary document-identity patch and starts its CLI directly with the runtime
running OpenClaw, Node or Bun.
The npm package carries the patched dependency; source checkouts obtain it through
diff --git a/extensions/browser/package.json b/extensions/browser/package.json
index f8d52e86173e..09cc9903f994 100644
--- a/extensions/browser/package.json
+++ b/extensions/browser/package.json
@@ -6,7 +6,7 @@
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "1.30.0",
- "chrome-devtools-mcp": "1.9.0",
+ "chrome-devtools-mcp": "1.10.1",
"express": "5.2.1",
"playwright-core": "1.63.0",
"typebox": "1.3.34",
diff --git a/extensions/browser/src/browser/chrome-mcp.chromium.test.ts b/extensions/browser/src/browser/chrome-mcp.chromium.test.ts
index cf7e5760a896..d9078376a987 100644
--- a/extensions/browser/src/browser/chrome-mcp.chromium.test.ts
+++ b/extensions/browser/src/browser/chrome-mcp.chromium.test.ts
@@ -2,6 +2,7 @@ import path from "node:path";
import type { BrowserContext, Page } from "playwright-core";
import { afterEach, describe, expect, it } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../test-support.js";
+import { callTargetTool, resolveChromeMcpSnapshotRef } from "./chrome-mcp-routing.js";
import {
clickChromeMcpCoords,
clickChromeMcpElement,
@@ -291,6 +292,69 @@ describe.runIf(process.env.OPENCLAW_BROWSER_MCP_E2E === "1")(
});
}, 120_000);
+ it("keeps CSS lookup bound to the captured renderer and document", async () => {
+ await withMcpBrowser(async ({ page, context, profile, profileName }) => {
+ const fixturePort = await getFreePort();
+ const mainUrl = `http://127.0.0.1:${fixturePort}/styles`;
+ const frameUrl = `http://localhost:${fixturePort}/frame`;
+ const replacementUrl = `http://localhost:${fixturePort}/replacement`;
+ await context.route(mainUrl, (route) =>
+ route.fulfill({
+ contentType: "text/html",
+ body: `${Array.from(
+ { length: 64 },
+ (_, index) => ``,
+ ).join("")}`,
+ }),
+ );
+ await context.route(frameUrl, (route) =>
+ route.fulfill({
+ contentType: "text/html",
+ body: `${Array.from(
+ { length: 32 },
+ (_, index) => ``,
+ ).join("")}`,
+ }),
+ );
+ await context.route(replacementUrl, (route) =>
+ route.fulfill({
+ contentType: "text/html",
+ body: '',
+ }),
+ );
+ await page.goto(mainUrl);
+
+ const [tab] = await listChromeMcpTabs(profileName, profile, { timeoutMs: 30_000 });
+ const target = {
+ profileName,
+ profile,
+ targetId: tab!.targetId,
+ timeoutMs: 10_000,
+ };
+ const snapshot = await takeChromeMcpSnapshot(target);
+ const topRef = snapshotRef(snapshot, "button", "Top 0");
+ const frameRef = snapshotRef(snapshot, "button", "Frame 0");
+ const cssArgs =
+ (ref: string) => (session: Parameters[0]) => ({
+ uid: resolveChromeMcpSnapshotRef(session, target.targetId, ref).uid,
+ pageSize: 10,
+ pageIdx: 0,
+ });
+
+ await expect(
+ callTargetTool(target, "get_css_styles", cssArgs(topRef)),
+ ).resolves.toBeDefined();
+ await expect(callTargetTool(target, "get_css_styles", cssArgs(frameRef))).rejects.toThrow(
+ /detached or no longer exists/,
+ );
+
+ await page.goto(replacementUrl);
+ await expect(callTargetTool(target, "get_css_styles", cssArgs(topRef))).rejects.toThrow(
+ /no longer exists/,
+ );
+ });
+ }, 120_000);
+
it("retires canceled session handles and reconnects to the same browser", async () => {
await withMcpBrowser(async ({ page, profile, profileName }) => {
await page.setContent(
diff --git a/package.json b/package.json
index 42f2a4488416..d1192c82f63a 100644
--- a/package.json
+++ b/package.json
@@ -2270,7 +2270,7 @@
"@trycua/cua-driver": "0.28.2",
"acorn": "8.18.0",
"chalk": "6.0.0",
- "chrome-devtools-mcp": "1.9.0",
+ "chrome-devtools-mcp": "1.10.1",
"clawpdf": "0.3.2",
"commander": "15.0.0",
"croner": "10.0.1",
diff --git a/patches/README.md b/patches/README.md
index 844edaea393a..eb774f528410 100644
--- a/patches/README.md
+++ b/patches/README.md
@@ -20,16 +20,16 @@ Remove the dropdown focus hunk when an upstream release passes `ui/src/e2e/chat-
The popup disposes its previous Floating UI subscription before starting another and releases the new subscription if a synchronous reposition listener disconnects or deactivates it. Remove both popup hunks when an upstream release passes the public resize/disconnect lifecycle cases in `ui/src/components/web-awesome.test.ts` without retaining resize work after removal.
-`chrome-devtools-mcp@1.9.0` has an approved exact-version snapshot-identity patch, backported from [ChromeDevTools/chrome-devtools-mcp#2788](https://github.com/ChromeDevTools/chrome-devtools-mcp/pull/2788) at `06c8d4bc44f68bde8bd3fcd97dcc47375df81fe9`. Stable IDs include the frame's captured CDP session and document generation; ambiguous IDs stay capture-local, and stale lazy handles cannot resolve into a replacement renderer. Frame-local lookup and retained extra handles preserve actions and labeled screenshots. The published bundle also needs its existing `CdpFrame` export exposed. Original license notices remain intact, with modifications recorded in `build/src/OPENCLAW_PATCH_NOTICE.md`.
+`chrome-devtools-mcp@1.10.1` has an approved exact-version snapshot-identity patch, backported from [ChromeDevTools/chrome-devtools-mcp#2788](https://github.com/ChromeDevTools/chrome-devtools-mcp/pull/2788) at `06c8d4bc44f68bde8bd3fcd97dcc47375df81fe9` and forward-ported onto tag commit `e52c6b59b476c5e04d8dd9fd4bd017ba3b3d65df`. Stable IDs include the frame's captured CDP session and document generation; ambiguous IDs stay capture-local, and stale lazy handles cannot resolve into a replacement renderer. Frame-local lookup and retained extra handles preserve actions and labeled screenshots. The 1.10.1 CSS-style lookup resolves the same guarded handle and stays within its captured renderer, failing closed when the embedded DevTools target cannot represent a cross-renderer frame. Version 1.10.1 already exports its bundled `CdpFrame`, so the patch changes only the snapshot owner and its DOM-result caller. Original license notices remain intact, with modifications recorded in `build/src/OPENCLAW_PATCH_NOTICE.md`.
-The published integrity is `sha512-RnzXoJiUQ44hpOihWk90uOhLD/CnwDkDy0ldHMZONJ2nYQ+dWN1fq1luHHqyd+7FuYnyIlCY6uTThbN5ut9kSQ==`; the patch SHA-256 is `832ce1d2ff002b44edb93bae88858d6edd8bc833acc87dd0fe82454d8834e340`.
+The published integrity is `sha512-Klw6HWDqHC/XS1JwZldd2r49aUhbUJN9m9Mvcx4SEueIPXtzuQX+QelxAViobv8YUkDZ7HWDrmViR6LeYK0wAw==`; the patch SHA-256 is `d31d6e7e187ea9fd4dad87ed3ad08073c77ffcfddd52bc4761b39a2dfce0ac1e`.
| Target | Published SHA-256 | Patched SHA-256 |
| --- | --- | --- |
| `build/src/TextSnapshot.js` | `f3496989b93d174723fcf394628fabc36936b37e6a815fd85cbe20fba46d5ea0` | `299833ad0e4cfc171a417afaec41df594e4862fe53a7ada6ba160409f979788b` |
-| `build/src/McpPage.js` | `829f11b3cb4c7b87dbb4f5b6a3f62583f4fc05746daee21e629287247debb340` | `6d83dbd4d79c913664fbfa428d138b22fe4246612ab4dfcd93e9d8e62d5d6b51` |
-| `build/src/third_party/index.js` | `fc6ae43cb8f6007eba4b0f269290ec8fea6db7670686d17967b4812d90d2cc10` | `7609bb6c575c7c1152b3f4233ad4b98d97885c62ccff7bd9ee29257ca8ffc83f` |
-| `build/src/OPENCLAW_PATCH_NOTICE.md` | Added | `0e53a04f337a3760f2f1adab9c20e3b4f07019795f503266c0b68e0f46d55a6c` |
+| `build/src/McpPage.js` | `d49b2666cb9c6c5b4d7b9130235c476cb8a3af580363ff224796d9dbde99538d` | `47aa13c6b28cc11e1b0883532edea97cfde7563d78d143035850852d09809975` |
+| `build/src/third_party/index.js` | `c988e0684584b75e87ae04b768c4f8ae7064401afe5187ec0d4878b2c6833f12` | Unchanged |
+| `build/src/OPENCLAW_PATCH_NOTICE.md` | Added | `4bf44b52a80b5860b2160bc83407a5f0dd09f1801c85d0a2b6e4fec26bd7045d` |
The root package bundles this patched dependency so npm installations preserve the same bytes as pnpm source installs. Browser launches the packaged CLI directly with the runtime running OpenClaw, Node or Bun. Remove this patch, its registration, and the patch-specific package checks when a published upstream version passes `pnpm test:e2e:browser-mcp` and the installed-package stdio proof, including renderer replacement, cross-origin frames, cancellation, and snapshot → wait → action.
diff --git a/patches/chrome-devtools-mcp@1.9.0.patch b/patches/chrome-devtools-mcp@1.10.1.patch
similarity index 73%
rename from patches/chrome-devtools-mcp@1.9.0.patch
rename to patches/chrome-devtools-mcp@1.10.1.patch
index ad4ae2c4f461..9664216595f1 100644
--- a/patches/chrome-devtools-mcp@1.9.0.patch
+++ b/patches/chrome-devtools-mcp@1.10.1.patch
@@ -1,5 +1,5 @@
diff --git a/build/src/McpPage.js b/build/src/McpPage.js
-index 13eb306aea9d516c1e06f4d40a6a61f83bfada11..0c02d5fc9b164dad0f65b74ad926e5cbb6437fcf 100644
+index 3be93a33ad4afb206223290c96fd8749cd49a131..f6887160eb88fb0d19792d20ca6466496ca48b9c 100644
--- a/build/src/McpPage.js
+++ b/build/src/McpPage.js
@@ -1,3 +1,4 @@
@@ -7,7 +7,16 @@ index 13eb306aea9d516c1e06f4d40a6a61f83bfada11..0c02d5fc9b164dad0f65b74ad926e5cb
/**
* @license
* Copyright 2025 Google LLC
-@@ -516,7 +517,7 @@ export class McpPage {
+@@ -105,7 +106,7 @@ import { DevToolsCommentBridge } from './devtools/DevToolsCommentBridge.js';
+ import { createTargetUniverse, } from './devtools/DevtoolsUtils.js';
+ import { ConsoleCollector, NetworkCollector, } from './collectors/PageCollector.js';
+ import { TextSnapshot } from './TextSnapshot.js';
+-import { PredefinedNetworkConditions, DevTools, } from './third_party/index.js';
++import { PredefinedNetworkConditions, DevTools, CdpFrame, } from './third_party/index.js';
+ const DEFAULT_TIMEOUT = 5_000;
+ const NAVIGATION_TIMEOUT = 10_000;
+ import { logger } from './utils/logger.js';
+@@ -549,7 +550,7 @@ export class McpPage {
logger?.(`No backendNodeId for stashed DOM element with index ${index}`);
return `stashed-${index}`;
}
@@ -16,29 +25,133 @@ index 13eb306aea9d516c1e06f4d40a6a61f83bfada11..0c02d5fc9b164dad0f65b74ad926e5cb
if (!cdpElementId) {
logger?.(`Could not get cdpElementId for backend node ${backendNodeId}`);
return `stashed-${index}`;
+@@ -639,44 +640,62 @@ export class McpPage {
+ }
+ }
+ async getMatchedStylesForUid(uid) {
+- if (!this.textSnapshot) {
+- throw new Error(`No snapshot found for page ${this.id ?? '?'}. Use take_snapshot to capture one.`);
+- }
+- const node = this.textSnapshot.idToNode.get(uid);
+- if (!node) {
+- throw new Error(`Element uid "${uid}" not found on page ${this.id}.`);
+- }
+- const backendNodeId = node.backendNodeId;
+- if (!isBackendNodeId(backendNodeId)) {
+- throw new Error(`Failed to resolve backend node ID for element with uid "${uid}".`);
+- }
+- if (!this.#devtoolsUniverse) {
+- throw new Error(`DevTools universe is not available for page ${this.id ?? '?'}.`);
+- }
+- const targetManager = this.#devtoolsUniverse.universe.context.get(DevTools.TargetManager);
+- let domNode;
+- let cssModel = null;
+- for (const dom of targetManager.models(DevTools.DOMModel.DOMModel)) {
+- const nodeMap = await dom.pushNodesByBackendIdsToFrontend(new Set([backendNodeId]));
+- const frontendNode = nodeMap?.get(backendNodeId);
+- if (frontendNode) {
+- domNode = frontendNode;
+- cssModel = dom.target().model(DevTools.CSSModel.CSSModel);
+- break;
++ const env_3 = { stack: [], error: void 0, hasError: false };
++ try {
++ if (!this.textSnapshot) {
++ throw new Error(`No snapshot found for page ${this.id ?? '?'}. Use take_snapshot to capture one.`);
+ }
++ const node = this.textSnapshot.idToNode.get(uid);
++ if (!node) {
++ throw new Error(`Element uid "${uid}" not found on page ${this.id}.`);
++ }
++ const snapshotBackendNodeId = node.backendNodeId;
++ if (!isBackendNodeId(snapshotBackendNodeId)) {
++ throw new Error(`Failed to resolve backend node ID for element with uid "${uid}".`);
++ }
++ const handle = __addDisposableResource(env_3, await this.#resolveElementHandle(node, uid), false);
++ const liveBackendNodeId = await handle.backendNodeId();
++ if (liveBackendNodeId !== snapshotBackendNodeId) {
++ throw new Error(`Element with uid "${uid}" was detached or no longer exists on the page. Please take a new snapshot with take_snapshot.`);
++ }
++ const backendNodeId = snapshotBackendNodeId;
++ if (!this.#devtoolsUniverse) {
++ throw new Error(`DevTools universe is not available for page ${this.id ?? '?'}.`);
++ }
++ const targetManager = this.#devtoolsUniverse.universe.context.get(DevTools.TargetManager);
++ const frame = handle.frame;
++ const mainFrame = this.pptrPage.mainFrame();
++ const frameTarget = frame instanceof CdpFrame ? targetManager.targetById(frame._id) : null;
++ const target = frameTarget ??
++ (frame instanceof CdpFrame &&
++ mainFrame instanceof CdpFrame &&
++ frame.client === mainFrame.client
++ ? targetManager.primaryPageTarget()
++ : null);
++ const dom = target?.model(DevTools.DOMModel.DOMModel);
++ const nodeMap = await dom?.pushNodesByBackendIdsToFrontend(new Set([backendNodeId]));
++ const domNode = nodeMap?.get(backendNodeId);
++ const cssModel = dom?.target().model(DevTools.CSSModel.CSSModel);
++ if (!domNode || !cssModel) {
++ throw new Error(`Element with uid "${uid}" was detached or no longer exists on the page. Please take a new snapshot with take_snapshot.`);
++ }
++ const targetElement = domNode.enclosingElementOrSelf();
++ if (!targetElement) {
++ throw new Error(`Element with uid "${uid}" is not an element node and has no parent element.`);
++ }
++ const matchedStyles = await cssModel.getMatchedStyles(targetElement.id);
++ if (!matchedStyles) {
++ throw new Error(`Could not retrieve matched styles for element with uid "${uid}".`);
++ }
++ return matchedStyles;
+ }
+- if (!domNode || !cssModel) {
+- throw new Error(`Element with uid "${uid}" was detached or no longer exists on the page. Please take a new snapshot with take_snapshot.`);
+- }
+- const targetElement = domNode.enclosingElementOrSelf();
+- if (!targetElement) {
+- throw new Error(`Element with uid "${uid}" is not an element node and has no parent element.`);
++ catch (e_3) {
++ env_3.error = e_3;
++ env_3.hasError = true;
+ }
+- const matchedStyles = await cssModel.getMatchedStyles(targetElement.id);
+- if (!matchedStyles) {
+- throw new Error(`Could not retrieve matched styles for element with uid "${uid}".`);
++ finally {
++ __disposeResources(env_3);
+ }
+- return matchedStyles;
+ }
+ async getDevToolsData() {
+ try {
diff --git a/build/src/OPENCLAW_PATCH_NOTICE.md b/build/src/OPENCLAW_PATCH_NOTICE.md
new file mode 100644
-index 0000000000000000000000000000000000000000..7543bd478e184a709e45b54db944df3dd8231336
+index 0000000000000000000000000000000000000000..abdbc42e09793bf885493afc41be15a28d0fe432
--- /dev/null
+++ b/build/src/OPENCLAW_PATCH_NOTICE.md
-@@ -0,0 +1,20 @@
+@@ -0,0 +1,27 @@
+# Temporary OpenClaw snapshot identity patch
+
-+This package remains `chrome-devtools-mcp@1.9.0`. OpenClaw modifies three
++This package remains `chrome-devtools-mcp@1.10.1`. OpenClaw modifies two
+published runtime files to backport the snapshot identity repair proposed in
+https://github.com/ChromeDevTools/chrome-devtools-mcp/pull/2788.
+
+The reviewed upstream source is commit
-+`06c8d4bc44f68bde8bd3fcd97dcc47375df81fe9`.
++`06c8d4bc44f68bde8bd3fcd97dcc47375df81fe9`, forward-ported onto the
++`chrome-devtools-mcp-v1.10.1` source tag at
++`e52c6b59b476c5e04d8dd9fd4bd017ba3b3d65df`.
+
-+- `TextSnapshot.js` carries that reviewed snapshot-owner implementation.
-+- `McpPage.js` supplies the known element frame for DOM-result reverse lookup.
-+- `third_party/index.js` exports its already-bundled `CdpFrame` class, required
-+ by the backport. No bundled dependency implementation is replaced.
++- `TextSnapshot.js` carries the reviewed snapshot-owner implementation.
++- `McpPage.js` supplies the known element frame for DOM-result reverse lookup
++ and resolves CSS-style UIDs through the same guarded snapshot handle. CSS
++ lookup stays within the captured renderer and fails closed when the embedded
++ DevTools target cannot represent a cross-renderer frame.
+
-+The original package is the published npm 1.9.0 tarball with integrity
-+`sha512-RnzXoJiUQ44hpOihWk90uOhLD/CnwDkDy0ldHMZONJ2nYQ+dWN1fq1luHHqyd+7FuYnyIlCY6uTThbN5ut9kSQ==`.
-+The original `LICENSE` and `third_party/THIRD_PARTY_NOTICES` remain unchanged.
++Version 1.10.1 already exports its bundled `CdpFrame` implementation, so this
++patch does not modify third-party bundle code.
++
++The original package is the published npm 1.10.1 tarball with integrity
++`sha512-Klw6HWDqHC/XS1JwZldd2r49aUhbUJN9m9Mvcx4SEueIPXtzuQX+QelxAViobv8YUkDZ7HWDrmViR6LeYK0wAw==`.
++The original `LICENSE` and `build/src/third_party/THIRD_PARTY_NOTICES` remain
++unchanged.
+
+Remove this patch and notice after adopting a published upstream release that
+contains the repair and passes the same snapshot/stdio regression proof.
@@ -447,18 +560,3 @@ index fe2bae7bc287e43d8a7f41f5886496a4f82c0cd7..5b2671a4b416633710886c9089762e0b
reorgInfo.push({ extraNode, attachTarget, descendantIds });
}
for (const { extraNode, attachTarget, descendantIds } of reorgInfo) {
-diff --git a/build/src/third_party/index.js b/build/src/third_party/index.js
-index 5bce2352d7dd5950547fbb83e1c7d495605d9de5..6a86c23c58dc18ca620977357f1c06220e602b6c 100644
---- a/build/src/third_party/index.js
-+++ b/build/src/third_party/index.js
-@@ -1,3 +1,4 @@
-+// Modified by OpenClaw: expose existing CdpFrame for the snapshot backport; see ../OPENCLAW_PATCH_NOTICE.md.
- import childProcess, { execSync, execFile, spawnSync, spawn as spawn$1 } from 'node:child_process';
- import fs, { readdirSync as readdirSync$1, readFileSync as readFileSync$1, accessSync, existsSync, constants as constants$2 } from 'node:fs';
- import os, { tmpdir } from 'node:os';
-@@ -181481,4 +181482,4 @@ const snapshot = snapshot$1;
- const navigation = navigation$1;
- const generateReport = generateReport$1;
-
--export { AsyncDisposableStack, Browser as BrowserEnum, CDPSessionEvent, CdpPage, Client, mcp as DevTools, DisposableStack, KnownDevices, ListRootsRequestSchema, ListRootsResultSchema, Locator, McpServer, Mutex$2 as Mutex, PipeTransport, PredefinedNetworkConditions, RootsListChangedNotificationSchema, ScreenRecorder, SetLevelRequestSchema, StdioClientTransport, StdioServerTransport, SuppressedError$1 as SuppressedError, ajv, detectBrowserPlatform, generateReport, getGcfEncode, getToonEncode, hideBin, navigation, puppeteer, resolveDefaultUserDataDir, index as semver, snapshot, Yargs as yargs, z$2 as zod };
-+export { AsyncDisposableStack, Browser as BrowserEnum, CDPSessionEvent, CdpFrame, CdpPage, Client, mcp as DevTools, DisposableStack, KnownDevices, ListRootsRequestSchema, ListRootsResultSchema, Locator, McpServer, Mutex$2 as Mutex, PipeTransport, PredefinedNetworkConditions, RootsListChangedNotificationSchema, ScreenRecorder, SetLevelRequestSchema, StdioClientTransport, StdioServerTransport, SuppressedError$1 as SuppressedError, ajv, detectBrowserPlatform, generateReport, getGcfEncode, getToonEncode, hideBin, navigation, puppeteer, resolveDefaultUserDataDir, index as semver, snapshot, Yargs as yargs, z$2 as zod };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 44ba5d6a8ed0..20ef30d5602e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -218,7 +218,7 @@ patchedDependencies:
'@novnc/novnc@1.7.0': bfde0e1bda172da3525f9f5014d85ad28073325b74f09609084598a65d2f412d
'@openclaw/proxyline@0.3.12': 9e0969d2dc0abb32610053fba342756afb56f4996c90adfdc34f21ad090fbb0f
baileys@7.0.0-rc14: 03ae85550381d1bda7d014c9aa9f544ad421d35d745d1167fd5d33f7f715dafb
- chrome-devtools-mcp@1.9.0: 832ce1d2ff002b44edb93bae88858d6edd8bc833acc87dd0fe82454d8834e340
+ chrome-devtools-mcp@1.10.1: d31d6e7e187ea9fd4dad87ed3ad08073c77ffcfddd52bc4761b39a2dfce0ac1e
ghostty-web@0.4.0: eb0a0b91bbf843ba8dceeade968072a6c7a6be402fdd1e80d466e54241b62fa6
matrix-js-sdk@42.4.0: 12d9bb9bdc53b7b96d8d887d0bea6b3072d1fef488b51102caec19fb651c8e92
vitest@5.0.1: 90ba2969491e095cc2e92a4d38256dfcf64f85078ec29e1762b9429a00b35901
@@ -294,8 +294,8 @@ importers:
specifier: 6.0.0
version: 6.0.0
chrome-devtools-mcp:
- specifier: 1.9.0
- version: 1.9.0(patch_hash=832ce1d2ff002b44edb93bae88858d6edd8bc833acc87dd0fe82454d8834e340)
+ specifier: 1.10.1
+ version: 1.10.1(patch_hash=d31d6e7e187ea9fd4dad87ed3ad08073c77ffcfddd52bc4761b39a2dfce0ac1e)
clawpdf:
specifier: 0.3.2
version: 0.3.2
@@ -799,8 +799,8 @@ importers:
specifier: 1.30.0
version: 1.30.0(supports-color@10.2.2)(zod@4.6.5)
chrome-devtools-mcp:
- specifier: 1.9.0
- version: 1.9.0(patch_hash=832ce1d2ff002b44edb93bae88858d6edd8bc833acc87dd0fe82454d8834e340)
+ specifier: 1.10.1
+ version: 1.10.1(patch_hash=d31d6e7e187ea9fd4dad87ed3ad08073c77ffcfddd52bc4761b39a2dfce0ac1e)
express:
specifier: 5.2.1
version: 5.2.1(supports-color@10.2.2)
@@ -6702,8 +6702,8 @@ packages:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
- chrome-devtools-mcp@1.9.0:
- resolution: {integrity: sha512-RnzXoJiUQ44hpOihWk90uOhLD/CnwDkDy0ldHMZONJ2nYQ+dWN1fq1luHHqyd+7FuYnyIlCY6uTThbN5ut9kSQ==}
+ chrome-devtools-mcp@1.10.1:
+ resolution: {integrity: sha512-Klw6HWDqHC/XS1JwZldd2r49aUhbUJN9m9Mvcx4SEueIPXtzuQX+QelxAViobv8YUkDZ7HWDrmViR6LeYK0wAw==}
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
hasBin: true
peerDependencies:
@@ -14313,7 +14313,7 @@ snapshots:
chownr@3.0.0: {}
- chrome-devtools-mcp@1.9.0(patch_hash=832ce1d2ff002b44edb93bae88858d6edd8bc833acc87dd0fe82454d8834e340): {}
+ chrome-devtools-mcp@1.10.1(patch_hash=d31d6e7e187ea9fd4dad87ed3ad08073c77ffcfddd52bc4761b39a2dfce0ac1e): {}
cjs-module-lexer@2.2.1: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index c495e6056d42..6cc4352e6763 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -116,10 +116,10 @@ packageExtensions:
patchedDependencies:
"baileys@7.0.0-rc14": patches/baileys@7.0.0-rc14.patch
- "chrome-devtools-mcp@1.9.0": patches/chrome-devtools-mcp@1.9.0.patch
"matrix-js-sdk@42.4.0": patches/matrix-js-sdk@42.4.0.patch
"@novnc/novnc@1.7.0": patches/@novnc__novnc@1.7.0.patch
vitest@5.0.1: patches/vitest@5.0.1.patch
"@awesome.me/webawesome@3.13.0": patches/@awesome.me__webawesome@3.13.0.patch
"@openclaw/proxyline@0.3.12": patches/@openclaw__proxyline@0.3.12.patch
ghostty-web@0.4.0: patches/ghostty-web@0.4.0.patch
+ chrome-devtools-mcp@1.10.1: patches/chrome-devtools-mcp@1.10.1.patch
diff --git a/scripts/check-package-patches.mts b/scripts/check-package-patches.mts
index cec92ca87126..67f7b33e005d 100644
--- a/scripts/check-package-patches.mts
+++ b/scripts/check-package-patches.mts
@@ -11,7 +11,7 @@ import { runAsScript } from "./lib/ts-guard-utils.mts";
const ALLOWED_PATCHED_DEPENDENCIES = new Map([
["@openclaw/proxyline@0.3.12", "patches/@openclaw__proxyline@0.3.12.patch"],
- ["chrome-devtools-mcp@1.9.0", "patches/chrome-devtools-mcp@1.9.0.patch"],
+ ["chrome-devtools-mcp@1.10.1", "patches/chrome-devtools-mcp@1.10.1.patch"],
["@awesome.me/webawesome@3.13.0", "patches/@awesome.me__webawesome@3.13.0.patch"],
["@novnc/novnc@1.7.0", "patches/@novnc__novnc@1.7.0.patch"],
["vitest@5.0.1", "patches/vitest@5.0.1.patch"],
diff --git a/scripts/lib/package-bundled-mcp.mts b/scripts/lib/package-bundled-mcp.mts
index b58919d9d0ce..a0977f24190b 100644
--- a/scripts/lib/package-bundled-mcp.mts
+++ b/scripts/lib/package-bundled-mcp.mts
@@ -64,6 +64,34 @@ const MCP_FILE_HASHES = new Map>([
],
]),
],
+ [
+ "1.10.1",
+ new Map([
+ [PATCHED_MCP_CLI, "9f380d06e1ac05b257e27e708c0cc4b4ba190e285ed6eb6c8aa50978d98a12c5"],
+ [
+ "build/src/bin/chrome-devtools-mcp-main.js",
+ "10603ea8c2ac9f69a42791100701795018ebfaa522a7c93d783ed583cce5cce4",
+ ],
+ ["LICENSE", "58d1e17ffe5109a7ae296caafcadfdbe6a7d176f0bc4ab01e12a689b0499d8bd"],
+ [
+ "build/src/third_party/THIRD_PARTY_NOTICES",
+ "6ae0ce181dbc9ba4217b6aad679d7503ed3ab56c45e6f3647e9ab753aa97b67b",
+ ],
+ [
+ "build/src/TextSnapshot.js",
+ "299833ad0e4cfc171a417afaec41df594e4862fe53a7ada6ba160409f979788b",
+ ],
+ ["build/src/McpPage.js", "47aa13c6b28cc11e1b0883532edea97cfde7563d78d143035850852d09809975"],
+ [
+ "build/src/third_party/index.js",
+ "c988e0684584b75e87ae04b768c4f8ae7064401afe5187ec0d4878b2c6833f12",
+ ],
+ [
+ "build/src/OPENCLAW_PATCH_NOTICE.md",
+ "4bf44b52a80b5860b2160bc83407a5f0dd09f1801c85d0a2b6e4fec26bd7045d",
+ ],
+ ]),
+ ],
]);
const REQUIRED_MCP_FILES = [
"build/src/third_party/devtools-formatter-worker.js",
@@ -80,7 +108,7 @@ export function isSupportedPatchedMcpVersion(version: unknown): version is strin
export function collectPatchedMcpArtifactErrors({
// The node-bootstrap producer checks the current runtime; frozen package
// consumers pass their independently checked exact dependency pin.
- declaredVersion = "1.9.0",
+ declaredVersion = "1.10.1",
manifest,
files,
sha256,
diff --git a/test/scripts/check-openclaw-package-tarball.bundled-mcp.test.ts b/test/scripts/check-openclaw-package-tarball.bundled-mcp.test.ts
index 407f9742f444..2f6b44383852 100644
--- a/test/scripts/check-openclaw-package-tarball.bundled-mcp.test.ts
+++ b/test/scripts/check-openclaw-package-tarball.bundled-mcp.test.ts
@@ -34,7 +34,7 @@ const MCP_PROCESS_TIMEOUT_MS = 180_000;
const MCP_PACKAGE_TEST_TIMEOUT_MS = MCP_PROCESS_TIMEOUT_MS + 60_000;
const packageJson = {
files: ["dist"],
- dependencies: { [MCP_NAME]: "1.9.0" },
+ dependencies: { [MCP_NAME]: "1.10.1" },
bundleDependencies: [MCP_NAME],
};
@@ -81,7 +81,7 @@ function installPatchedMcp(packageRoot: string) {
JSON.stringify({
packages: ["."],
autoInstallPeers: false,
- overrides: { [MCP_NAME]: `file:${join(fixtureRoot, `${MCP_NAME}-1.9.0.tgz`)}` },
+ overrides: { [MCP_NAME]: `file:${join(fixtureRoot, `${MCP_NAME}-1.10.1.tgz`)}` },
}),
);
const pnpm = resolvePnpmRunner({
@@ -215,7 +215,7 @@ describe("bundled browser MCP package", () => {
},
);
expectPackageCommandSuccess(cli, "read bundled browser MCP version");
- expect(cli.stdout.trim()).toBe("1.9.0");
+ expect(cli.stdout.trim()).toBe("1.10.1");
},
undefined,
{
@@ -241,7 +241,7 @@ describe("bundled browser MCP package", () => {
},
{
name: "unpinned dependency",
- manifest: { ...packageJson, dependencies: { [MCP_NAME]: "^1.9.0" } },
+ manifest: { ...packageJson, dependencies: { [MCP_NAME]: "^1.10.1" } },
error: "must be pinned to a supported patched version",
},
{
@@ -298,7 +298,7 @@ describe("bundled browser MCP package", () => {
{
file: "package.json",
change: "modify",
- error: "bundled chrome-devtools-mcp must be ESM version 1.9.0",
+ error: "bundled chrome-devtools-mcp must be ESM version 1.10.1",
},
...[
"build/src/TextSnapshot.js",
diff --git a/test/scripts/package-bundled-mcp.test.ts b/test/scripts/package-bundled-mcp.test.ts
index 9614e52c2fc7..becf5152134e 100644
--- a/test/scripts/package-bundled-mcp.test.ts
+++ b/test/scripts/package-bundled-mcp.test.ts
@@ -1,8 +1,8 @@
import { describe, expect, it } from "vitest";
import { collectPatchedMcpArtifactErrors } from "../../scripts/lib/package-bundled-mcp.mts";
-// Independent expectations from v2026.9.6 (eb377ac59e6) and the 1.9.0 upgrade (7dbfab8c2c7).
-const CONTRACT_HASHES: Record<"1.8.0" | "1.9.0", Record> = {
+// Independent expectations from v2026.9.6 (eb377ac59e6) through the 1.10.1 upgrade.
+const CONTRACT_HASHES: Record<"1.8.0" | "1.9.0" | "1.10.1", Record> = {
"1.8.0": {
"build/src/bin/chrome-devtools-mcp.js":
"9f380d06e1ac05b257e27e708c0cc4b4ba190e285ed6eb6c8aa50978d98a12c5",
@@ -33,18 +33,33 @@ const CONTRACT_HASHES: Record<"1.8.0" | "1.9.0", Record> = {
"build/src/OPENCLAW_PATCH_NOTICE.md":
"0e53a04f337a3760f2f1adab9c20e3b4f07019795f503266c0b68e0f46d55a6c",
},
+ "1.10.1": {
+ "build/src/bin/chrome-devtools-mcp.js":
+ "9f380d06e1ac05b257e27e708c0cc4b4ba190e285ed6eb6c8aa50978d98a12c5",
+ "build/src/bin/chrome-devtools-mcp-main.js":
+ "10603ea8c2ac9f69a42791100701795018ebfaa522a7c93d783ed583cce5cce4",
+ LICENSE: "58d1e17ffe5109a7ae296caafcadfdbe6a7d176f0bc4ab01e12a689b0499d8bd",
+ "build/src/third_party/THIRD_PARTY_NOTICES":
+ "6ae0ce181dbc9ba4217b6aad679d7503ed3ab56c45e6f3647e9ab753aa97b67b",
+ "build/src/TextSnapshot.js": "299833ad0e4cfc171a417afaec41df594e4862fe53a7ada6ba160409f979788b",
+ "build/src/McpPage.js": "47aa13c6b28cc11e1b0883532edea97cfde7563d78d143035850852d09809975",
+ "build/src/third_party/index.js":
+ "c988e0684584b75e87ae04b768c4f8ae7064401afe5187ec0d4878b2c6833f12",
+ "build/src/OPENCLAW_PATCH_NOTICE.md":
+ "4bf44b52a80b5860b2160bc83407a5f0dd09f1801c85d0a2b6e4fec26bd7045d",
+ },
};
it("preserves the bootstrap producer's current-runtime default", () => {
- for (const version of ["1.8.0", "1.9.0"] as const) {
+ for (const version of ["1.8.0", "1.9.0", "1.10.1"] as const) {
const { declaredVersion: _declaredVersion, ...input } = fixture(
version,
CONTRACT_HASHES[version],
);
const errors = collectPatchedMcpArtifactErrors(input);
- if (version === "1.9.0") {
+ if (version === "1.10.1") {
expect(errors).toEqual([]);
} else {
- expect(errors).toContain("bundled chrome-devtools-mcp must be ESM version 1.9.0");
+ expect(errors).toContain("bundled chrome-devtools-mcp must be ESM version 1.10.1");
}
}
});
@@ -74,7 +89,7 @@ describe.each(Object.entries(CONTRACT_HASHES))("patched MCP %s contract", (versi
it("binds the bundled manifest to the exact declared pin", () => {
const input = fixture(version, hashes);
- input.manifest.version = version === "1.8.0" ? "1.9.0" : "1.8.0";
+ input.manifest.version = version === "1.10.1" ? "1.9.0" : "1.10.1";
expect(collectPatchedMcpArtifactErrors(input)).toContain(
"bundled chrome-devtools-mcp must be ESM version " + version,
);
@@ -99,8 +114,11 @@ describe.each(Object.entries(CONTRACT_HASHES))("patched MCP %s contract", (versi
});
it("rejects mixed-version byte contracts", () => {
- const other = CONTRACT_HASHES[version === "1.8.0" ? "1.9.0" : "1.8.0"];
- expect(collectPatchedMcpArtifactErrors(fixture(version, other))).toHaveLength(5);
+ const other = Object.entries(CONTRACT_HASHES).find(([candidate]) => candidate !== version)?.[1];
+ expect(other).toBeDefined();
+ expect(collectPatchedMcpArtifactErrors(fixture(version, other ?? {}))).toContain(
+ "bundled chrome-devtools-mcp has unpatched or changed runtime entry build/src/bin/chrome-devtools-mcp-main.js",
+ );
});
it("does not trust artifact-supplied replacement hashes", () => {