豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.

Commit bb85ab8

Browse files
nroscinowolfib
authored andcommitted
chore: list service workers (ChromeDevTools#1035)
This PR adds the extension service workers to the output of listPages. This is available only behind "--category-extensions" flag
1 parent ce27686 commit bb85ab8

File tree

15 files changed

+214
-33
lines changed

15 files changed

+214
-33
lines changed

docs/tool-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!-- AUTO GENERATED DO NOT EDIT - run 'npm run docs' to update-->
22

3-
# Chrome DevTools MCP Tool Reference (~7094 cl100k_base tokens)
3+
# Chrome DevTools MCP Tool Reference (~7095 cl100k_base tokens)
44

55
- **[Input automation](#input-automation)** (9 tools)
66
- [`click`](#click)

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import tseslint from 'typescript-eslint';
1414
import localPlugin from './scripts/eslint_rules/local-plugin.js';
1515

1616
export default defineConfig([
17-
globalIgnores(['**/node_modules', '**/build/']),
17+
globalIgnores(['**/node_modules', '**/build/', 'tests/tools/fixtures/']),
1818
importPlugin.flatConfigs.typescript,
1919
{
2020
languageOptions: {

src/McpContext.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
ScreenRecorder,
3030
SerializedAXNode,
3131
Viewport,
32+
Target,
3233
} from './third_party/index.js';
3334
import {Locator, PredefinedNetworkConditions} from './third_party/index.js';
3435
import {type ToolGroup} from './tools/inPage.js';
@@ -50,6 +51,12 @@ export interface TextSnapshotNode extends SerializedAXNode {
5051
children: TextSnapshotNode[];
5152
}
5253

54+
export interface ExtensionServiceWorker {
55+
url: string;
56+
target: Target;
57+
id: string;
58+
}
59+
5360
export interface GeolocationOptions {
5461
latitude: number;
5562
longitude: number;
@@ -129,6 +136,8 @@ export class McpContext implements Context {
129136
#nextIsolatedContextId = 1;
130137

131138
#pages: Page[] = [];
139+
#extensionServiceWorkers: ExtensionServiceWorker[] = [];
140+
132141
#pageToDevToolsPage = new Map<Page, Page>();
133142
#selectedPage?: Page;
134143
#textSnapshot: TextSnapshot | null = null;
@@ -147,6 +156,9 @@ export class McpContext implements Context {
147156
#pageIdMap = new WeakMap<Page, number>();
148157
#nextPageId = 1;
149158

159+
#extensionServiceWorkerMap = new WeakMap<Target, string>();
160+
#nextExtensionServiceWorkerId = 1;
161+
150162
#nextSnapshotId = 1;
151163
#traceResults: TraceResult[] = [];
152164

@@ -186,6 +198,7 @@ export class McpContext implements Context {
186198

187199
async #init() {
188200
const pages = await this.createPagesSnapshot();
201+
await this.createExtensionServiceWorkersSnapshot();
189202
await this.#networkCollector.init(pages);
190203
await this.#consoleCollector.init(pages);
191204
await this.#devtoolsUniverseManager.init(pages);
@@ -495,7 +508,7 @@ export class McpContext implements Context {
495508
}
496509
if (page.isClosed()) {
497510
throw new Error(
498-
`The selected page has been closed. Call ${listPages.name} to see open pages.`,
511+
`The selected page has been closed. Call ${listPages().name} to see open pages.`,
499512
);
500513
}
501514
return page;
@@ -593,6 +606,41 @@ export class McpContext implements Context {
593606
}
594607
}
595608

609+
/**
610+
* Creates a snapshot of the extension service workers.
611+
*/
612+
async createExtensionServiceWorkersSnapshot(): Promise<
613+
ExtensionServiceWorker[]
614+
> {
615+
const allTargets = await this.browser.targets();
616+
617+
const serviceWorkers = allTargets.filter(target => {
618+
return (
619+
target.type() === 'service_worker' &&
620+
target.url().includes('chrome-extension://')
621+
);
622+
});
623+
624+
for (const serviceWorker of serviceWorkers) {
625+
if (!this.#extensionServiceWorkerMap.has(serviceWorker)) {
626+
this.#extensionServiceWorkerMap.set(
627+
serviceWorker,
628+
'sw-' + this.#nextExtensionServiceWorkerId++,
629+
);
630+
}
631+
}
632+
633+
this.#extensionServiceWorkers = serviceWorkers.map(serviceWorker => {
634+
return {
635+
target: serviceWorker,
636+
id: this.#extensionServiceWorkerMap.get(serviceWorker)!,
637+
url: serviceWorker.url(),
638+
};
639+
});
640+
641+
return this.#extensionServiceWorkers;
642+
}
643+
596644
async createPagesSnapshot(): Promise<Page[]> {
597645
const allPages = await this.#getAllPages();
598646

@@ -686,6 +734,16 @@ export class McpContext implements Context {
686734
}
687735
}
688736

737+
getExtensionServiceWorkers(): ExtensionServiceWorker[] {
738+
return this.#extensionServiceWorkers;
739+
}
740+
741+
getExtensionServiceWorkerId(
742+
extensionServiceWorker: ExtensionServiceWorker,
743+
): string | undefined {
744+
return this.#extensionServiceWorkerMap.get(extensionServiceWorker.target);
745+
}
746+
689747
getPages(): Page[] {
690748
return this.#pages;
691749
}

src/McpResponse.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7+
import type {ParsedArguments} from './cli.js';
78
import {ConsoleFormatter} from './formatters/ConsoleFormatter.js';
89
import {IssueFormatter} from './formatters/IssueFormatter.js';
910
import {NetworkFormatter} from './formatters/NetworkFormatter.js';
@@ -58,6 +59,7 @@ async function getToolGroup(page: Page) {
5859

5960
export class McpResponse implements Response {
6061
#includePages = false;
62+
#includeExtensionServiceWorkers = false;
6163
#snapshotParams?: SnapshotParams;
6264
#attachedNetworkRequestId?: number;
6365
#attachedNetworkRequestOptions?: {
@@ -86,6 +88,11 @@ export class McpResponse implements Response {
8688
#listInPageTools?: boolean;
8789
#devToolsData?: DevToolsData;
8890
#tabId?: string;
91+
#args: ParsedArguments;
92+
93+
constructor(args: ParsedArguments) {
94+
this.#args = args;
95+
}
8996

9097
attachDevToolsData(data: DevToolsData): void {
9198
this.#devToolsData = data;
@@ -97,6 +104,10 @@ export class McpResponse implements Response {
97104

98105
setIncludePages(value: boolean): void {
99106
this.#includePages = value;
107+
108+
if (this.#args.categoryExtensions) {
109+
this.#includeExtensionServiceWorkers = value;
110+
}
100111
}
101112

102113
includeSnapshot(params?: SnapshotParams): void {
@@ -258,6 +269,10 @@ export class McpResponse implements Response {
258269
await context.createPagesSnapshot();
259270
}
260271

272+
if (this.#includeExtensionServiceWorkers) {
273+
await context.createExtensionServiceWorkersSnapshot();
274+
}
275+
261276
let snapshot: SnapshotFormatter | string | undefined;
262277
if (this.#snapshotParams) {
263278
await context.createTextSnapshot(
@@ -473,6 +488,7 @@ export class McpResponse implements Response {
473488
};
474489
pages?: object[];
475490
pagination?: object;
491+
extensionServiceWorkers?: object[];
476492
} = {};
477493

478494
const response = [`# ${toolName} response`];
@@ -567,6 +583,26 @@ Call ${handleDialog.name} to handle it before continuing.`);
567583
});
568584
}
569585

586+
if (this.#includeExtensionServiceWorkers) {
587+
if (!context.getExtensionServiceWorkers().length) {
588+
response.push(`## Extension Service Workers`);
589+
}
590+
591+
for (const extensionServiceWorker of context.getExtensionServiceWorkers()) {
592+
response.push(
593+
`${extensionServiceWorker.id}: ${extensionServiceWorker.url}`,
594+
);
595+
}
596+
structuredContent.extensionServiceWorkers = context
597+
.getExtensionServiceWorkers()
598+
.map(extensionServiceWorker => {
599+
return {
600+
id: extensionServiceWorker.id,
601+
url: extensionServiceWorker.url,
602+
};
603+
});
604+
}
605+
570606
if (this.#tabId) {
571607
structuredContent.tabId = this.#tabId;
572608
}

src/main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ function registerTool(tool: ToolDefinition): void {
203203
const context = await getContext();
204204
logger(`${tool.name} context: resolved`);
205205
await context.detectOpenDevToolsWindows();
206-
const response = args.slim ? new SlimMcpResponse() : new McpResponse();
206+
const response = args.slim
207+
? new SlimMcpResponse(args)
208+
: new McpResponse(args);
209+
207210
await tool.handler(
208211
{
209212
params,

src/tools/ToolDefinition.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,16 @@ export function defineTool<
168168
Schema extends zod.ZodRawShape,
169169
Args extends ParsedArguments = ParsedArguments,
170170
>(
171-
definition: (args: Args) => ToolDefinition<Schema>,
172-
): (args: Args) => ToolDefinition<Schema>;
171+
definition: (args?: Args) => ToolDefinition<Schema>,
172+
): (args?: Args) => ToolDefinition<Schema>;
173173

174174
export function defineTool<
175175
Schema extends zod.ZodRawShape,
176176
Args extends ParsedArguments = ParsedArguments,
177177
>(
178-
definition: ToolDefinition<Schema> | ((args: Args) => ToolDefinition<Schema>),
178+
definition:
179+
| ToolDefinition<Schema>
180+
| ((args?: Args) => ToolDefinition<Schema>),
179181
) {
180182
return definition;
181183
}

src/tools/pages.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,24 @@ import {zod} from '../third_party/index.js';
1111
import {ToolCategory} from './categories.js';
1212
import {CLOSE_PAGE_ERROR, defineTool, timeoutSchema} from './ToolDefinition.js';
1313

14-
export const listPages = defineTool({
15-
name: 'list_pages',
16-
description: `Get a list of pages open in the browser.`,
17-
annotations: {
18-
category: ToolCategory.NAVIGATION,
19-
readOnlyHint: true,
20-
},
21-
schema: {},
22-
handler: async (_request, response, context) => {
23-
response.setIncludePages(true);
24-
// Only include in-page tools in the response, if they haven't already been
25-
// sent before.
26-
if (context.getInPageTools() === undefined) {
27-
response.setListInPageTools();
28-
}
29-
},
14+
export const listPages = defineTool(args => {
15+
return {
16+
name: 'list_pages',
17+
description: `Get a list of pages ${args?.categoryExtensions ? 'including extension service workers' : ''} open in the browser.`,
18+
annotations: {
19+
category: ToolCategory.NAVIGATION,
20+
readOnlyHint: true,
21+
},
22+
schema: {},
23+
handler: async (_request, response, context) => {
24+
response.setIncludePages(true);
25+
// Only include in-page tools in the response, if they haven't already been
26+
// sent before.
27+
if (context.getInPageTools() === undefined) {
28+
response.setListInPageTools();
29+
}
30+
},
31+
};
3032
});
3133

3234
export const selectPage = defineTool({
@@ -40,7 +42,7 @@ export const selectPage = defineTool({
4042
pageId: zod
4143
.number()
4244
.describe(
43-
`The ID of the page to select. Call ${listPages.name} to get available pages.`,
45+
`The ID of the page to select. Call ${listPages().name} to get available pages.`,
4446
),
4547
bringToFront: zod
4648
.boolean()
@@ -380,7 +382,7 @@ export const getTabId = defineTool({
380382
pageId: zod
381383
.number()
382384
.describe(
383-
`The ID of the page to get the tab ID for. Call ${listPages.name} to get available pages.`,
385+
`The ID of the page to get the tab ID for. Call ${listPages().name} to get available pages.`,
384386
),
385387
},
386388
handler: async (request, response, context) => {

src/tools/tools.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ export const createTools = (args: ParsedArguments) => {
4444
const tools: ToolDefinition[] = [];
4545
for (const tool of rawTools) {
4646
if (typeof tool === 'function') {
47-
// @ts-expect-error none of the tools for now implement the function type tool has type "never"
48-
tools.push(tool(args) as ToolDefinition);
47+
tools.push(tool(args));
4948
} else {
5049
tools.push(tool as ToolDefinition);
5150
}

tests/tools/console.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import assert from 'node:assert';
88
import {before, describe, it} from 'node:test';
99

10+
import type {ParsedArguments} from '../../src/cli.js';
1011
import {loadIssueDescriptions} from '../../src/issue-descriptions.js';
1112
import {McpResponse} from '../../src/McpResponse.js';
1213
import {DevTools} from '../../src/third_party/index.js';
@@ -170,7 +171,7 @@ describe('console', () => {
170171
await context.createTextSnapshot();
171172
await issuePromise;
172173
await listConsoleMessages.handler({params: {}}, response, context);
173-
const response2 = new McpResponse();
174+
const response2 = new McpResponse({} as ParsedArguments);
174175
await getConsoleMessage.handler(
175176
{params: {msgid: 1}},
176177
response2,
@@ -225,7 +226,7 @@ describe('console', () => {
225226
response,
226227
context,
227228
);
228-
const response2 = new McpResponse();
229+
const response2 = new McpResponse({} as ParsedArguments);
229230
await getConsoleMessage.handler(
230231
{params: {msgid: id}},
231232
response2,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "Test Extension with SW",
4+
"version": "1.0",
5+
"background": {
6+
"service_worker": "sw.js"
7+
},
8+
"action": {
9+
"default_popup": "popup.html"
10+
}
11+
}

0 commit comments

Comments
 (0)