From 2aec002977dad9265f963c1ab2793d8af569bd44 Mon Sep 17 00:00:00 2001 From: Lightning00Blade <34244704+Lightning00Blade@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:22:34 +0000 Subject: [PATCH 01/10] feat: auto handle dialogs during script evaluation Added a new option `dialog` to `waitForEventsAfterAction` to automatically handle dialogs that trigger during execution. We hardcode it to `'accept'` for the `evaluate_script` tool so that `alert()` or `confirm()` don't block and timeout script evaluation. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/McpPage.ts | 2 +- src/WaitForHelper.ts | 16 +++++++++++++++- src/tools/ToolDefinition.ts | 2 +- src/tools/script.ts | 4 ++-- tests/tools/script.test.ts | 23 +++++++++++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/McpPage.ts b/src/McpPage.ts index 1e311bc62..dc4c53785 100644 --- a/src/McpPage.ts +++ b/src/McpPage.ts @@ -112,7 +112,7 @@ export class McpPage implements ContextPage { waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number}, + options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise { const helper = this.createWaitForHelper( this.cpuThrottlingRate, diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index 2dd0f48c1..c6e777cbd 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -126,8 +126,19 @@ export class WaitForHelper { async waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number}, + options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise { + const dialogHandler = (dialog: any) => { + if (options?.dialog === 'dismiss') { + void dialog.dismiss(); + } else { + void dialog.accept(); + } + }; + if (options?.dialog) { + this.#page.on('dialog', dialogHandler); + } + const navigationFinished = this.waitForNavigationStarted() .then(navigationStated => { if (navigationStated) { @@ -158,6 +169,9 @@ export class WaitForHelper { logger(error); } finally { this.#abortController.abort(); + if (options?.dialog) { + this.#page.off('dialog', dialogHandler); + } } } } diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 8001e18c7..b6d44f6fa 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -210,7 +210,7 @@ export type ContextPage = Readonly<{ clearDialog(): void; waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number}, + options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise; getInPageTools(): ToolGroup | undefined; }>; diff --git a/src/tools/script.ts b/src/tools/script.ts index 725628bcd..469dccfb0 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -81,7 +81,7 @@ Example with arguments: \`(el) => { .getSelectedMcpPage() .waitForEventsAfterAction(async () => { await performEvaluation(worker, fnString, [], response); - }); + }, {dialog: 'accept'}); return; } @@ -103,7 +103,7 @@ Example with arguments: \`(el) => { await mcpPage.waitForEventsAfterAction(async () => { await performEvaluation(evaluatable, fnString, args, response); - }); + }, {dialog: 'accept'}); } finally { void Promise.allSettled(args.map(arg => arg.dispose())); } diff --git a/tests/tools/script.test.ts b/tests/tools/script.test.ts index 772ebc076..a0dc4f694 100644 --- a/tests/tools/script.test.ts +++ b/tests/tools/script.test.ts @@ -98,6 +98,29 @@ describe('script', () => { }); }); + it('work for scripts that trigger dialogs', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + + await page.setContent(html``); + + await evaluateScript().handler( + { + params: { + function: String(() => { + alert('hello'); + return 'Works'; + }), + }, + }, + response, + context, + ); + const lineEvaluation = response.responseLines.at(2)!; + assert.strictEqual(JSON.parse(lineEvaluation), 'Works'); + }); + }); + it('work for async functions', async () => { await withMcpContext(async (response, context) => { const page = context.getSelectedPptrPage(); From c2dc3a2228f84235591551d26ed550e3eafd4a3a Mon Sep 17 00:00:00 2001 From: Lightning00Blade <34244704+Lightning00Blade@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:41:47 +0000 Subject: [PATCH 02/10] fix: move dialogHandler inside conditional block Moved `dialogHandler` inside the `if (options?.dialog)` block to avoid unnecessarily declaring it when it is not needed. Also imported the correct `Dialog` type from Puppeteer. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/WaitForHelper.ts | 23 ++++++++++++----------- src/tools/script.ts | 18 +++++++++++------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index c6e777cbd..9d97bbf43 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -5,7 +5,7 @@ */ import {logger} from './logger.js'; -import type {Page, Protocol, CdpPage} from './third_party/index.js'; +import type {Page, Protocol, CdpPage, Dialog} from './third_party/index.js'; import type {PredefinedNetworkConditions} from './third_party/index.js'; export class WaitForHelper { @@ -128,15 +128,16 @@ export class WaitForHelper { action: () => Promise, options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise { - const dialogHandler = (dialog: any) => { - if (options?.dialog === 'dismiss') { - void dialog.dismiss(); - } else { - void dialog.accept(); - } - }; + let dialogHandler: ((dialog: Dialog) => void) | undefined; if (options?.dialog) { - this.#page.on('dialog', dialogHandler); + dialogHandler = (dialog: Dialog) => { + if (options.dialog === 'dismiss') { + void dialog.dismiss(); + } else { + void dialog.accept(); + } + }; + (this.#page as unknown as Page).on('dialog', dialogHandler); } const navigationFinished = this.waitForNavigationStarted() @@ -169,8 +170,8 @@ export class WaitForHelper { logger(error); } finally { this.#abortController.abort(); - if (options?.dialog) { - this.#page.off('dialog', dialogHandler); + if (options?.dialog && dialogHandler) { + (this.#page as unknown as Page).off('dialog', dialogHandler); } } } diff --git a/src/tools/script.ts b/src/tools/script.ts index 469dccfb0..fff3383e8 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -77,11 +77,12 @@ Example with arguments: \`(el) => { } const worker = await getWebWorker(context, serviceWorkerId); - await context - .getSelectedMcpPage() - .waitForEventsAfterAction(async () => { + await context.getSelectedMcpPage().waitForEventsAfterAction( + async () => { await performEvaluation(worker, fnString, [], response); - }, {dialog: 'accept'}); + }, + {dialog: 'accept'}, + ); return; } @@ -101,9 +102,12 @@ Example with arguments: \`(el) => { const evaluatable = await getPageOrFrame(page, frames); - await mcpPage.waitForEventsAfterAction(async () => { - await performEvaluation(evaluatable, fnString, args, response); - }, {dialog: 'accept'}); + await mcpPage.waitForEventsAfterAction( + async () => { + await performEvaluation(evaluatable, fnString, args, response); + }, + {dialog: 'accept'}, + ); } finally { void Promise.allSettled(args.map(arg => arg.dispose())); } From f753ea8f4c4cce6c988e2b272050c1405383dcba Mon Sep 17 00:00:00 2001 From: Lightning00Blade <34244704+Lightning00Blade@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:11:36 +0000 Subject: [PATCH 03/10] fix: type page properly as Page Removed the unnecessary type cast by typing `#page` properly as `Page`. Casts to `CdpPage` are now isolated to where `_client()` is required. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/WaitForHelper.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index 9d97bbf43..3d715f02e 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -10,7 +10,7 @@ import type {PredefinedNetworkConditions} from './third_party/index.js'; export class WaitForHelper { #abortController = new AbortController(); - #page: CdpPage; + #page: Page; #stableDomTimeout: number; #stableDomFor: number; #expectNavigationIn: number; @@ -25,7 +25,7 @@ export class WaitForHelper { this.#stableDomFor = 100 * cpuTimeoutMultiplier; this.#expectNavigationIn = 100 * cpuTimeoutMultiplier; this.#navigationTimeout = 3000 * networkTimeoutMultiplier; - this.#page = page as unknown as CdpPage; + this.#page = page; } /** @@ -101,10 +101,11 @@ export class WaitForHelper { resolve(true); }; - this.#page._client().on('Page.frameStartedNavigating', listener); + const client = (this.#page as unknown as CdpPage)._client(); + client.on('Page.frameStartedNavigating', listener); this.#abortController.signal.addEventListener('abort', () => { resolve(false); - this.#page._client().off('Page.frameStartedNavigating', listener); + client.off('Page.frameStartedNavigating', listener); }); }); @@ -128,16 +129,18 @@ export class WaitForHelper { action: () => Promise, options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise { - let dialogHandler: ((dialog: Dialog) => void) | undefined; if (options?.dialog) { - dialogHandler = (dialog: Dialog) => { + const dialogHandler = (dialog: Dialog) => { if (options.dialog === 'dismiss') { void dialog.dismiss(); } else { void dialog.accept(); } }; - (this.#page as unknown as Page).on('dialog', dialogHandler); + this.#page.on('dialog', dialogHandler); + this.#abortController.signal.addEventListener('abort', () => { + this.#page.off('dialog', dialogHandler); + }); } const navigationFinished = this.waitForNavigationStarted() @@ -170,9 +173,6 @@ export class WaitForHelper { logger(error); } finally { this.#abortController.abort(); - if (options?.dialog && dialogHandler) { - (this.#page as unknown as Page).off('dialog', dialogHandler); - } } } } From 0003dae64cf48bfc0a7c01b2b4f08abd90026db5 Mon Sep 17 00:00:00 2001 From: Lightning00Blade <34244704+Lightning00Blade@users.noreply.github.com> Date: Thu, 2 Apr 2026 09:32:56 +0000 Subject: [PATCH 04/10] fix: revert `#page` type change and use `@ts-expect-error` Reverted the type of `#page` back to `CdpPage` and handled the `Dialog` compatibility issue locally using `@ts-expect-error` instead of a cast. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/WaitForHelper.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index 3d715f02e..f09a44db8 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -10,7 +10,7 @@ import type {PredefinedNetworkConditions} from './third_party/index.js'; export class WaitForHelper { #abortController = new AbortController(); - #page: Page; + #page: CdpPage; #stableDomTimeout: number; #stableDomFor: number; #expectNavigationIn: number; @@ -25,7 +25,7 @@ export class WaitForHelper { this.#stableDomFor = 100 * cpuTimeoutMultiplier; this.#expectNavigationIn = 100 * cpuTimeoutMultiplier; this.#navigationTimeout = 3000 * networkTimeoutMultiplier; - this.#page = page; + this.#page = page as unknown as CdpPage; } /** @@ -101,11 +101,10 @@ export class WaitForHelper { resolve(true); }; - const client = (this.#page as unknown as CdpPage)._client(); - client.on('Page.frameStartedNavigating', listener); + this.#page._client().on('Page.frameStartedNavigating', listener); this.#abortController.signal.addEventListener('abort', () => { resolve(false); - client.off('Page.frameStartedNavigating', listener); + this.#page._client().off('Page.frameStartedNavigating', listener); }); }); @@ -137,8 +136,10 @@ export class WaitForHelper { void dialog.accept(); } }; + // @ts-expect-error The Dialog type from CdpPage and Page are incompatible due to private properties. this.#page.on('dialog', dialogHandler); this.#abortController.signal.addEventListener('abort', () => { + // @ts-expect-error The Dialog type from CdpPage and Page are incompatible due to private properties. this.#page.off('dialog', dialogHandler); }); } From c8e37726002aa9bca0ac6b1bb0c627732eb23465 Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov Date: Thu, 2 Apr 2026 15:01:06 +0200 Subject: [PATCH 05/10] fix --- src/WaitForHelper.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index f09a44db8..97536ca83 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -136,10 +136,8 @@ export class WaitForHelper { void dialog.accept(); } }; - // @ts-expect-error The Dialog type from CdpPage and Page are incompatible due to private properties. this.#page.on('dialog', dialogHandler); this.#abortController.signal.addEventListener('abort', () => { - // @ts-expect-error The Dialog type from CdpPage and Page are incompatible due to private properties. this.#page.off('dialog', dialogHandler); }); } From 95d377b070655ef65536dd698add5f9f4af62ba7 Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov Date: Thu, 2 Apr 2026 15:18:44 +0200 Subject: [PATCH 06/10] fix --- src/WaitForHelper.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index 97536ca83..5546b2155 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -126,15 +126,11 @@ export class WaitForHelper { async waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, + options?: {timeout?: number; handleDialog?: boolean}, ): Promise { - if (options?.dialog) { - const dialogHandler = (dialog: Dialog) => { - if (options.dialog === 'dismiss') { - void dialog.dismiss(); - } else { - void dialog.accept(); - } + if (options?.handleDialog) { + const dialogHandler = (dialog: Pick) => { + void dialog.accept(); }; this.#page.on('dialog', dialogHandler); this.#abortController.signal.addEventListener('abort', () => { From 0585595a1d682b35677bb0fde70c477e519d914b Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov Date: Thu, 2 Apr 2026 15:21:39 +0200 Subject: [PATCH 07/10] fix --- src/McpPage.ts | 2 +- src/tools/ToolDefinition.ts | 2 +- src/tools/script.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/McpPage.ts b/src/McpPage.ts index dc4c53785..a4500bc8c 100644 --- a/src/McpPage.ts +++ b/src/McpPage.ts @@ -112,7 +112,7 @@ export class McpPage implements ContextPage { waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, + options?: {timeout?: number; handleDialog?: boolean}, ): Promise { const helper = this.createWaitForHelper( this.cpuThrottlingRate, diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index b6d44f6fa..edea24959 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -210,7 +210,7 @@ export type ContextPage = Readonly<{ clearDialog(): void; waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, + options?: {timeout?: number; handleDialog?: boolean}, ): Promise; getInPageTools(): ToolGroup | undefined; }>; diff --git a/src/tools/script.ts b/src/tools/script.ts index fff3383e8..6c6e87e5d 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -81,7 +81,7 @@ Example with arguments: \`(el) => { async () => { await performEvaluation(worker, fnString, [], response); }, - {dialog: 'accept'}, + {handleDialog: true}, ); return; } @@ -106,7 +106,7 @@ Example with arguments: \`(el) => { async () => { await performEvaluation(evaluatable, fnString, args, response); }, - {dialog: 'accept'}, + {handleDialog: true}, ); } finally { void Promise.allSettled(args.map(arg => arg.dispose())); From b92b5c6c8803b677e12bf40c720526c131bd38a8 Mon Sep 17 00:00:00 2001 From: Lightning00Blade <34244704+Lightning00Blade@users.noreply.github.com> Date: Thu, 2 Apr 2026 14:04:44 +0000 Subject: [PATCH 08/10] fix: avoid evaluate sync deadlock and handle dialog rejections Replaced `alert()` with `setTimeout(() => alert(), 10)` in `script.test.ts` to prevent headless Chromium deadlocks during `evaluate` in CI environments. Additionally added `.catch(logger)` when resolving dialog actions to prevent unhandled promise rejections if the dialog closes too quickly or fails. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .github/plugin/plugin.json | 11 - .npmrc | 1 - .prettierignore | 5 +- README.md | 37 +- package-lock.json | 378 +++++++------- package.json | 7 +- release-please-config.json | 5 - scripts/update_tool_call_metrics.ts | 65 --- src/McpPage.ts | 2 +- src/McpResponse.ts | 56 --- src/WaitForHelper.ts | 14 +- src/bin/check-latest-version.ts | 32 -- src/bin/chrome-devtools-mcp-cli-options.ts | 7 +- src/bin/chrome-devtools-mcp-main.ts | 5 - src/bin/chrome-devtools.ts | 5 - src/daemon/daemon.ts | 4 + src/telemetry/ClearcutLogger.ts | 24 +- src/telemetry/toolMetricsUtils.ts | 126 ----- src/telemetry/tool_call_metrics.json | 543 --------------------- src/third_party/index.ts | 2 +- src/tools/ToolDefinition.ts | 2 +- src/tools/inPage.ts | 38 +- src/tools/script.ts | 4 +- src/utils/check-for-updates.ts | 96 ---- test_dialog.js | 21 + test_dialog.mjs | 20 + test_dialog2.mjs | 26 + tests/McpContext.test.ts | 4 + tests/McpResponse.test.ts | 250 ---------- tests/check-for-updates.test.ts | 148 ------ tests/telemetry/toolMetricsUtils.test.ts | 263 ---------- tests/tools/inPage.test.ts | 93 ---- tests/tools/script.test.ts | 5 +- tsconfig.json | 7 +- 34 files changed, 305 insertions(+), 2001 deletions(-) delete mode 100644 .github/plugin/plugin.json delete mode 100644 .npmrc delete mode 100644 scripts/update_tool_call_metrics.ts delete mode 100644 src/bin/check-latest-version.ts delete mode 100644 src/telemetry/toolMetricsUtils.ts delete mode 100644 src/telemetry/tool_call_metrics.json delete mode 100644 src/utils/check-for-updates.ts create mode 100644 test_dialog.js create mode 100644 test_dialog.mjs create mode 100644 test_dialog2.mjs delete mode 100644 tests/check-for-updates.test.ts delete mode 100644 tests/telemetry/toolMetricsUtils.test.ts diff --git a/.github/plugin/plugin.json b/.github/plugin/plugin.json deleted file mode 100644 index 51464ff4a..000000000 --- a/.github/plugin/plugin.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "chrome-devtools-mcp", - "version": "0.20.3", - "description": "Reliable automation, in-depth debugging, and performance analysis in Chrome using Chrome DevTools and Puppeteer", - "mcpServers": { - "chrome-devtools": { - "command": "npx", - "args": ["chrome-devtools-mcp@latest"] - } - } -} diff --git a/.npmrc b/.npmrc deleted file mode 100644 index cafe685a1..000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=true diff --git a/.prettierignore b/.prettierignore index c357cfaf0..0a39840d3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,3 @@ # Prettier-only ignores. CHANGELOG.md -src/third_party/lighthouse-devtools-mcp-bundle.js - -# Release-please formatting brakes CI checks -.claude-plugin/plugin.json +src/third_party/lighthouse-devtools-mcp-bundle.js \ No newline at end of file diff --git a/README.md b/README.md index 577cf7bb1..14eead745 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,7 @@ Google handles this data in accordance with the [Google Privacy Policy](https:// Google's collection of usage statistics for Chrome DevTools MCP is independent from the Chrome browser's usage statistics. Opting out of Chrome metrics does not automatically opt you out of this tool, and vice-versa. -Collection is disabled if `CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS` or `CI` env variables are set. - -## Update checks - -By default, the server periodically checks the npm registry for updates and logs a notification when a newer version is available. -You can disable these update checks by setting the `CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS` environment variable. +Collection is disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set. ## Requirements @@ -240,22 +235,6 @@ Configure the following fields and press `CTRL+S` to save the configuration:
Copilot / VS Code -**Install as a Plugin (Recommended)** - -The easiest way to get up and running is to install `chrome-devtools-mcp` as an agent plugin. -This bundles the **MCP server** and all **skills** together, so your agent gets both the tools -and the expert guidance it needs to use them effectively. - -1. Open the **Command Palette** (`Cmd+Shift+P` on macOS or `Ctrl+Shift+P` on Windows/Linux). -2. Search for and run the **Chat: Install Plugin From Source** command. -3. Paste in our repository URL: `https://github.com/ChromeDevTools/chrome-devtools-mcp` - -That's it! Your agent is now supercharged with Chrome DevTools capabilities. - ---- - -**Install as an MCP Server (MCP only)** - **Click the button to install:** [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=io.github.ChromeDevTools%2Fchrome-devtools-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22chrome-devtools-mcp%22%5D%2C%22env%22%3A%7B%7D%7D) @@ -264,7 +243,8 @@ That's it! Your agent is now supercharged with Chrome DevTools capabilities. **Or install manually:** -Follow the VS Code [MCP configuration guide](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server) using the standard config from above, or use the CLI: +Follow the MCP install guide, +with the standard config from above. You can also install the Chrome DevTools MCP server using the VS Code CLI: For macOS and Linux: @@ -423,10 +403,9 @@ qodercli mcp add -s user chrome-devtools -- npx chrome-devtools-mcp@latest
Visual Studio -**Click the button to install:** - -[Install in Visual Studio](https://vs-open.link/mcp-install?%7B%22name%22%3A%22chrome-devtools%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22chrome-devtools-mcp%40latest%22%5D%7D) + **Click the button to install:** + [Install in Visual Studio](https://vs-open.link/mcp-install?%7B%22name%22%3A%22chrome-devtools%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22chrome-devtools-mcp%40latest%22%5D%7D)
@@ -560,10 +539,6 @@ The Chrome DevTools MCP server supports the following configuration option: If enabled, ignores errors relative to self-signed and expired certificates. Use with caution. - **Type:** boolean -- **`--experimentalVision`/ `--experimental-vision`** - Whether to enable coordinate-based tools such as click_at(x,y). Usually requires a computer-use model able to produce accurate coordinates by looking at screenshots. - - **Type:** boolean - - **`--experimentalScreencast`/ `--experimental-screencast`** Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH. - **Type:** boolean @@ -597,7 +572,7 @@ The Chrome DevTools MCP server supports the following configuration option: - **Default:** `true` - **`--usageStatistics`/ `--usage-statistics`** - Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if `CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS` or `CI` env variables are set. + Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set. - **Type:** boolean - **Default:** `true` diff --git a/package-lock.json b/package-lock.json index 22e629cb8..e3e6fd45f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,12 +37,12 @@ "lighthouse": "13.0.3", "prettier": "^3.6.2", "puppeteer": "24.40.0", - "rollup": "4.60.1", + "rollup": "4.59.1", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-license": "^3.6.0", "sinon": "^21.0.0", "tiktoken": "^1.0.22", - "typescript": "^6.0.2", + "typescript": "^5.9.2", "typescript-eslint": "^8.43.0", "yargs": "18.0.0" }, @@ -381,9 +381,9 @@ } }, "node_modules/@google/genai": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.48.0.tgz", - "integrity": "sha512-plonYK4ML2PrxsRD9SeqmFt76eREWkQdPCglOA6aYDzL1AAbE+7PUnT54SvpWGfws13L0AZEqGSpL7+1IPnTxQ==", + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.46.0.tgz", + "integrity": "sha512-ewPMN5JkKfgU5/kdco9ZhXBHDPhVqZpMQqIFQhwsHLf8kyZfx1cNpw1pHo1eV6PGEW7EhIBFi3aYZraFndAXqg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1536,9 +1536,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.1.tgz", + "integrity": "sha512-xB0b51TB7IfDEzAojXahmr+gfA00uYVInJGgNNkeQG6RPnCPGr7udsylFLTubuIUSRE6FkcI1NElyRt83PP5oQ==", "cpu": [ "arm" ], @@ -1550,9 +1550,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.1.tgz", + "integrity": "sha512-XOjPId0qwSDKHaIsdzHJtKCxX0+nH8MhBwvrNsT7tVyKmdTx1jJ4XzN5RZXCdTzMpufLb+B8llTC0D8uCrLhcw==", "cpu": [ "arm64" ], @@ -1564,9 +1564,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.1.tgz", + "integrity": "sha512-vQuRd28p0gQpPrS6kppd8IrWmFo42U8Pz1XLRjSZXq5zCqyMDYFABT7/sywL11mO1EL10Qhh7MVPEwkG8GiBeg==", "cpu": [ "arm64" ], @@ -1578,9 +1578,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.1.tgz", + "integrity": "sha512-x6VG6U29+Ivlnajrg1IHdzXeAwSoEHBFVO+CtC9Brugx6de712CUJobRUxsIA0KYrQvCmzNrMPFTT1A4CCqNTg==", "cpu": [ "x64" ], @@ -1592,9 +1592,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.1.tgz", + "integrity": "sha512-Sgi0Uo6t1YCHJMNO3Y8+bm+SvOanUGkoZKn/VJPwYUe2kp31X5KnXmzKd/NjW8iA3gFcfNZ64zh14uOGrIllCQ==", "cpu": [ "arm64" ], @@ -1606,9 +1606,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.1.tgz", + "integrity": "sha512-AM4xnwEZwukdhk7laMWfzWu9JGSVnJd+Fowt6Fd7QW1nrf3h0Hp7Qx5881M4aqrUlKBCybOxz0jofvIIfl7C5g==", "cpu": [ "x64" ], @@ -1620,9 +1620,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.1.tgz", + "integrity": "sha512-KUizqxpwaR2AZdAUsMWfL/C94pUu7TKpoPd88c8yFVixJ+l9hejkrwoK5Zj3wiNh65UeyryKnJyxL1b7yNqFQA==", "cpu": [ "arm" ], @@ -1634,9 +1634,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.1.tgz", + "integrity": "sha512-MZoQ/am77ckJtZGFAtPucgUuJWiop3m2R3lw7tC0QCcbfl4DRhQUBUkHWCkcrT3pqy5Mzv5QQgY6Dmlba6iTWg==", "cpu": [ "arm" ], @@ -1648,9 +1648,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.1.tgz", + "integrity": "sha512-Sez95TP6xGjkWB1608EfhCX1gdGrO5wzyN99VqzRtC17x/1bhw5VU1V0GfKUwbW/Xr1J8mSasoFoJa6Y7aGGSA==", "cpu": [ "arm64" ], @@ -1662,9 +1662,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.1.tgz", + "integrity": "sha512-9Cs2Seq98LWNOJzR89EGTZoiP8EkZ9UbQhBlDgfAkM6asVna1xJ04W2CLYWDN/RpUgOjtQvcv8wQVi1t5oQazA==", "cpu": [ "arm64" ], @@ -1676,9 +1676,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.1.tgz", + "integrity": "sha512-n9yqttftgFy7IrNEnHy1bOp6B4OSe8mJDiPkT7EqlM9FnKOwUMnCK62ixW0Kd9Clw0/wgvh8+SqaDXMFvw3KqQ==", "cpu": [ "loong64" ], @@ -1690,9 +1690,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.1.tgz", + "integrity": "sha512-SfpNXDzVTqs/riak4xXcLpq5gIQWsqGWMhN1AGRQKB4qGSs4r0sEs3ervXPcE1O9RsQ5bm8Muz6zmQpQnPss1g==", "cpu": [ "loong64" ], @@ -1704,9 +1704,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.1.tgz", + "integrity": "sha512-LjaChED0wQnjKZU+tsmGbN+9nN1XhaWUkAlSbTdhpEseCS4a15f/Q8xC2BN4GDKRzhhLZpYtJBZr2NZhR0jvNw==", "cpu": [ "ppc64" ], @@ -1718,9 +1718,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.1.tgz", + "integrity": "sha512-ojW7iTJSIs4pwB2xV6QXGwNyDctvXOivYllttuPbXguuKDX5vwpqYJsHc6D2LZzjDGHML414Tuj3LvVPe1CT1A==", "cpu": [ "ppc64" ], @@ -1732,9 +1732,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.1.tgz", + "integrity": "sha512-FP+Q6WTcxxvsr0wQczhSE+tOZvFPV8A/mUE6mhZYFW9/eea/y/XqAgRoLLMuE9Cz0hfX5bi7p116IWoB+P237A==", "cpu": [ "riscv64" ], @@ -1746,9 +1746,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.1.tgz", + "integrity": "sha512-L1uD9b/Ig8Z+rn1KttCJjwhN1FgjRMBKsPaBsDKkfUl7GfFq71pU4vWCnpOsGljycFEbkHWARZLf4lMYg3WOLw==", "cpu": [ "riscv64" ], @@ -1760,9 +1760,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.1.tgz", + "integrity": "sha512-EZc9NGTk/oSUzzOD4nYY4gIjteo2M3CiozX6t1IXGCOdgxJTlVu/7EdPeiqeHPSIrxkLhavqpBAUCfvC6vBOug==", "cpu": [ "s390x" ], @@ -1774,9 +1774,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.1.tgz", + "integrity": "sha512-NQ9KyU1Anuy59L8+HHOKM++CoUxrQWrZWXRik4BJFm+7i5NP6q/SW43xIBr80zzt+PDBJ7LeNmloQGfa0JGk0w==", "cpu": [ "x64" ], @@ -1788,9 +1788,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.1.tgz", + "integrity": "sha512-GZkLk2t6naywsveSFBsEb0PLU+JC9ggVjbndsbG20VPhar6D1gkMfCx4NfP9owpovBXTN+eRdqGSkDGIxPHhmQ==", "cpu": [ "x64" ], @@ -1802,9 +1802,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.1.tgz", + "integrity": "sha512-1hjG9Jpl2KDOetr64iQd8AZAEjkDUUK5RbDkYWsViYLC1op1oNzdjMJeFiofcGhqbNTaY2kfgqowE7DILifsrA==", "cpu": [ "x64" ], @@ -1816,9 +1816,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.1.tgz", + "integrity": "sha512-ARoKfflk0SiiYm3r1fmF73K/yB+PThmOwfWCk1sr7x/k9dc3uGLWuEE9if+Pw21el8MSpp3TMnG5vLNsJ/MMGQ==", "cpu": [ "arm64" ], @@ -1830,9 +1830,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.1.tgz", + "integrity": "sha512-oOST61G6VM45Mz2vdzWMr1s2slI7y9LqxEV5fCoWi2MDONmMvgsJVHSXxce/I2xOSZPTZ47nDPOl1tkwKWSHcw==", "cpu": [ "arm64" ], @@ -1844,9 +1844,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.1.tgz", + "integrity": "sha512-x5WgLi5dWpRz7WclKBGEF15LcWTh0ewrHM6Cq4A+WUbkysUMZNeqt05bwPonOQ3ihPS/WMhAZV5zB1DfnI4Sxg==", "cpu": [ "ia32" ], @@ -1858,9 +1858,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.1.tgz", + "integrity": "sha512-wS+zHAJRVP5zOL0e+a3V3E/NTEwM2HEvvNKoDy5Xcfs0o8lljxn+EAFPkUsxihBdmDq1JWzXmmB9cbssCPdxxw==", "cpu": [ "x64" ], @@ -1872,9 +1872,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.1.tgz", + "integrity": "sha512-rhHyrMeLpErT/C7BxcEsU4COHQUzHyrPYW5tOZUeUhziNtRuYxmDWvqQqzpuUt8xpOgmbKa1btGXfnA/ANVO+g==", "cpu": [ "x64" ], @@ -2257,20 +2257,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", - "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz", + "integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/type-utils": "8.58.0", - "@typescript-eslint/utils": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/type-utils": "8.57.1", + "@typescript-eslint/utils": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2282,20 +2282,20 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.43.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", - "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.1.tgz", + "integrity": "sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", "debug": "^4.4.3" }, "engines": { @@ -2307,18 +2307,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", - "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz", + "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.0", - "@typescript-eslint/types": "^8.58.0", + "@typescript-eslint/tsconfig-utils": "^8.57.1", + "@typescript-eslint/types": "^8.57.1", "debug": "^4.4.3" }, "engines": { @@ -2329,18 +2329,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", - "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz", + "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0" + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2351,9 +2351,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", - "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz", + "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==", "dev": true, "license": "MIT", "engines": { @@ -2364,21 +2364,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", - "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz", + "integrity": "sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1", + "@typescript-eslint/utils": "8.57.1", "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2389,13 +2389,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", - "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz", + "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==", "dev": true, "license": "MIT", "engines": { @@ -2407,21 +2407,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", - "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz", + "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.58.0", - "@typescript-eslint/tsconfig-utils": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/project-service": "8.57.1", + "@typescript-eslint/tsconfig-utils": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2431,7 +2431,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { @@ -2458,13 +2458,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.2" }, "engines": { "node": "18 || 20 || >=22" @@ -2474,16 +2474,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", - "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz", + "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0" + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2494,17 +2494,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", - "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz", + "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/types": "8.57.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -7715,9 +7715,9 @@ } }, "node_modules/rollup": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.1.tgz", + "integrity": "sha512-iZKH8BeoCwTCBTZBZWQQMreekd4mdomwdjIQ40GC1oZm6o+8PnNMIxFOiCsGMWeS8iDJ7KZcl7KwmKk/0HOQpA==", "dev": true, "license": "MIT", "dependencies": { @@ -7731,31 +7731,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.1", - "@rollup/rollup-android-arm64": "4.60.1", - "@rollup/rollup-darwin-arm64": "4.60.1", - "@rollup/rollup-darwin-x64": "4.60.1", - "@rollup/rollup-freebsd-arm64": "4.60.1", - "@rollup/rollup-freebsd-x64": "4.60.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", - "@rollup/rollup-linux-arm-musleabihf": "4.60.1", - "@rollup/rollup-linux-arm64-gnu": "4.60.1", - "@rollup/rollup-linux-arm64-musl": "4.60.1", - "@rollup/rollup-linux-loong64-gnu": "4.60.1", - "@rollup/rollup-linux-loong64-musl": "4.60.1", - "@rollup/rollup-linux-ppc64-gnu": "4.60.1", - "@rollup/rollup-linux-ppc64-musl": "4.60.1", - "@rollup/rollup-linux-riscv64-gnu": "4.60.1", - "@rollup/rollup-linux-riscv64-musl": "4.60.1", - "@rollup/rollup-linux-s390x-gnu": "4.60.1", - "@rollup/rollup-linux-x64-gnu": "4.60.1", - "@rollup/rollup-linux-x64-musl": "4.60.1", - "@rollup/rollup-openbsd-x64": "4.60.1", - "@rollup/rollup-openharmony-arm64": "4.60.1", - "@rollup/rollup-win32-arm64-msvc": "4.60.1", - "@rollup/rollup-win32-ia32-msvc": "4.60.1", - "@rollup/rollup-win32-x64-gnu": "4.60.1", - "@rollup/rollup-win32-x64-msvc": "4.60.1", + "@rollup/rollup-android-arm-eabi": "4.59.1", + "@rollup/rollup-android-arm64": "4.59.1", + "@rollup/rollup-darwin-arm64": "4.59.1", + "@rollup/rollup-darwin-x64": "4.59.1", + "@rollup/rollup-freebsd-arm64": "4.59.1", + "@rollup/rollup-freebsd-x64": "4.59.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.1", + "@rollup/rollup-linux-arm-musleabihf": "4.59.1", + "@rollup/rollup-linux-arm64-gnu": "4.59.1", + "@rollup/rollup-linux-arm64-musl": "4.59.1", + "@rollup/rollup-linux-loong64-gnu": "4.59.1", + "@rollup/rollup-linux-loong64-musl": "4.59.1", + "@rollup/rollup-linux-ppc64-gnu": "4.59.1", + "@rollup/rollup-linux-ppc64-musl": "4.59.1", + "@rollup/rollup-linux-riscv64-gnu": "4.59.1", + "@rollup/rollup-linux-riscv64-musl": "4.59.1", + "@rollup/rollup-linux-s390x-gnu": "4.59.1", + "@rollup/rollup-linux-x64-gnu": "4.59.1", + "@rollup/rollup-linux-x64-musl": "4.59.1", + "@rollup/rollup-openbsd-x64": "4.59.1", + "@rollup/rollup-openharmony-arm64": "4.59.1", + "@rollup/rollup-win32-arm64-msvc": "4.59.1", + "@rollup/rollup-win32-ia32-msvc": "4.59.1", + "@rollup/rollup-win32-x64-gnu": "4.59.1", + "@rollup/rollup-win32-x64-msvc": "4.59.1", "fsevents": "~2.3.2" } }, @@ -8702,9 +8702,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -8871,9 +8871,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8885,16 +8885,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", - "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.1.tgz", + "integrity": "sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.0", - "@typescript-eslint/parser": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/utils": "8.58.0" + "@typescript-eslint/eslint-plugin": "8.57.1", + "@typescript-eslint/parser": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1", + "@typescript-eslint/utils": "8.57.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8905,7 +8905,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/unbox-primitive": { diff --git a/package.json b/package.json index 731b2fb6a..29fb93d8e 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "typecheck": "tsc --noEmit", "format": "eslint --cache --fix . && prettier --write --cache .", "check-format": "eslint --cache . && prettier --check --cache .;", - "gen": "npm run build && npm run docs:generate && npm run cli:generate && npm run update-tool-call-metrics && npm run format", + "gen": "npm run build && npm run docs:generate && npm run cli:generate && npm run format", "docs:generate": "node --experimental-strip-types scripts/generate-docs.ts", "start": "npm run build && node build/src/index.js", "start-debug": "DEBUG=mcp:* DEBUG_COLORS=false npm run build && node build/src/index.js", @@ -27,7 +27,6 @@ "prepare": "node --experimental-strip-types scripts/prepare.ts", "verify-server-json-version": "node --experimental-strip-types scripts/verify-server-json-version.ts", "update-lighthouse": "node --experimental-strip-types scripts/update-lighthouse.ts", - "update-tool-call-metrics": "node --experimental-strip-types scripts/update_tool_call_metrics.ts", "verify-npm-package": "node scripts/verify-npm-package.mjs", "eval": "npm run build && node --experimental-strip-types scripts/eval_gemini.ts", "count-tokens": "node --experimental-strip-types scripts/count_tokens.ts" @@ -70,12 +69,12 @@ "lighthouse": "13.0.3", "prettier": "^3.6.2", "puppeteer": "24.40.0", - "rollup": "4.60.1", + "rollup": "4.59.1", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-license": "^3.6.0", "sinon": "^21.0.0", "tiktoken": "^1.0.22", - "typescript": "^6.0.2", + "typescript": "^5.9.2", "typescript-eslint": "^8.43.0", "yargs": "18.0.0" }, diff --git a/release-please-config.json b/release-please-config.json index c45a9ffc2..b09f46882 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -39,11 +39,6 @@ "type": "json", "path": ".claude-plugin/plugin.json", "jsonpath": "version" - }, - { - "type": "json", - "path": ".github/plugin/plugin.json", - "jsonpath": "version" } ] } diff --git a/scripts/update_tool_call_metrics.ts b/scripts/update_tool_call_metrics.ts deleted file mode 100644 index f28e2688d..000000000 --- a/scripts/update_tool_call_metrics.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @license - * Copyright 2026 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as fs from 'node:fs'; -import * as path from 'node:path'; - -import type {ParsedArguments} from '../build/src/bin/chrome-devtools-mcp-cli-options.js'; -import { - applyToExistingMetrics, - generateToolMetrics, - type ToolMetric, -} from '../build/src/telemetry/toolMetricsUtils.js'; -import type {ToolDefinition} from '../build/src/tools/ToolDefinition.js'; -import {createTools} from '../build/src/tools/tools.js'; - -export function HaveUniqueNames(tools: ToolDefinition[]): boolean { - const toolNames = tools.map(tool => tool.name); - const toolNamesSet = new Set(toolNames); - return toolNamesSet.size === toolNames.length; -} - -function writeToolCallMetricsConfig() { - const outputPath = path.resolve('src/telemetry/tool_call_metrics.json'); - - const dir = path.dirname(outputPath); - if (!fs.existsSync(dir)) { - throw new Error(`Error: Directory ${dir} does not exist.`); - } - - const fullTools = createTools({slim: false} as ParsedArguments); - const slimTools = createTools({slim: true} as ParsedArguments); - - const allTools = [...fullTools, ...slimTools]; - - if (!HaveUniqueNames(allTools)) { - throw new Error('Error: Duplicate tool names found.'); - } - - let existingMetrics: ToolMetric[] = []; - if (fs.existsSync(outputPath)) { - try { - existingMetrics = JSON.parse( - fs.readFileSync(outputPath, 'utf8'), - ) as ToolMetric[]; - } catch { - console.warn( - `Warning: Failed to parse existing metrics from ${outputPath}. Starting fresh.`, - ); - } - } - - const newMetrics = generateToolMetrics(allTools); - const mergedMetrics = applyToExistingMetrics(existingMetrics, newMetrics); - - fs.writeFileSync(outputPath, JSON.stringify(mergedMetrics, null, 2) + '\n'); - - console.log( - `Successfully wrote ${mergedMetrics.length} total tool metrics (including deprecated ones) to ${outputPath}`, - ); -} - -writeToolCallMetricsConfig(); diff --git a/src/McpPage.ts b/src/McpPage.ts index a4500bc8c..dc4c53785 100644 --- a/src/McpPage.ts +++ b/src/McpPage.ts @@ -112,7 +112,7 @@ export class McpPage implements ContextPage { waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; handleDialog?: boolean}, + options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise { const helper = this.createWaitForHelper( this.cpuThrottlingRate, diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 77898b5df..ed20e4c4f 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -19,7 +19,6 @@ import type { Page, ResourceType, TextContent, - JSONSchema7Definition, } from './third_party/index.js'; import type {ToolGroup, ToolDefinition} from './tools/inPage.js'; import {handleDialog} from './tools/pages.js'; @@ -42,57 +41,6 @@ interface TraceInsightData { insightName: InsightName; } -export function replaceHtmlElementsWithUids(schema: JSONSchema7Definition) { - if (typeof schema === 'boolean') { - return; - } - - let isHtmlElement = false; - for (const [key, value] of Object.entries(schema)) { - if (key === 'x-mcp-type' && value === 'HTMLElement') { - isHtmlElement = true; - break; - } - } - - if (isHtmlElement) { - schema.properties = {uid: {type: 'string'}}; - schema.required = ['uid']; - } - - if (schema.properties) { - for (const key of Object.keys(schema.properties)) { - replaceHtmlElementsWithUids(schema.properties[key]); - } - } - - if (schema.items) { - if (Array.isArray(schema.items)) { - for (const item of schema.items) { - replaceHtmlElementsWithUids(item); - } - } else { - replaceHtmlElementsWithUids(schema.items); - } - } - - if (schema.anyOf) { - for (const s of schema.anyOf) { - replaceHtmlElementsWithUids(s); - } - } - if (schema.allOf) { - for (const s of schema.allOf) { - replaceHtmlElementsWithUids(s); - } - } - if (schema.oneOf) { - for (const s of schema.oneOf) { - replaceHtmlElementsWithUids(s); - } - } -} - async function getToolGroup( page: McpPage, ): Promise | undefined> { @@ -143,10 +91,6 @@ async function getToolGroup( }, 0); }); }); - - for (const tool of toolGroup?.tools ?? []) { - replaceHtmlElementsWithUids(tool.inputSchema); - } return toolGroup; } diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index 5546b2155..e43e47961 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -126,14 +126,20 @@ export class WaitForHelper { async waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; handleDialog?: boolean}, + options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise { - if (options?.handleDialog) { - const dialogHandler = (dialog: Pick) => { - void dialog.accept(); + if (options?.dialog) { + const dialogHandler = (dialog: Dialog) => { + if (options.dialog === 'dismiss') { + dialog.dismiss().catch((e: unknown) => logger(e)); + } else { + dialog.accept().catch((e: unknown) => logger(e)); + } }; + // @ts-expect-error The Dialog type from CdpPage and Page are incompatible due to private properties. this.#page.on('dialog', dialogHandler); this.#abortController.signal.addEventListener('abort', () => { + // @ts-expect-error The Dialog type from CdpPage and Page are incompatible due to private properties. this.#page.off('dialog', dialogHandler); }); } diff --git a/src/bin/check-latest-version.ts b/src/bin/check-latest-version.ts deleted file mode 100644 index eb45674df..000000000 --- a/src/bin/check-latest-version.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @license - * Copyright 2026 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import fs from 'node:fs/promises'; -import path from 'node:path'; -import process from 'node:process'; - -const cachePath = process.argv[2]; - -if (cachePath) { - try { - const response = await fetch( - 'https://registry.npmjs.org/chrome-devtools-mcp/latest', - ); - const data = response.ok ? await response.json() : null; - - if ( - data && - typeof data === 'object' && - 'version' in data && - typeof data.version === 'string' - ) { - await fs.mkdir(path.dirname(cachePath), {recursive: true}); - await fs.writeFile(cachePath, JSON.stringify({version: data.version})); - } - } catch { - // Ignore errors. - } -} diff --git a/src/bin/chrome-devtools-mcp-cli-options.ts b/src/bin/chrome-devtools-mcp-cli-options.ts index 4b8976f22..80046b115 100644 --- a/src/bin/chrome-devtools-mcp-cli-options.ts +++ b/src/bin/chrome-devtools-mcp-cli-options.ts @@ -160,9 +160,8 @@ export const cliOptions = { }, experimentalVision: { type: 'boolean', - describe: - 'Whether to enable coordinate-based tools such as click_at(x,y). Usually requires a computer-use model able to produce accurate coordinates by looking at screenshots.', - hidden: false, + describe: 'Whether to enable vision tools', + hidden: true, }, experimentalStructuredContent: { type: 'boolean', @@ -233,7 +232,7 @@ export const cliOptions = { type: 'boolean', default: true, describe: - 'Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if `CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS` or `CI` env variables are set.', + 'Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.', }, clearcutEndpoint: { type: 'string', diff --git a/src/bin/chrome-devtools-mcp-main.ts b/src/bin/chrome-devtools-mcp-main.ts index 46100ed94..bfb6bb38e 100644 --- a/src/bin/chrome-devtools-mcp-main.ts +++ b/src/bin/chrome-devtools-mcp-main.ts @@ -12,15 +12,10 @@ import {createMcpServer, logDisclaimers} from '../index.js'; import {logger, saveLogsToFile} from '../logger.js'; import {computeFlagUsage} from '../telemetry/flagUtils.js'; import {StdioServerTransport} from '../third_party/index.js'; -import {checkForUpdates} from '../utils/check-for-updates.js'; import {VERSION} from '../version.js'; import {cliOptions, parseArguments} from './chrome-devtools-mcp-cli-options.js'; -await checkForUpdates( - 'Run `npm install chrome-devtools-mcp@latest` to update.', -); - export const args = parseArguments(VERSION); const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined; diff --git a/src/bin/chrome-devtools.ts b/src/bin/chrome-devtools.ts index e3aab5f45..f417c8373 100644 --- a/src/bin/chrome-devtools.ts +++ b/src/bin/chrome-devtools.ts @@ -21,16 +21,11 @@ import { import {isDaemonRunning, serializeArgs} from '../daemon/utils.js'; import {logDisclaimers} from '../index.js'; import {hideBin, yargs, type CallToolResult} from '../third_party/index.js'; -import {checkForUpdates} from '../utils/check-for-updates.js'; import {VERSION} from '../version.js'; import {commands} from './chrome-devtools-cli-options.js'; import {cliOptions, parseArguments} from './chrome-devtools-mcp-cli-options.js'; -await checkForUpdates( - 'Run `npm install -g chrome-devtools-mcp@latest` and `chrome-devtools start` to update and restart the daemon.', -); - async function start(args: string[]) { const combinedArgs = [...args, ...defaultArgs]; await startDaemon(combinedArgs); diff --git a/src/daemon/daemon.ts b/src/daemon/daemon.ts index 18f6ebad0..cde623745 100644 --- a/src/daemon/daemon.ts +++ b/src/daemon/daemon.ts @@ -55,6 +55,10 @@ async function setupMCPClient() { console.log('Setting up MCP client connection...'); // Create stdio transport for chrome-devtools-mcp + // Workaround for https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/src/client/stdio.ts#L128 + // which causes the console window to show on Windows. + // @ts-expect-error no types for type. + process.type = 'mcp-client'; mcpTransport = new StdioClientTransport({ command: process.execPath, args: [INDEX_SCRIPT_PATH, ...mcpServerArgs], diff --git a/src/telemetry/ClearcutLogger.ts b/src/telemetry/ClearcutLogger.ts index 82f766cd4..a53345746 100644 --- a/src/telemetry/ClearcutLogger.ts +++ b/src/telemetry/ClearcutLogger.ts @@ -21,7 +21,7 @@ import { import {WatchdogClient} from './WatchdogClient.js'; const MS_PER_DAY = 24 * 60 * 60 * 1000; -export const PARAM_BLOCKLIST = new Set(['uid', 'reqid', 'msgid']); +const PARAM_BLOCKLIST = new Set(['uid']); const SUPPORTED_ZOD_TYPES = [ 'ZodString', @@ -36,7 +36,7 @@ function isZodType(type: string): type is ZodType { return SUPPORTED_ZOD_TYPES.includes(type as ZodType); } -export function getZodType(zodType: zod.ZodTypeAny): ZodType { +function getZodType(zodType: zod.ZodTypeAny): ZodType { const def = zodType._def; const typeName = def.typeName; @@ -59,7 +59,7 @@ export function getZodType(zodType: zod.ZodTypeAny): ZodType { type LoggedToolCallArgValue = string | number | boolean; -export function transformArgName(zodType: ZodType, name: string): string { +function transformName(zodType: ZodType, name: string): string { if (zodType === 'ZodString') { return `${name}_length`; } else if (zodType === 'ZodArray') { @@ -69,22 +69,6 @@ export function transformArgName(zodType: ZodType, name: string): string { } } -export function transformArgType(zodType: ZodType): string { - if (zodType === 'ZodString' || zodType === 'ZodArray') { - return 'number'; - } - switch (zodType) { - case 'ZodNumber': - return 'number'; - case 'ZodBoolean': - return 'boolean'; - case 'ZodEnum': - return 'enum'; - default: - throw new Error(`Unsupported zod type for tool parameter: ${zodType}`); - } -} - function transformValue( zodType: ZodType, value: unknown, @@ -133,7 +117,7 @@ export function sanitizeParams( `parameter ${name} has type ${zodType} but value ${value} is not of equivalent type`, ); } - const transformedName = transformArgName(zodType, name); + const transformedName = transformName(zodType, name); const transformedValue = transformValue(zodType, value); transformed[transformedName] = transformedValue; } diff --git a/src/telemetry/toolMetricsUtils.ts b/src/telemetry/toolMetricsUtils.ts deleted file mode 100644 index 53783645d..000000000 --- a/src/telemetry/toolMetricsUtils.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * @license - * Copyright 2026 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import type {ToolDefinition} from '../tools/ToolDefinition.js'; - -import { - transformArgName, - transformArgType, - getZodType, - PARAM_BLOCKLIST, -} from './ClearcutLogger.js'; - -/** - * Validates that all values in an enum are of the homogeneous primitive type. - * Returns the primitive type string. Throws an error if heterogeneous. - */ -export function validateEnumHomogeneity(values: unknown[]): string { - const firstType = typeof values[0]; - for (const val of values) { - if (typeof val !== firstType) { - throw new Error('Heterogeneous enum types found'); - } - } - return firstType; -} - -export interface ArgMetric { - name: string; - argType: string; - isDeprecated?: boolean; -} - -export interface ToolMetric { - name: string; - args: ArgMetric[]; - isDeprecated?: boolean; -} - -export function applyToExistingMetrics( - existing: ToolMetric[], - update: ToolMetric[], -): ToolMetric[] { - const updated = applyToExisting(existing, update); - const existingByName = new Map(existing.map(tool => [tool.name, tool])); - const updatedByName = new Map(update.map(tool => [tool.name, tool])); - - return updated.map(tool => { - const existingTool = existingByName.get(tool.name); - const updatedTool = updatedByName.get(tool.name); - // If the tool still exists in the update, we will update the args. - if (existingTool && updatedTool) { - const updatedArgs = applyToExisting( - existingTool.args, - updatedTool.args, - ); - return {...tool, args: updatedArgs}; - } - return tool; - }); -} - -function applyToExisting( - existing: T[], - update: T[], -): T[] { - const existingNames = new Set(existing.map(item => item.name)); - const updatedNames = new Set(update.map(item => item.name)); - - const result: T[] = []; - // Keep the original ordering. - for (const entry of existing) { - const toAdd = {...entry}; - if (!updatedNames.has(entry.name)) { - toAdd.isDeprecated = true; - } - result.push(toAdd); - } - // New entries must be added to the very back of the list. - for (const entry of update) { - if (!existingNames.has(entry.name)) { - result.push({...entry}); - } - } - return result; -} - -/** - * Generates tool metrics from tool definitions. - */ -export function generateToolMetrics(tools: ToolDefinition[]): ToolMetric[] { - return tools.map(tool => { - const args: ArgMetric[] = []; - - for (const [name, schema] of Object.entries(tool.schema)) { - if (PARAM_BLOCKLIST.has(name)) { - continue; - } - const zodType = getZodType(schema); - const transformedName = transformArgName(zodType, name); - let argType = transformArgType(zodType); - - if (argType === 'enum') { - let values; - if (schema._def.values?.length > 0) { - values = schema._def.values; - } else { - values = schema._def.innerType._def.values; - } - argType = validateEnumHomogeneity(values); - } - - args.push({ - name: transformedName, - argType, - }); - } - - return { - name: tool.name, - args, - }; - }); -} diff --git a/src/telemetry/tool_call_metrics.json b/src/telemetry/tool_call_metrics.json deleted file mode 100644 index 496045ff6..000000000 --- a/src/telemetry/tool_call_metrics.json +++ /dev/null @@ -1,543 +0,0 @@ -[ - { - "name": "click", - "args": [ - { - "name": "dblClick", - "argType": "boolean" - }, - { - "name": "includeSnapshot", - "argType": "boolean" - } - ] - }, - { - "name": "click_at", - "args": [ - { - "name": "x", - "argType": "number" - }, - { - "name": "y", - "argType": "number" - }, - { - "name": "dblClick", - "argType": "boolean" - }, - { - "name": "includeSnapshot", - "argType": "boolean" - } - ] - }, - { - "name": "close_page", - "args": [ - { - "name": "pageId", - "argType": "number" - } - ] - }, - { - "name": "drag", - "args": [ - { - "name": "from_uid_length", - "argType": "number" - }, - { - "name": "to_uid_length", - "argType": "number" - }, - { - "name": "includeSnapshot", - "argType": "boolean" - } - ] - }, - { - "name": "emulate", - "args": [ - { - "name": "networkConditions", - "argType": "string" - }, - { - "name": "cpuThrottlingRate", - "argType": "number" - }, - { - "name": "geolocation_length", - "argType": "number" - }, - { - "name": "userAgent_length", - "argType": "number" - }, - { - "name": "colorScheme", - "argType": "string" - }, - { - "name": "viewport_length", - "argType": "number" - } - ] - }, - { - "name": "evaluate", - "args": [ - { - "name": "script_length", - "argType": "number" - } - ] - }, - { - "name": "evaluate_script", - "args": [ - { - "name": "function_length", - "argType": "number" - }, - { - "name": "args_count", - "argType": "number" - } - ] - }, - { - "name": "execute_in_page_tool", - "args": [ - { - "name": "toolName_length", - "argType": "number" - }, - { - "name": "params_length", - "argType": "number" - } - ] - }, - { - "name": "fill", - "args": [ - { - "name": "value_length", - "argType": "number" - }, - { - "name": "includeSnapshot", - "argType": "boolean" - } - ] - }, - { - "name": "fill_form", - "args": [ - { - "name": "elements_count", - "argType": "number" - }, - { - "name": "includeSnapshot", - "argType": "boolean" - } - ] - }, - { - "name": "get_console_message", - "args": [] - }, - { - "name": "get_network_request", - "args": [ - { - "name": "requestFilePath_length", - "argType": "number" - }, - { - "name": "responseFilePath_length", - "argType": "number" - } - ] - }, - { - "name": "get_tab_id", - "args": [ - { - "name": "pageId", - "argType": "number" - } - ] - }, - { - "name": "handle_dialog", - "args": [ - { - "name": "action", - "argType": "string" - }, - { - "name": "promptText_length", - "argType": "number" - } - ] - }, - { - "name": "hover", - "args": [ - { - "name": "includeSnapshot", - "argType": "boolean" - } - ] - }, - { - "name": "install_extension", - "args": [ - { - "name": "path_length", - "argType": "number" - } - ] - }, - { - "name": "lighthouse_audit", - "args": [ - { - "name": "mode", - "argType": "string" - }, - { - "name": "device", - "argType": "string" - }, - { - "name": "outputDirPath_length", - "argType": "number" - } - ] - }, - { - "name": "list_console_messages", - "args": [ - { - "name": "pageSize", - "argType": "number" - }, - { - "name": "pageIdx", - "argType": "number" - }, - { - "name": "types_count", - "argType": "number" - }, - { - "name": "includePreservedMessages", - "argType": "boolean" - } - ] - }, - { - "name": "list_extensions", - "args": [] - }, - { - "name": "list_in_page_tools", - "args": [] - }, - { - "name": "list_network_requests", - "args": [ - { - "name": "pageSize", - "argType": "number" - }, - { - "name": "pageIdx", - "argType": "number" - }, - { - "name": "resourceTypes_count", - "argType": "number" - }, - { - "name": "includePreservedRequests", - "argType": "boolean" - } - ] - }, - { - "name": "list_pages", - "args": [] - }, - { - "name": "navigate", - "args": [ - { - "name": "url_length", - "argType": "number" - } - ] - }, - { - "name": "navigate_page", - "args": [ - { - "name": "type", - "argType": "string" - }, - { - "name": "url_length", - "argType": "number" - }, - { - "name": "ignoreCache", - "argType": "boolean" - }, - { - "name": "handleBeforeUnload", - "argType": "string" - }, - { - "name": "initScript_length", - "argType": "number" - }, - { - "name": "timeout", - "argType": "number" - } - ] - }, - { - "name": "new_page", - "args": [ - { - "name": "url_length", - "argType": "number" - }, - { - "name": "background", - "argType": "boolean" - }, - { - "name": "isolatedContext_length", - "argType": "number" - }, - { - "name": "timeout", - "argType": "number" - } - ] - }, - { - "name": "performance_analyze_insight", - "args": [ - { - "name": "insightSetId_length", - "argType": "number" - }, - { - "name": "insightName_length", - "argType": "number" - } - ] - }, - { - "name": "performance_start_trace", - "args": [ - { - "name": "reload", - "argType": "boolean" - }, - { - "name": "autoStop", - "argType": "boolean" - }, - { - "name": "filePath_length", - "argType": "number" - } - ] - }, - { - "name": "performance_stop_trace", - "args": [ - { - "name": "filePath_length", - "argType": "number" - } - ] - }, - { - "name": "press_key", - "args": [ - { - "name": "key_length", - "argType": "number" - }, - { - "name": "includeSnapshot", - "argType": "boolean" - } - ] - }, - { - "name": "reload_extension", - "args": [ - { - "name": "id_length", - "argType": "number" - } - ] - }, - { - "name": "resize_page", - "args": [ - { - "name": "width", - "argType": "number" - }, - { - "name": "height", - "argType": "number" - } - ] - }, - { - "name": "screencast_start", - "args": [ - { - "name": "path_length", - "argType": "number" - } - ] - }, - { - "name": "screencast_stop", - "args": [] - }, - { - "name": "screenshot", - "args": [] - }, - { - "name": "select_page", - "args": [ - { - "name": "pageId", - "argType": "number" - }, - { - "name": "bringToFront", - "argType": "boolean" - } - ] - }, - { - "name": "take_memory_snapshot", - "args": [ - { - "name": "filePath_length", - "argType": "number" - } - ] - }, - { - "name": "take_screenshot", - "args": [ - { - "name": "format", - "argType": "string" - }, - { - "name": "quality", - "argType": "number" - }, - { - "name": "fullPage", - "argType": "boolean" - }, - { - "name": "filePath_length", - "argType": "number" - } - ] - }, - { - "name": "take_snapshot", - "args": [ - { - "name": "verbose", - "argType": "boolean" - }, - { - "name": "filePath_length", - "argType": "number" - } - ] - }, - { - "name": "trigger_extension_action", - "args": [ - { - "name": "id_length", - "argType": "number" - } - ] - }, - { - "name": "type_text", - "args": [ - { - "name": "text_length", - "argType": "number" - }, - { - "name": "submitKey_length", - "argType": "number" - } - ] - }, - { - "name": "uninstall_extension", - "args": [ - { - "name": "id_length", - "argType": "number" - } - ] - }, - { - "name": "upload_file", - "args": [ - { - "name": "filePath_length", - "argType": "number" - }, - { - "name": "includeSnapshot", - "argType": "boolean" - } - ] - }, - { - "name": "wait_for", - "args": [ - { - "name": "text_count", - "argType": "number" - }, - { - "name": "timeout", - "argType": "number" - } - ] - } -] diff --git a/src/third_party/index.ts b/src/third_party/index.ts index 09b0d02ce..96f5295c0 100644 --- a/src/third_party/index.ts +++ b/src/third_party/index.ts @@ -41,7 +41,7 @@ export {default as puppeteer} from 'puppeteer-core'; export type * from 'puppeteer-core'; export {PipeTransport} from 'puppeteer-core/internal/node/PipeTransport.js'; export type {CdpPage} from 'puppeteer-core/internal/cdp/Page.js'; -export type {JSONSchema7, JSONSchema7Definition} from 'json-schema'; +export type {JSONSchema7} from 'json-schema'; export { resolveDefaultUserDataDir, detectBrowserPlatform, diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index edea24959..b6d44f6fa 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -210,7 +210,7 @@ export type ContextPage = Readonly<{ clearDialog(): void; waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; handleDialog?: boolean}, + options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise; getInPageTools(): ToolGroup | undefined; }>; diff --git a/src/tools/inPage.ts b/src/tools/inPage.ts index 0cb1b6ba7..5b0031f62 100644 --- a/src/tools/inPage.ts +++ b/src/tools/inPage.ts @@ -4,12 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - zod, - ajv, - type JSONSchema7, - type ElementHandle, -} from '../third_party/index.js'; +import {zod, ajv, type JSONSchema7} from '../third_party/index.js'; import {ToolCategory} from './categories.js'; import {definePageTool} from './ToolDefinition.js'; @@ -92,22 +87,6 @@ export const executeInPageTool = definePageTool({ } } - // Creates array of ElementHandles from the UIDs in the params. - // We do not replace the uids with the ElementsHandles yet, because - // the `evaluate` function only turns them into DOM elements if they - // are passed as non-nested arguments. - const handles: ElementHandle[] = []; - for (const value of Object.values(params)) { - if ( - value instanceof Object && - 'uid' in value && - typeof value.uid === 'string' && - Object.keys(value).length === 1 - ) { - handles.push(await request.page.getElementByUid(value.uid)); - } - } - const toolGroup = request.page.getInPageTools(); const tool = toolGroup?.tools.find(t => t.name === toolName); if (!tool) { @@ -123,19 +102,7 @@ export const executeInPageTool = definePageTool({ } const result = await request.page.pptrPage.evaluate( - async (name, args, ...elements) => { - // Replace the UIDs with DOM elements. - for (const [key, value] of Object.entries(args)) { - if ( - value instanceof Object && - 'uid' in value && - typeof value.uid === 'string' && - Object.keys(value).length === 1 - ) { - args[key] = elements.shift(); - } - } - + async (name, args) => { if (!window.__dtmcp?.executeTool) { throw new Error('No tools found on the page'); } @@ -147,7 +114,6 @@ export const executeInPageTool = definePageTool({ }, toolName, params, - ...handles, ); response.appendResponseLine(JSON.stringify(result, null, 2)); }, diff --git a/src/tools/script.ts b/src/tools/script.ts index 6c6e87e5d..fff3383e8 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -81,7 +81,7 @@ Example with arguments: \`(el) => { async () => { await performEvaluation(worker, fnString, [], response); }, - {handleDialog: true}, + {dialog: 'accept'}, ); return; } @@ -106,7 +106,7 @@ Example with arguments: \`(el) => { async () => { await performEvaluation(evaluatable, fnString, args, response); }, - {handleDialog: true}, + {dialog: 'accept'}, ); } finally { void Promise.allSettled(args.map(arg => arg.dispose())); diff --git a/src/utils/check-for-updates.ts b/src/utils/check-for-updates.ts deleted file mode 100644 index 36c5b13b8..000000000 --- a/src/utils/check-for-updates.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @license - * Copyright 2026 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import child_process from 'node:child_process'; -import fs from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import process from 'node:process'; - -import {VERSION} from '../version.js'; - -/** - * Notifies the user if an update is available. - * @param message The message to display in the update notification. - */ -let isChecking = false; - -/** @internal Reset flag for tests only. */ -export function resetUpdateCheckFlagForTesting() { - isChecking = false; -} - -export async function checkForUpdates(message: string) { - if (isChecking || process.env['CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS']) { - return; - } - isChecking = true; - - const cachePath = path.join( - os.homedir(), - '.cache', - 'chrome-devtools-mcp', - 'latest.json', - ); - - let cachedVersion: string | undefined; - let stats: {mtimeMs: number} | undefined; - try { - stats = await fs.stat(cachePath); - const data = await fs.readFile(cachePath, 'utf8'); - cachedVersion = JSON.parse(data).version; - } catch { - // Ignore errors reading cache. - } - - if (cachedVersion && cachedVersion !== VERSION) { - console.warn( - `\nUpdate available: ${VERSION} -> ${cachedVersion}\n${message}\n`, - ); - } - - const now = Date.now(); - if (stats && now - stats.mtimeMs < 24 * 60 * 60 * 1000) { - return; - } - - // Update mtime immediately to prevent multiple subprocesses. - try { - const parentDir = path.dirname(cachePath); - await fs.mkdir(parentDir, {recursive: true}); - const nowTime = new Date(); - if (stats) { - await fs.utimes(cachePath, nowTime, nowTime); - } else { - await fs.writeFile(cachePath, JSON.stringify({version: VERSION})); - } - } catch { - // Ignore errors. - } - - // In a separate process, check the latest available version number - // and update the local snapshot accordingly. - const scriptPath = path.join( - import.meta.dirname, - '..', - 'bin', - 'check-latest-version.js', - ); - - try { - const child = child_process.spawn( - process.execPath, - [scriptPath, cachePath], - { - detached: true, - stdio: 'ignore', - }, - ); - child.unref(); - } catch { - // Fail silently in case of any errors. - } -} diff --git a/test_dialog.js b/test_dialog.js new file mode 100644 index 000000000..69deb5228 --- /dev/null +++ b/test_dialog.js @@ -0,0 +1,21 @@ +const puppeteer = require('puppeteer-core'); +const cp = require('child_process'); + +(async () => { + const browser = await puppeteer.launch({ + executablePath: '/usr/bin/google-chrome', + headless: 'new', + }); + const page = await browser.newPage(); + + page.on('dialog', async dialog => { + console.log('Dialog opened'); + await dialog.accept(); + }); + + console.log('Evaluating alert...'); + await page.evaluate(() => alert('hello')); + console.log('Evaluate finished'); + + await browser.close(); +})(); diff --git a/test_dialog.mjs b/test_dialog.mjs new file mode 100644 index 000000000..e4094fe57 --- /dev/null +++ b/test_dialog.mjs @@ -0,0 +1,20 @@ +import puppeteer from 'puppeteer-core'; + +(async () => { + const browser = await puppeteer.launch({ + executablePath: '/usr/bin/google-chrome', + headless: 'new', + }); + const page = await browser.newPage(); + + page.on('dialog', async dialog => { + console.log('Dialog opened'); + await dialog.accept(); + }); + + console.log('Evaluating alert...'); + await page.evaluate(() => alert('hello')); + console.log('Evaluate finished'); + + await browser.close(); +})(); diff --git a/test_dialog2.mjs b/test_dialog2.mjs new file mode 100644 index 000000000..4b9de4233 --- /dev/null +++ b/test_dialog2.mjs @@ -0,0 +1,26 @@ +import puppeteer from 'puppeteer-core'; + +(async () => { + const browser = await puppeteer.launch({ + executablePath: '/usr/bin/google-chrome', + headless: 'new', + }); + const page = await browser.newPage(); + + let savedDialog; + page.on('dialog', dialog => { + console.log('Dialog opened - first handler'); + savedDialog = dialog; + }); + + page.on('dialog', async dialog => { + console.log('Dialog opened - second handler'); + await dialog.accept(); + }); + + console.log('Evaluating alert...'); + await page.evaluate(() => alert('hello')); + console.log('Evaluate finished. Saved dialog:', savedDialog?.message()); + + await browser.close(); +})(); diff --git a/tests/McpContext.test.ts b/tests/McpContext.test.ts index 31a6c88b3..d84b506cb 100644 --- a/tests/McpContext.test.ts +++ b/tests/McpContext.test.ts @@ -89,6 +89,10 @@ describe('McpContext', () => { await withMcpContext( async (_response, context) => { const page = await context.newPage(); + // TODO: we do not know when the CLI flag to auto open DevTools will run + // so we need this until + // https://github.com/puppeteer/puppeteer/issues/14368 is there. + await new Promise(resolve => setTimeout(resolve, 5000)); await context.createPagesSnapshot(); assert.ok(context.getDevToolsPage(page.pptrPage)); }, diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index 915377197..66be61e84 100644 --- a/tests/McpResponse.test.ts +++ b/tests/McpResponse.test.ts @@ -15,8 +15,6 @@ import sinon from 'sinon'; import type {ParsedArguments} from '../src/bin/chrome-devtools-mcp-cli-options.js'; import type {McpContext} from '../src/McpContext.js'; import type {McpResponse} from '../src/McpResponse.js'; -import {replaceHtmlElementsWithUids} from '../src/McpResponse.js'; -import type {JSONSchema7Definition} from '../src/third_party/index.js'; import { closePage, listPages, @@ -1207,251 +1205,3 @@ describe('inPage tools', () => { }, 'new_page'); }); }); - -describe('replaceHtmlElementsWithUids', () => { - it('does nothing for boolean schemas', () => { - const schemaTrue: JSONSchema7Definition = true; - const schemaFalse: JSONSchema7Definition = false; - - replaceHtmlElementsWithUids(schemaTrue); - replaceHtmlElementsWithUids(schemaFalse); - - assert.strictEqual(schemaTrue, true); - assert.strictEqual(schemaFalse, false); - }); - - it('replaces HTMLElement type with uid string', () => { - const schema: JSONSchema7Definition = { - type: 'object', - properties: { - foo: {type: 'string'}, - bar: {type: 'number'}, - }, - required: ['foo'], - }; - Object.assign(schema, {'x-mcp-type': 'HTMLElement'}); - - replaceHtmlElementsWithUids(schema); - - if (typeof schema === 'object') { - assert.deepStrictEqual(schema.properties, { - uid: {type: 'string'}, - }); - assert.deepStrictEqual(schema.required, ['uid']); - } else { - assert.fail('Schema should be an object'); - } - }); - - it('does not replace if x-mcp-type is not HTMLElement', () => { - const schema: JSONSchema7Definition = { - type: 'object', - properties: { - foo: {type: 'string'}, - }, - }; - Object.assign(schema, {'x-mcp-type': 'OtherType'}); - - replaceHtmlElementsWithUids(schema); - - if (typeof schema === 'object') { - assert.deepStrictEqual(schema.properties, { - foo: {type: 'string'}, - }); - assert.strictEqual(schema.required, undefined); - } else { - assert.fail('Schema should be an object'); - } - }); - - it('recurses into nested properties', () => { - const schema: JSONSchema7Definition = { - type: 'object', - properties: { - element: { - type: 'object', - properties: { - foo: {type: 'string'}, - }, - }, - other: { - type: 'string', - }, - }, - }; - if (typeof schema === 'object' && schema.properties) { - Object.assign(schema.properties.element, {'x-mcp-type': 'HTMLElement'}); - } - - replaceHtmlElementsWithUids(schema); - - if ( - typeof schema === 'object' && - schema.properties && - typeof schema.properties.element === 'object' - ) { - const elementSchema = schema.properties.element; - assert.deepStrictEqual(elementSchema.properties, { - uid: {type: 'string'}, - }); - assert.deepStrictEqual(elementSchema.required, ['uid']); - } else { - assert.fail('Unexpected schema structure'); - } - }); - - it('recurses into array items (single schema object)', () => { - const schema: JSONSchema7Definition = { - type: 'array', - items: { - type: 'object', - }, - }; - if (typeof schema === 'object' && typeof schema.items === 'object') { - Object.assign(schema.items, {'x-mcp-type': 'HTMLElement'}); - } - - replaceHtmlElementsWithUids(schema); - - if (typeof schema === 'object' && typeof schema.items === 'object') { - const itemsSchema = schema.items; - if (!Array.isArray(itemsSchema)) { - assert.deepStrictEqual(itemsSchema.properties, { - uid: {type: 'string'}, - }); - assert.deepStrictEqual(itemsSchema.required, ['uid']); - } else { - assert.fail('items should not be an array in this test case'); - } - } else { - assert.fail('Unexpected schema structure'); - } - }); - - it('recurses into array items (array of schemas)', () => { - const schema: JSONSchema7Definition = { - type: 'array', - items: [ - { - type: 'object', - }, - { - type: 'string', - }, - ], - }; - if (typeof schema === 'object' && Array.isArray(schema.items)) { - Object.assign(schema.items[0], {'x-mcp-type': 'HTMLElement'}); - } - - replaceHtmlElementsWithUids(schema); - - if (typeof schema === 'object' && Array.isArray(schema.items)) { - const firstItem = schema.items[0]; - if (typeof firstItem === 'object') { - assert.deepStrictEqual(firstItem.properties, { - uid: {type: 'string'}, - }); - assert.deepStrictEqual(firstItem.required, ['uid']); - } else { - assert.fail('First item should be an object'); - } - - const secondItem = schema.items[1]; - if (typeof secondItem === 'object') { - assert.strictEqual(secondItem.properties, undefined); - } else { - assert.fail('Second item should be an object'); - } - } else { - assert.fail('Unexpected schema structure'); - } - }); - - it('recurses into anyOf', () => { - const schema: JSONSchema7Definition = { - anyOf: [ - { - type: 'object', - }, - { - type: 'string', - }, - ], - }; - if (typeof schema === 'object' && Array.isArray(schema.anyOf)) { - Object.assign(schema.anyOf[0], {'x-mcp-type': 'HTMLElement'}); - } - - replaceHtmlElementsWithUids(schema); - - if (typeof schema === 'object' && Array.isArray(schema.anyOf)) { - const firstItem = schema.anyOf[0]; - if (typeof firstItem === 'object') { - assert.deepStrictEqual(firstItem.properties, { - uid: {type: 'string'}, - }); - } else { - assert.fail('First item should be an object'); - } - } else { - assert.fail('Unexpected schema structure'); - } - }); - - it('recurses into allOf', () => { - const schema: JSONSchema7Definition = { - allOf: [ - { - type: 'object', - }, - ], - }; - if (typeof schema === 'object' && Array.isArray(schema.allOf)) { - Object.assign(schema.allOf[0], {'x-mcp-type': 'HTMLElement'}); - } - - replaceHtmlElementsWithUids(schema); - - if (typeof schema === 'object' && Array.isArray(schema.allOf)) { - const firstItem = schema.allOf[0]; - if (typeof firstItem === 'object') { - assert.deepStrictEqual(firstItem.properties, { - uid: {type: 'string'}, - }); - } else { - assert.fail('First item should be an object'); - } - } else { - assert.fail('Unexpected schema structure'); - } - }); - - it('recurses into oneOf', () => { - const schema: JSONSchema7Definition = { - oneOf: [ - { - type: 'object', - }, - ], - }; - if (typeof schema === 'object' && Array.isArray(schema.oneOf)) { - Object.assign(schema.oneOf[0], {'x-mcp-type': 'HTMLElement'}); - } - - replaceHtmlElementsWithUids(schema); - - if (typeof schema === 'object' && Array.isArray(schema.oneOf)) { - const firstItem = schema.oneOf[0]; - if (typeof firstItem === 'object') { - assert.deepStrictEqual(firstItem.properties, { - uid: {type: 'string'}, - }); - } else { - assert.fail('First item should be an object'); - } - } else { - assert.fail('Unexpected schema structure'); - } - }); -}); diff --git a/tests/check-for-updates.test.ts b/tests/check-for-updates.test.ts deleted file mode 100644 index 82413c493..000000000 --- a/tests/check-for-updates.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * @license - * Copyright 2026 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'node:assert'; -import child_process from 'node:child_process'; -import type {Stats} from 'node:fs'; -import fs from 'node:fs/promises'; -import os from 'node:os'; -import {afterEach, beforeEach, describe, it} from 'node:test'; - -import sinon from 'sinon'; - -import { - checkForUpdates, - resetUpdateCheckFlagForTesting, -} from '../src/utils/check-for-updates.js'; -import {VERSION} from '../src/version.js'; - -describe('checkForUpdates', () => { - beforeEach(() => { - sinon.stub(fs, 'mkdir').resolves(); - sinon.stub(fs, 'utimes').resolves(); - sinon.stub(fs, 'writeFile').resolves(); - }); - - afterEach(() => { - sinon.restore(); - resetUpdateCheckFlagForTesting(); - }); - - it('does nothing if CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS is set', async () => { - process.env['CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS'] = 'true'; - - const warnStub = sinon.stub(console, 'warn'); - const spawnStub = sinon.stub(child_process, 'spawn'); - const readFileStub = sinon.stub(fs, 'readFile'); - const statStub = sinon.stub(fs, 'stat'); - - await checkForUpdates('Run `npm update` to update.'); - - assert.ok(warnStub.notCalled); - assert.ok(spawnStub.notCalled); - assert.ok(readFileStub.notCalled); - assert.ok(statStub.notCalled); - - delete process.env['CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS']; - }); - - it('notifies if cache exists and version is different', async () => { - sinon.stub(os, 'homedir').returns('/home/user'); - sinon.stub(fs, 'stat').resolves({mtimeMs: Date.now()} as unknown as Stats); - sinon.stub(fs, 'readFile').callsFake(async filePath => { - if (filePath.toString().includes('latest.json')) { - return JSON.stringify({ - version: '99.9.9', - }); - } - throw new Error(`File not found: ${filePath}`); - }); - const warnStub = sinon.stub(console, 'warn'); - const spawnStub = sinon.stub(child_process, 'spawn'); - - await checkForUpdates('Run `npm update` to update.'); - - assert.ok( - warnStub.calledWith( - sinon.match('Update available: ' + VERSION + ' -> 99.9.9'), - ), - ); - assert.ok(spawnStub.notCalled); - }); - - it('does not spawn fetch process if cache is fresh', async () => { - sinon.stub(os, 'homedir').returns('/home/user'); - sinon.stub(fs, 'stat').resolves({mtimeMs: Date.now()} as unknown as Stats); - sinon.stub(fs, 'readFile').callsFake(async filePath => { - if (filePath.toString().includes('latest.json')) { - return JSON.stringify({ - version: VERSION, - }); - } - throw new Error(`File not found: ${filePath}`); - }); - const spawnStub = sinon.stub(child_process, 'spawn'); - - await checkForUpdates('Run `npm update` to update.'); - - assert.ok(spawnStub.notCalled); - }); - - it('spawns detached process if cache is stale', async () => { - sinon.stub(os, 'homedir').returns('/home/user'); - sinon.stub(fs, 'stat').resolves({ - mtimeMs: Date.now() - 25 * 60 * 60 * 1000, // 25 hours ago - } as unknown as Stats); - sinon.stub(fs, 'readFile').callsFake(async filePath => { - if (filePath.toString().includes('latest.json')) { - return JSON.stringify({ - version: VERSION, - }); - } - throw new Error(`File not found: ${filePath}`); - }); - - const unrefSpy = sinon.spy(); - const spawnStub = sinon.stub(child_process, 'spawn').returns({ - unref: unrefSpy, - } as unknown as child_process.ChildProcess); - - await checkForUpdates('Run `npm update` to update.'); - - assert.ok(spawnStub.calledOnce); - assert.strictEqual(spawnStub.firstCall.args[0], process.execPath); - assert.ok( - spawnStub.firstCall.args[1][0]?.includes('check-latest-version.js'), - ); - assert.ok(spawnStub.firstCall.args[1][1]?.includes('latest.json')); - assert.strictEqual(spawnStub.firstCall.args[2]?.detached, true); - assert.ok(unrefSpy.calledOnce); - }); - - it('spawns detached process if cache is missing', async () => { - sinon.stub(os, 'homedir').returns('/home/user'); - sinon.stub(fs, 'stat').rejects(new Error('File not found')); - sinon.stub(fs, 'readFile').callsFake(async filePath => { - throw new Error(`File not found: ${filePath}`); - }); - - const unrefSpy = sinon.spy(); - const spawnStub = sinon.stub(child_process, 'spawn').returns({ - unref: unrefSpy, - } as unknown as child_process.ChildProcess); - - await checkForUpdates('Run `npm update` to update.'); - - assert.ok(spawnStub.calledOnce); - assert.strictEqual(spawnStub.firstCall.args[0], process.execPath); - assert.ok( - spawnStub.firstCall.args[1][0]?.includes('check-latest-version.js'), - ); - assert.ok(spawnStub.firstCall.args[1][1]?.includes('latest.json')); - assert.strictEqual(spawnStub.firstCall.args[2]?.detached, true); - assert.ok(unrefSpy.calledOnce); - }); -}); diff --git a/tests/telemetry/toolMetricsUtils.test.ts b/tests/telemetry/toolMetricsUtils.test.ts deleted file mode 100644 index 0c369aaea..000000000 --- a/tests/telemetry/toolMetricsUtils.test.ts +++ /dev/null @@ -1,263 +0,0 @@ -/** - * @license - * Copyright 2026 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'node:assert'; -import {describe, it} from 'node:test'; - -import { - applyToExistingMetrics, - generateToolMetrics, - validateEnumHomogeneity, -} from '../../src/telemetry/toolMetricsUtils.js'; -import {zod} from '../../src/third_party/index.js'; -import {ToolCategory} from '../../src/tools/categories.js'; -import type {ToolDefinition} from '../../src/tools/ToolDefinition.js'; - -describe('toolMetricsUtils', () => { - describe('validateEnumHomogeneity', () => { - it('should return the primitive type of a homogeneous enum', () => { - const result = validateEnumHomogeneity(['a', 'b', 'c']); - assert.strictEqual(result, 'string'); - - const result2 = validateEnumHomogeneity([1, 2, 3]); - assert.strictEqual(result2, 'number'); - }); - - it('should throw for heterogeneous enum types', () => { - assert.throws(() => { - validateEnumHomogeneity(['a', 1, 'c']); - }, /Heterogeneous enum types found/); - }); - }); - - describe('generateToolMetrics', () => { - it('should map tools correctly and apply transformations', () => { - const mockTool: ToolDefinition = { - name: 'test_tool', - description: 'test description', - annotations: { - category: ToolCategory.INPUT, - readOnlyHint: true, - }, - schema: { - argStr: zod.string(), - uid: zod.string(), // Should be blocked - }, - handler: async () => { - // no-op - }, - }; - - const metrics = generateToolMetrics([mockTool]); - assert.strictEqual(metrics.length, 1); - assert.strictEqual(metrics[0].name, 'test_tool'); - assert.strictEqual(metrics[0].args.length, 1); // uid is blocked - assert.strictEqual(metrics[0].args[0].name, 'argStr_length'); - assert.strictEqual(metrics[0].args[0].argType, 'number'); - }); - - it('should handle enums correctly', () => { - const mockTool: ToolDefinition = { - name: 'enum_tool', - description: 'test description', - annotations: { - category: ToolCategory.INPUT, - readOnlyHint: true, - }, - schema: { - argEnum: zod.enum(['foo', 'bar']), - }, - handler: async () => { - // no-op - }, - }; - - const metrics = generateToolMetrics([mockTool]); - assert.strictEqual(metrics.length, 1); - assert.strictEqual(metrics[0].args[0].name, 'argEnum'); - assert.strictEqual(metrics[0].args[0].argType, 'string'); - }); - }); - - describe('applyToExistingMetrics', () => { - it('should return the same metrics if existing and update are the same', () => { - const existing = [{name: 'foo', args: []}]; - const update = [{name: 'foo', args: []}]; - const result = applyToExistingMetrics(existing, update); - const expected = [{name: 'foo', args: []}]; - assert.deepStrictEqual(result, expected); - }); - - it('should append new entries to the end of the array', () => { - const existing = [{name: 'foo', args: []}]; - const update = [ - {name: 'foo', args: []}, - {name: 'bar', args: []}, - ]; - const result = applyToExistingMetrics(existing, update); - const expected = [ - {name: 'foo', args: []}, - {name: 'bar', args: []}, - ]; - assert.deepStrictEqual(result, expected); - }); - - it('should mark missing entries as deprecated and preserve their order', () => { - const existing = [ - {name: 'foo', args: []}, - {name: 'bar', args: []}, - ]; - const update = [{name: 'foo', args: []}]; - const result = applyToExistingMetrics(existing, update); - const expected = [ - {name: 'foo', args: []}, - {name: 'bar', args: [], isDeprecated: true}, - ]; - assert.deepStrictEqual(result, expected); - }); - - it('should handle adding new entries and deprecating old ones simultaneously', () => { - const existing = [ - {name: 'foo', args: []}, - {name: 'bar', args: []}, - ]; - const update = [ - {name: 'bar', args: []}, - {name: 'baz', args: []}, - ]; - const result = applyToExistingMetrics(existing, update); - const expected = [ - {name: 'foo', args: [], isDeprecated: true}, - {name: 'bar', args: []}, - {name: 'baz', args: []}, - ]; - assert.deepStrictEqual(result, expected); - }); - - it('should append new arguments to the back', () => { - const existing = [ - {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]}, - ]; - const update = [ - { - name: 'foo', - args: [ - {name: 'arg_a', argType: 'string'}, - {name: 'arg_b', argType: 'string'}, - ], - }, - ]; - const result = applyToExistingMetrics(existing, update); - const expected = [ - { - name: 'foo', - args: [ - {name: 'arg_a', argType: 'string'}, - {name: 'arg_b', argType: 'string'}, - ], - }, - ]; - assert.deepStrictEqual(result, expected); - }); - - it('should mark removed arguments as deprecated', () => { - const existing = [ - { - name: 'foo', - args: [ - {name: 'arg_a', argType: 'string'}, - {name: 'arg_b', argType: 'string'}, - ], - }, - ]; - const update = [ - {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]}, - ]; - const result = applyToExistingMetrics(existing, update); - const expected = [ - { - name: 'foo', - args: [ - {name: 'arg_a', argType: 'string'}, - {name: 'arg_b', argType: 'string', isDeprecated: true}, - ], - }, - ]; - assert.deepStrictEqual(result, expected); - }); - - it('should not change args if they are the same', () => { - const existing = [ - {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]}, - ]; - const update = [ - {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]}, - ]; - const result = applyToExistingMetrics(existing, update); - const expected = [ - {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]}, - ]; - assert.deepStrictEqual(result, expected); - }); - - it('should handle adding and removing arguments simultaneously', () => { - const existing = [ - { - name: 'foo', - args: [ - {name: 'arg_a', argType: 'string'}, - {name: 'arg_b', argType: 'string'}, - ], - }, - ]; - const update = [ - { - name: 'foo', - args: [ - {name: 'arg_b', argType: 'string'}, - {name: 'arg_c', argType: 'string'}, - ], - }, - ]; - const result = applyToExistingMetrics(existing, update); - const expected = [ - { - name: 'foo', - args: [ - {name: 'arg_a', argType: 'string', isDeprecated: true}, - {name: 'arg_b', argType: 'string'}, - {name: 'arg_c', argType: 'string'}, - ], - }, - ]; - assert.deepStrictEqual(result, expected); - }); - - it('should handle tool and argument changes simultaneously', () => { - const existing = [ - {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]}, - {name: 'bar', args: []}, - ]; - const update = [ - {name: 'foo', args: [{name: 'arg_b', argType: 'string'}]}, - {name: 'baz', args: []}, - ]; - const result = applyToExistingMetrics(existing, update); - const expected = [ - { - name: 'foo', - args: [ - {name: 'arg_a', argType: 'string', isDeprecated: true}, - {name: 'arg_b', argType: 'string'}, - ], - }, - {name: 'bar', args: [], isDeprecated: true}, - {name: 'baz', args: []}, - ]; - assert.deepStrictEqual(result, expected); - }); - }); -}); diff --git a/tests/tools/inPage.test.ts b/tests/tools/inPage.test.ts index d87bbea83..622da9034 100644 --- a/tests/tools/inPage.test.ts +++ b/tests/tools/inPage.test.ts @@ -347,98 +347,5 @@ describe('inPage', () => { {categoryInPageTools: true} as ParsedArguments, ); }); - - it('replaces uid with element handle in params', async () => { - await withMcpContext(async (response, context) => { - const page = await context.newPage(); - response.setPage(page); - - page.inPageTools = { - name: 'test-group', - description: 'test description', - tools: [ - { - name: 'test-tool', - description: 'test tool description', - inputSchema: { - type: 'object', - properties: { - element: {type: 'object'}, - }, - required: ['element'], - }, - }, - ], - }; - - await page.pptrPage.evaluate(() => { - window.__dtmcp = { - executeTool: async ( - _name: string, - args: Record, - ) => { - const el = args.element; - if (el instanceof HTMLElement) { - return { - isElement: true, - tagName: el.tagName, - id: el.id, - }; - } - return { - isElement: false, - tagName: '', - id: '', - }; - }, - }; - }); - - await page.pptrPage.evaluate(() => { - const div = document.createElement('div'); - div.id = 'test-id'; - document.body.appendChild(div); - }); - - const handle = await page.pptrPage.$('#test-id'); - if (!handle) { - throw new Error('Handle not found'); - } - - page.getElementByUid = async (uid: string) => { - if (uid === 'some-uid') { - return handle; - } - throw new Error('Not found'); - }; - - await executeInPageTool.handler( - { - params: { - toolName: 'test-tool', - params: JSON.stringify({element: {uid: 'some-uid'}}), - }, - page: page, - }, - response, - context, - ); - - assert.strictEqual( - response.responseLines[0], - JSON.stringify( - { - result: { - isElement: true, - tagName: 'DIV', - id: 'test-id', - }, - }, - null, - 2, - ), - ); - }); - }); }); }); diff --git a/tests/tools/script.test.ts b/tests/tools/script.test.ts index a0dc4f694..85deffb29 100644 --- a/tests/tools/script.test.ts +++ b/tests/tools/script.test.ts @@ -107,8 +107,9 @@ describe('script', () => { await evaluateScript().handler( { params: { - function: String(() => { - alert('hello'); + function: String(async () => { + setTimeout(() => alert('hello'), 10); + await new Promise(r => setTimeout(r, 20)); return 'Works'; }), }, diff --git a/tsconfig.json b/tsconfig.json index c6597abe7..3d09e3f58 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,13 +5,14 @@ "ES2023", "DOM", "ES2024.Promise", - "ES2025.Iterator", - "ES2025.Collection" + "ESNext.Iterator", + "ESNext.Collection" ], - "types": ["node", "filesystem"], + "module": "esnext", "moduleResolution": "bundler", "outDir": "./build", "rootDir": ".", + "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, From 00c5447445736f428f5b94feda843d940b17101f Mon Sep 17 00:00:00 2001 From: Lightning00Blade <34244704+Lightning00Blade@users.noreply.github.com> Date: Thu, 2 Apr 2026 14:18:31 +0000 Subject: [PATCH 09/10] chore: remove temporary debug scripts Removed `test_dialog.js`, `test_dialog.mjs`, and `test_dialog2.mjs` which were accidentally left in the workspace and caused `npm run check-format` to fail. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- test_dialog.js | 21 --------------------- test_dialog.mjs | 20 -------------------- test_dialog2.mjs | 26 -------------------------- 3 files changed, 67 deletions(-) delete mode 100644 test_dialog.js delete mode 100644 test_dialog.mjs delete mode 100644 test_dialog2.mjs diff --git a/test_dialog.js b/test_dialog.js deleted file mode 100644 index 69deb5228..000000000 --- a/test_dialog.js +++ /dev/null @@ -1,21 +0,0 @@ -const puppeteer = require('puppeteer-core'); -const cp = require('child_process'); - -(async () => { - const browser = await puppeteer.launch({ - executablePath: '/usr/bin/google-chrome', - headless: 'new', - }); - const page = await browser.newPage(); - - page.on('dialog', async dialog => { - console.log('Dialog opened'); - await dialog.accept(); - }); - - console.log('Evaluating alert...'); - await page.evaluate(() => alert('hello')); - console.log('Evaluate finished'); - - await browser.close(); -})(); diff --git a/test_dialog.mjs b/test_dialog.mjs deleted file mode 100644 index e4094fe57..000000000 --- a/test_dialog.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import puppeteer from 'puppeteer-core'; - -(async () => { - const browser = await puppeteer.launch({ - executablePath: '/usr/bin/google-chrome', - headless: 'new', - }); - const page = await browser.newPage(); - - page.on('dialog', async dialog => { - console.log('Dialog opened'); - await dialog.accept(); - }); - - console.log('Evaluating alert...'); - await page.evaluate(() => alert('hello')); - console.log('Evaluate finished'); - - await browser.close(); -})(); diff --git a/test_dialog2.mjs b/test_dialog2.mjs deleted file mode 100644 index 4b9de4233..000000000 --- a/test_dialog2.mjs +++ /dev/null @@ -1,26 +0,0 @@ -import puppeteer from 'puppeteer-core'; - -(async () => { - const browser = await puppeteer.launch({ - executablePath: '/usr/bin/google-chrome', - headless: 'new', - }); - const page = await browser.newPage(); - - let savedDialog; - page.on('dialog', dialog => { - console.log('Dialog opened - first handler'); - savedDialog = dialog; - }); - - page.on('dialog', async dialog => { - console.log('Dialog opened - second handler'); - await dialog.accept(); - }); - - console.log('Evaluating alert...'); - await page.evaluate(() => alert('hello')); - console.log('Evaluate finished. Saved dialog:', savedDialog?.message()); - - await browser.close(); -})(); From 560384b4f8774ddee0c0726353e38993c3804113 Mon Sep 17 00:00:00 2001 From: Lightning00Blade <34244704+Lightning00Blade@users.noreply.github.com> Date: Thu, 2 Apr 2026 14:35:31 +0000 Subject: [PATCH 10/10] docs: update README format Updated README.md formatting to match latest requirements to fix `check-format` CI job. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 14eead745..ed6167d30 100644 --- a/README.md +++ b/README.md @@ -403,9 +403,10 @@ qodercli mcp add -s user chrome-devtools -- npx chrome-devtools-mcp@latest
Visual Studio - **Click the button to install:** +**Click the button to install:** + +[Install in Visual Studio](https://vs-open.link/mcp-install?%7B%22name%22%3A%22chrome-devtools%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22chrome-devtools-mcp%40latest%22%5D%7D) - [Install in Visual Studio](https://vs-open.link/mcp-install?%7B%22name%22%3A%22chrome-devtools%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22chrome-devtools-mcp%40latest%22%5D%7D)