From d25b7c69872ca84a81e29c21330263b81c401780 Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Thu, 14 May 2026 12:12:47 +0200 Subject: [PATCH 1/3] feat: implementation of service worker --- docs/tool-reference.md | 7 +- src/McpContext.ts | 46 ++-- src/McpResponse.ts | 30 ++- src/PageCollector.ts | 6 +- src/ServiceWorkerCollector.ts | 202 ++++++++++++++++++ src/tools/ToolDefinition.ts | 1 + tests/ServiceWorkerCollector.test.ts | 75 +++++++ tests/tools/console.test.js.snapshot | 8 + tests/tools/console.test.ts | 87 ++++++++ tests/tools/extensions.test.ts | 1 + .../fixtures/extension-logging/manifest.json | 8 + tests/tools/fixtures/extension-logging/sw.js | 1 + tests/tools/script.test.ts | 2 + 13 files changed, 444 insertions(+), 30 deletions(-) create mode 100644 src/ServiceWorkerCollector.ts create mode 100644 tests/ServiceWorkerCollector.test.ts create mode 100644 tests/tools/fixtures/extension-logging/manifest.json create mode 100644 tests/tools/fixtures/extension-logging/sw.js diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 238d5981a..f7afe71fa 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -1,6 +1,7 @@ # Chrome DevTools MCP Tool Reference +# Chrome DevTools MCP Tool Reference (~7005 cl100k_base tokens) - **[Input automation](#input-automation)** (10 tools) - [`click`](#click) @@ -345,12 +346,12 @@ so returned values have to be JSON-serializable. **Parameters:** - **function** (string) **(required)**: A JavaScript function declaration to be executed by the tool in the currently selected page. - Example without arguments: `() => { +Example without arguments: `() => { return document.title }` or `async () => { return await fetch("example.com") }`. - Example with arguments: `(el) => { +Example with arguments: `(el) => { return el.innerText; }` @@ -362,7 +363,7 @@ so returned values have to be JSON-serializable. ### `get_console_message` -**Description:** Gets a console message by its ID. You can get all messages by calling [`list_console_messages`](#list_console_messages). +**Description:** Gets a console message by its ID. You can get all messages by calling . **Parameters:** diff --git a/src/McpContext.ts b/src/McpContext.ts index 0b8107693..ea63383b2 100644 --- a/src/McpContext.ts +++ b/src/McpContext.ts @@ -20,22 +20,24 @@ import { type ListenerMap, type UncaughtError, } from './PageCollector.js'; -import { - Locator, - PredefinedNetworkConditions, - type Browser, - type BrowserContext, - type ConsoleMessage, - type Debugger, - type HTTPRequest, - type Page, - type ScreenRecorder, - type Viewport, - type Target, - type Extension, - type Root, - type DevTools, +import {ServiceWorkerConsoleCollector} from './ServiceWorkerCollector.js'; +import type {DevTools} from './third_party/index.js'; +import type { + Browser, + BrowserContext, + ConsoleMessage, + Debugger, + HTTPRequest, + Page, + ScreenRecorder, + SerializedAXNode, + Viewport, + Target, + Extension, } from './third_party/index.js'; +import type {DevTools} from './third_party/index.js'; +import {Locator} from './third_party/index.js'; +import {PredefinedNetworkConditions} from './third_party/index.js'; import {listPages} from './tools/pages.js'; import {CLOSE_PAGE_ERROR} from './tools/ToolDefinition.js'; import type {Context, SupportedExtensions} from './tools/ToolDefinition.js'; @@ -77,6 +79,7 @@ export class McpContext implements Context { #networkCollector: NetworkCollector; #consoleCollector: ConsoleCollector; #devtoolsUniverseManager: UniverseManager; + #serviceWorkerConsoleCollector: ServiceWorkerConsoleCollector; #isRunningTrace = false; #screenRecorderData: {recorder: ScreenRecorder; filePath: string} | null = @@ -121,21 +124,26 @@ export class McpContext implements Context { }, } as ListenerMap; }); + this.#serviceWorkerConsoleCollector = new ServiceWorkerConsoleCollector( + this.browser, + ); this.#devtoolsUniverseManager = new UniverseManager(this.browser); } async #init() { const pages = await this.createPagesSnapshot(); - await this.createExtensionServiceWorkersSnapshot(); + const workers = await this.createExtensionServiceWorkersSnapshot(); await this.#networkCollector.init(pages); await this.#consoleCollector.init(pages); await this.#devtoolsUniverseManager.init(pages); + await this.#serviceWorkerConsoleCollector.init(workers); } dispose() { this.#networkCollector.dispose(); this.#consoleCollector.dispose(); this.#devtoolsUniverseManager.dispose(); + this.#serviceWorkerConsoleCollector.dispose(); for (const mcpPage of this.#mcpPages.values()) { mcpPage.dispose(); } @@ -524,6 +532,12 @@ export class McpContext implements Context { return this.#extensionServiceWorkers; } + getServiceWorkerConsoleData( + extensionId: string, + ): Array { + return this.#serviceWorkerConsoleCollector.getData(extensionId); + } + async createPagesSnapshot(): Promise { const {pages: allPages, isolatedContextNames} = await this.#getAllPages(); diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 3c4044b36..6e00584c1 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -197,6 +197,7 @@ export class McpResponse implements Response { pagination?: PaginationOptions; types?: string[]; includePreservedMessages?: boolean; + serviceWorkerId?: string; }; #listExtensions?: boolean; #listThirdPartyDeveloperTools?: boolean; @@ -289,6 +290,7 @@ export class McpResponse implements Response { options?: PaginationOptions & { types?: string[]; includePreservedMessages?: boolean; + serviceWorkerId?: string; }, ): void { if (!value) { @@ -307,6 +309,7 @@ export class McpResponse implements Response { : undefined, types: options?.types, includePreservedMessages: options?.includePreservedMessages, + serviceWorkerId: options?.serviceWorkerId, }; } @@ -582,14 +585,23 @@ export class McpResponse implements Response { let consoleMessages: Array | undefined; if (this.#consoleDataOptions?.include) { - if (!this.#page) { - throw new Error(`Response must have an McpPage`); + let messages; + let page: McpPage | undefined; + + if (this.#consoleDataOptions.serviceWorkerId) { + messages = context.getServiceWorkerConsoleData( + this.#consoleDataOptions.serviceWorkerId, + ); + } else { + page = this.#page; + if (!page) { + throw new Error(`Response must have an McpPage`); + } + messages = context.getConsoleData( + page, + this.#consoleDataOptions.includePreservedMessages, + ); } - const page = this.#page; - let messages = context.getConsoleData( - this.#page, - this.#consoleDataOptions.includePreservedMessages, - ); if (this.#consoleDataOptions.types?.length) { const normalizedTypes = new Set(this.#consoleDataOptions.types); @@ -612,7 +624,9 @@ export class McpResponse implements Response { context.getConsoleMessageStableId(item); if ('args' in item || item instanceof UncaughtError) { const consoleMessage = item as ConsoleMessage | UncaughtError; - const devTools = context.getDevToolsUniverse(page); + const devTools = page + ? context.getDevToolsUniverse(page) + : null; return await ConsoleFormatter.from(consoleMessage, { id: consoleMessageStableId, fetchDetailedData: false, diff --git a/src/PageCollector.ts b/src/PageCollector.ts index 962026491..5b26bf2ac 100644 --- a/src/PageCollector.ts +++ b/src/PageCollector.ts @@ -194,11 +194,11 @@ export class PageCollector { const item = this.find(page, item => item[stableIdSymbol] === stableId); - if (item) { - return item; + if (!item) { + throw new Error('Request not found for selected page'); } - throw new Error('Request not found for selected page'); + return item; } find( diff --git a/src/ServiceWorkerCollector.ts b/src/ServiceWorkerCollector.ts new file mode 100644 index 000000000..b6cbb4b3c --- /dev/null +++ b/src/ServiceWorkerCollector.ts @@ -0,0 +1,202 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UncaughtError} from './PageCollector.js'; +import type { + ConsoleMessage, + WebWorker, + Target, + CDPSession, + Protocol, + Browser, +} from './third_party/index.js'; +import type {ExtensionServiceWorker} from './types.js'; +import type {WithSymbolId} from './utils/id.js'; +import {createIdGenerator, stableIdSymbol} from './utils/id.js'; + +const CHROME_EXTENSION_PREFIX = 'chrome-extension://'; + +export class ServiceWorkerSubscriber { + #target: Target; + #callback: (item: ConsoleMessage | UncaughtError) => void; + #session?: CDPSession; + #worker?: WebWorker; + + constructor( + target: Target, + callback: (item: ConsoleMessage | UncaughtError) => void, + ) { + this.#target = target; + this.#callback = callback; + } + + async subscribe() { + this.#session = await this.#target.createCDPSession(); + await this.#session.send('Runtime.enable'); + this.#session.on('Runtime.exceptionThrown', this.#onExceptionThrown); + + this.#worker = (await this.#target.worker()) ?? undefined; + if (this.#worker) { + this.#worker.on('console', this.#onConsole); + } + } + + async unsubscribe() { + if (this.#worker) { + this.#worker.off('console', this.#onConsole); + } + if (this.#session) { + this.#session.off('Runtime.exceptionThrown', this.#onExceptionThrown); + await this.#session.send('Runtime.disable'); + } + } + + #onConsole = (message: ConsoleMessage) => { + this.#callback(message); + }; + + #onExceptionThrown = (event: Protocol.Runtime.ExceptionThrownEvent) => { + const url = this.#target.url(); + + const extensionId = extractExtensionId(url); + + if (extensionId) { + this.#callback(new UncaughtError(event.exceptionDetails, extensionId)); + } + }; +} + +export class ServiceWorkerConsoleCollector { + #storage = new Map< + string, + Array> + >(); + #maxLogs: number; + #browser?: Browser; + #serviceWorkerSubscribers = new Map(); + #idGenerator = createIdGenerator(); + + constructor(browser?: Browser, maxLogs = 1000) { + this.#browser = browser; + this.#maxLogs = maxLogs; + } + + async init(workers: ExtensionServiceWorker[]) { + if (!this.#browser) { + return; + } + this.#browser.on('targetcreated', this.#onTargetCreated); + this.#browser.on('targetdestroyed', this.#onTargetDestroyed); + + for (const worker of workers) { + void this.#onTargetCreated(worker.target); + } + } + + dispose() { + if (!this.#browser) { + return; + } + this.#browser.off('targetcreated', this.#onTargetCreated); + this.#browser.off('targetdestroyed', this.#onTargetDestroyed); + for (const subscriber of this.#serviceWorkerSubscribers.values()) { + void subscriber.unsubscribe(); + } + this.#serviceWorkerSubscribers.clear(); + } + + #onTargetCreated = async (target: Target) => { + if (this.#serviceWorkerSubscribers.has(target)) { + return; + } + const origin = target.url(); + if (target.type() === 'service_worker' && isExtensionOrigin(origin)) { + const extensionId = extractExtensionId(origin); + + if (!extensionId) { + return; + } + + const subscriber = new ServiceWorkerSubscriber(target, item => { + this.addLog(extensionId, item); + }); + void subscriber.subscribe(); + this.#serviceWorkerSubscribers.set(target, subscriber); + } + }; + + #onTargetDestroyed = async (target: Target) => { + const subscriber = this.#serviceWorkerSubscribers.get(target); + if (subscriber) { + void subscriber.unsubscribe(); + this.#serviceWorkerSubscribers.delete(target); + } + }; + + addLog(extensionId: string, log: ConsoleMessage | UncaughtError) { + const logs = this.#storage.get(extensionId) ?? []; + const withId = log as WithSymbolId; + withId[stableIdSymbol] = this.#idGenerator(); + logs.push(withId); + if (logs.length > this.#maxLogs) { + logs.shift(); + } + this.#storage.set(extensionId, logs); + } + + getData( + extensionId: string, + ): Array> { + return this.#storage.get(extensionId) ?? []; + } + + getById( + extensionId: string, + stableId: number, + ): WithSymbolId { + const logs = this.#storage.get(extensionId); + if (!logs) { + throw new Error('No logs found for selected extension'); + } + const item = logs.find(item => item[stableIdSymbol] === stableId); + if (item) { + return item; + } + throw new Error('Log not found for selected extension'); + } + + find( + extensionId: string, + filter: (item: WithSymbolId) => boolean, + ): WithSymbolId | undefined { + const logs = this.#storage.get(extensionId); + if (!logs) { + return; + } + return logs.find(filter); + } + + clearLogs(extensionId: string) { + this.#storage.delete(extensionId); + } +} + +function extractExtensionId(origin: string): string | null { + if (!origin || !isExtensionOrigin(origin)) { + return null; + } + + const pathPart = origin.substring(CHROME_EXTENSION_PREFIX.length); + const slashIndex = pathPart.indexOf('/'); + + // if there's no / it means that pathPart is now the extensionId, otherwise + // we take everything until the first / + return slashIndex === -1 ? pathPart : pathPart.substring(0, slashIndex); +} + +function isExtensionOrigin(origin: string) { + return origin.startsWith(CHROME_EXTENSION_PREFIX); +} diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 54fb19109..60ef99188 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -132,6 +132,7 @@ export interface Response { options?: PaginationOptions & { types?: string[]; includePreservedMessages?: boolean; + serviceWorkerId?: string; }, ): void; includeSnapshot(params?: SnapshotParams): void; diff --git a/tests/ServiceWorkerCollector.test.ts b/tests/ServiceWorkerCollector.test.ts new file mode 100644 index 000000000..2417e7a62 --- /dev/null +++ b/tests/ServiceWorkerCollector.test.ts @@ -0,0 +1,75 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'node:assert'; +import {describe, it} from 'node:test'; + +import {UncaughtError} from '../src/PageCollector.js'; +import {ServiceWorkerConsoleCollector} from '../src/ServiceWorkerCollector.js'; +import type {Protocol} from '../src/third_party/index.js'; +import {stableIdSymbol} from '../src/utils/id.js'; + +describe('ServiceWorkerConsoleCollector', () => { + it('limits logs to 1000 per extension', () => { + const collector = new ServiceWorkerConsoleCollector(undefined, 10); + const extensionId = 'test-extension'; + + const mockDetails: Protocol.Runtime.ExceptionDetails = { + exceptionId: 1, + text: 'Error', + lineNumber: 1, + columnNumber: 1, + }; + + for (let i = 0; i < 15; i++) { + const error = new UncaughtError( + {...mockDetails, exceptionId: i}, + extensionId, + ); + collector.addLog(extensionId, error); + } + + const logs = collector.getData(extensionId); + assert.strictEqual(logs.length, 10, 'Should limit logs to 10'); + + const firstLog = logs[0] as UncaughtError; + assert.strictEqual( + firstLog.details.exceptionId, + 5, + 'Oldest log should be Log 5', + ); + + const lastLog = logs[logs.length - 1] as UncaughtError; + assert.strictEqual( + lastLog.details.exceptionId, + 14, + 'Last log should be Log 14', + ); + + const data = collector.getData(extensionId); + assert.strictEqual(data.length, 10, 'getData should return limited logs'); + + const logToFind = data[0]; + const logId = logToFind[stableIdSymbol]; + assert.ok(logId, 'Log should have a stable ID'); + + const foundLog = collector.getById(extensionId, logId); + assert.strictEqual( + foundLog, + logToFind, + 'getById should return correct log', + ); + + const foundViaFind = collector.find(extensionId, item => { + return item[stableIdSymbol] === logId; + }); + assert.strictEqual( + foundViaFind, + logToFind, + 'find should return correct log', + ); + }); +}); diff --git a/tests/tools/console.test.js.snapshot b/tests/tools/console.test.js.snapshot index 68ee72568..4e5d8d95a 100644 --- a/tests/tools/console.test.js.snapshot +++ b/tests/tools/console.test.js.snapshot @@ -1,3 +1,11 @@ +exports[`console > captures logs and errors from extension service worker 1`] = ` +## Console messages +Showing 1-3 of 3 (Page 1 of 1). +msgid=1 [log] Service Worker starting... (1 args) +msgid=2 [warn] This is a warning from Service Worker (1 args) +msgid=3 [error] Uncaught Error: Intentional error from Service Worker (0 args) +`; + exports[`console > get_console_message > applies source maps to stack traces of Error object (with cause) console.log arguments 1`] = ` ID: 1 Message: log> foo failed Error: bar failed diff --git a/tests/tools/console.test.ts b/tests/tools/console.test.ts index 41e310cec..47f6c8643 100644 --- a/tests/tools/console.test.ts +++ b/tests/tools/console.test.ts @@ -5,6 +5,7 @@ */ import assert from 'node:assert'; +import path from 'node:path'; import {before, describe, it} from 'node:test'; import type {Dialog} from 'puppeteer-core'; @@ -18,6 +19,7 @@ import { getConsoleMessage, listConsoleMessages, } from '../../src/tools/console.js'; +import {installExtension} from '../../src/tools/extensions.js'; import {serverHooks} from '../server.js'; import { getTextContent, @@ -29,6 +31,91 @@ describe('console', () => { before(async () => { await loadIssueDescriptions(); }); + + it('captures logs and errors from extension service worker', async t => { + await withMcpContext( + async (response, context) => { + await installExtension.handler( + {params: {path: EXTENSION_LOGGING_PATH}}, + response, + context, + ); + + const extensionId = extractExtensionId(response); + assert.ok(extensionId, 'Extension ID should be returned'); + + const swTarget = await context.browser.waitForTarget( + t => t.type() === 'service_worker' && t.url().includes(extensionId), + ); + + const swList = await context.createExtensionServiceWorkersSnapshot(); + const sw = swList.find(s => s.target === swTarget); + if (!sw) { + assert.fail('Service worker not found in context list'); + } + const swId = context.getExtensionServiceWorkerId(sw); + + const response2 = new McpResponse({} as ParsedArguments); + + await context.triggerExtensionAction(extensionId); + const worker = await swTarget.worker(); + + await worker?.evaluate( + ` + console.log('Service Worker starting...'); + console.warn('This is a warning from Service Worker'); + globalThis.setTimeout(() => { + throw new Error('Intentional error from Service Worker'); + }, 100); + `, + ); + + // This is important to wait logs from extension. + await new Promise(resolve => setTimeout(resolve, 500)); + + response2.resetResponseLineForTesting(); + + await listConsoleMessages({ + categoryExtensions: true, + } as ParsedArguments).handler( + { + params: {serviceWorkerId: extensionId}, + page: context.getSelectedMcpPage(), + }, + response2, + context, + ); + + const formattedResponse = await response2.handle('test', context); + const textContent = getTextContent(formattedResponse.content[0]); + + const sanitizedText = textContent.replaceAll( + new RegExp(extensionId, 'g'), + '', + ); + + t.assert.snapshot?.(sanitizedText); + + assert.ok( + sanitizedText.includes('Service Worker starting...'), + 'Should contain start log', + ); + assert.ok( + sanitizedText.includes('This is a warning from Service Worker'), + 'Should contain warning log', + ); + assert.ok( + sanitizedText.includes('Intentional error from Service Worker'), + 'Should contain error log', + ); + }, + {}, + { + categoryExtensions: true, + } as ParsedArguments, + ); + }); + describe('list_console_messages', () => { it('list messages', async () => { await withMcpContext(async (response, context) => { diff --git a/tests/tools/extensions.test.ts b/tests/tools/extensions.test.ts index 59580ddd8..d760be152 100644 --- a/tests/tools/extensions.test.ts +++ b/tests/tools/extensions.test.ts @@ -138,6 +138,7 @@ describe('extension', () => { assert.ok(list.length === 1, 'List should have only one extension'); const reinstalled = list.find(e => e.id === extensionId); assert.ok(reinstalled, 'Extension should be present after reload'); + await context.uninstallExtension(extensionId!); }, {}, { diff --git a/tests/tools/fixtures/extension-logging/manifest.json b/tests/tools/fixtures/extension-logging/manifest.json new file mode 100644 index 000000000..a11cf0ac2 --- /dev/null +++ b/tests/tools/fixtures/extension-logging/manifest.json @@ -0,0 +1,8 @@ +{ + "manifest_version": 3, + "name": "Test Extension for Logging", + "version": "1.0", + "background": { + "service_worker": "sw.js" + } +} diff --git a/tests/tools/fixtures/extension-logging/sw.js b/tests/tools/fixtures/extension-logging/sw.js new file mode 100644 index 000000000..e4ddc27a8 --- /dev/null +++ b/tests/tools/fixtures/extension-logging/sw.js @@ -0,0 +1 @@ +// Minimal service worker for testing diff --git a/tests/tools/script.test.ts b/tests/tools/script.test.ts index 2718deb21..5f69e5de2 100644 --- a/tests/tools/script.test.ts +++ b/tests/tools/script.test.ts @@ -332,6 +332,8 @@ describe('script', () => { const swId = context.getExtensionServiceWorkerId(sw); + await context.triggerExtensionAction(extensionId); + response.resetResponseLineForTesting(); await evaluateScript({ categoryExtensions: true, From 54e5198f7012a097e3f041a17b6039fb5688ae38 Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Thu, 14 May 2026 15:08:43 +0200 Subject: [PATCH 2/3] chore: trying to fix windows errors --- src/McpContext.ts | 2 +- src/tools/console.ts | 5 +++++ tests/tools/console.test.ts | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/McpContext.ts b/src/McpContext.ts index ea63383b2..d51077251 100644 --- a/src/McpContext.ts +++ b/src/McpContext.ts @@ -34,8 +34,8 @@ import type { Viewport, Target, Extension, + Root, } from './third_party/index.js'; -import type {DevTools} from './third_party/index.js'; import {Locator} from './third_party/index.js'; import {PredefinedNetworkConditions} from './third_party/index.js'; import {listPages} from './tools/pages.js'; diff --git a/src/tools/console.ts b/src/tools/console.ts index c0c0fd6c6..2a4c58f99 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -77,6 +77,10 @@ export const listConsoleMessages = definePageTool(cliArgs => { .describe( 'Set to true to return the preserved messages over the last 3 navigations.', ), + serviceWorkerId: zod + .string() + .optional() + .describe('Filter messages to only return messages of the specified service worker.'), }, blockedByDialog: false, handler: async (request, response) => { @@ -85,6 +89,7 @@ export const listConsoleMessages = definePageTool(cliArgs => { pageIdx: request.params.pageIdx, types: request.params.types, includePreservedMessages: request.params.includePreservedMessages, + serviceWorkerId: request.params.serviceWorkerId, }); }, }; diff --git a/tests/tools/console.test.ts b/tests/tools/console.test.ts index 47f6c8643..6320b29e5 100644 --- a/tests/tools/console.test.ts +++ b/tests/tools/console.test.ts @@ -25,8 +25,14 @@ import { getTextContent, withMcpContext, stabilizeStructuredContent, + extractExtensionId, } from '../utils.js'; +const EXTENSION_LOGGING_PATH = path.join( + import.meta.dirname, + '../../../tests/tools/fixtures/extension-logging', +); + describe('console', () => { before(async () => { await loadIssueDescriptions(); @@ -60,6 +66,16 @@ describe('console', () => { await context.triggerExtensionAction(extensionId); const worker = await swTarget.worker(); + // On Windows, the service worker context might not be fully initialized + // with all global APIs yet. + await worker?.evaluate(` + (async () => { + while (typeof globalThis.setTimeout !== 'function') { + await new Promise(resolve => Promise.resolve().then(resolve)); + } + })() + `); + await worker?.evaluate( ` console.log('Service Worker starting...'); From b29591189c64052781ec2b355dc6adc7ff616295 Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Thu, 14 May 2026 17:06:16 +0200 Subject: [PATCH 3/3] fix: lint and docs --- docs/tool-reference.md | 8 ++++---- src/McpContext.ts | 1 - src/bin/chrome-devtools-cli-options.ts | 7 +++++++ src/telemetry/tool_call_metrics.json | 4 ++++ src/tools/console.ts | 4 +++- tests/tools/console.test.ts | 1 - 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/tool-reference.md b/docs/tool-reference.md index f7afe71fa..f98784a9e 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -1,7 +1,6 @@ # Chrome DevTools MCP Tool Reference -# Chrome DevTools MCP Tool Reference (~7005 cl100k_base tokens) - **[Input automation](#input-automation)** (10 tools) - [`click`](#click) @@ -346,12 +345,12 @@ so returned values have to be JSON-serializable. **Parameters:** - **function** (string) **(required)**: A JavaScript function declaration to be executed by the tool in the currently selected page. -Example without arguments: `() => { + Example without arguments: `() => { return document.title }` or `async () => { return await fetch("example.com") }`. -Example with arguments: `(el) => { + Example with arguments: `(el) => { return el.innerText; }` @@ -363,7 +362,7 @@ Example with arguments: `(el) => { ### `get_console_message` -**Description:** Gets a console message by its ID. You can get all messages by calling . +**Description:** Gets a console message by its ID. You can get all messages by calling [`list_console_messages`](#list_console_messages). **Parameters:** @@ -392,6 +391,7 @@ Example with arguments: `(el) => { - **includePreservedMessages** (boolean) _(optional)_: Set to true to return the preserved messages over the last 3 navigations. - **pageIdx** (integer) _(optional)_: Page number to return (0-based). When omitted, returns the first page. - **pageSize** (integer) _(optional)_: Maximum number of messages to return. When omitted, returns all messages. +- **serviceWorkerId** (string) _(optional)_: Filter messages to only return messages of the specified service worker. - **types** (array) _(optional)_: Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages. --- diff --git a/src/McpContext.ts b/src/McpContext.ts index d51077251..33b60d78e 100644 --- a/src/McpContext.ts +++ b/src/McpContext.ts @@ -30,7 +30,6 @@ import type { HTTPRequest, Page, ScreenRecorder, - SerializedAXNode, Viewport, Target, Extension, diff --git a/src/bin/chrome-devtools-cli-options.ts b/src/bin/chrome-devtools-cli-options.ts index a0e315c42..fc5345486 100644 --- a/src/bin/chrome-devtools-cli-options.ts +++ b/src/bin/chrome-devtools-cli-options.ts @@ -492,6 +492,13 @@ export const commands: Commands = { required: false, default: false, }, + serviceWorkerId: { + name: 'serviceWorkerId', + type: 'string', + description: + 'Filter messages to only return messages of the specified service worker.', + required: false, + }, }, }, list_extensions: { diff --git a/src/telemetry/tool_call_metrics.json b/src/telemetry/tool_call_metrics.json index 2da34dc5f..3b4748e4c 100644 --- a/src/telemetry/tool_call_metrics.json +++ b/src/telemetry/tool_call_metrics.json @@ -250,6 +250,10 @@ { "name": "include_preserved_messages", "argType": "boolean" + }, + { + "name": "service_worker_id_length", + "argType": "number" } ] }, diff --git a/src/tools/console.ts b/src/tools/console.ts index 2a4c58f99..d975d53ca 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -80,7 +80,9 @@ export const listConsoleMessages = definePageTool(cliArgs => { serviceWorkerId: zod .string() .optional() - .describe('Filter messages to only return messages of the specified service worker.'), + .describe( + 'Filter messages to only return messages of the specified service worker.', + ), }, blockedByDialog: false, handler: async (request, response) => { diff --git a/tests/tools/console.test.ts b/tests/tools/console.test.ts index 6320b29e5..f79d60fcd 100644 --- a/tests/tools/console.test.ts +++ b/tests/tools/console.test.ts @@ -59,7 +59,6 @@ describe('console', () => { if (!sw) { assert.fail('Service worker not found in context list'); } - const swId = context.getExtensionServiceWorkerId(sw); const response2 = new McpResponse({} as ParsedArguments);