chrome-devtools-mcp 0.26.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -5
- package/build/src/DevtoolsUtils.js +13 -0
- package/build/src/HeapSnapshotManager.js +26 -2
- package/build/src/McpContext.js +3 -0
- package/build/src/McpResponse.js +51 -21
- package/build/src/ToolHandler.js +30 -1
- package/build/src/WaitForHelper.js +18 -4
- package/build/src/bin/check-latest-version.js +25 -1
- package/build/src/bin/chrome-devtools-cli-options.js +37 -1
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +2 -8
- package/build/src/daemon/client.js +12 -6
- package/build/src/formatters/HeapSnapshotFormatter.js +27 -6
- package/build/src/index.js +3 -1
- package/build/src/telemetry/ClearcutLogger.js +8 -119
- package/build/src/telemetry/errors.js +4 -0
- package/build/src/telemetry/flagUtils.js +4 -3
- package/build/src/telemetry/{toolMetricsUtils.js → metricsRegistry.js} +3 -3
- package/build/src/telemetry/persistence.js +20 -2
- package/build/src/telemetry/transformation.js +134 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +2 -717
- package/build/src/third_party/bundled-packages.json +2 -2
- package/build/src/third_party/devtools-formatter-worker.js +445 -114
- package/build/src/third_party/devtools-heap-snapshot-worker.js +0 -3
- package/build/src/third_party/index.js +3255 -30087
- package/build/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +4 -0
- package/build/src/tools/ToolDefinition.js +1 -1
- package/build/src/tools/emulation.js +3 -2
- package/build/src/tools/input.js +18 -9
- package/build/src/tools/memory.js +24 -0
- package/build/src/tools/script.js +32 -10
- package/build/src/version.js +1 -1
- package/package.json +7 -4
- package/build/src/telemetry/metricUtils.js +0 -15
|
@@ -33,7 +33,7 @@ export const emulate = definePageTool({
|
|
|
33
33
|
.string()
|
|
34
34
|
.optional()
|
|
35
35
|
.transform(geolocationTransform)
|
|
36
|
-
.describe('Geolocation (`<latitude
|
|
36
|
+
.describe('Geolocation (`<latitude>,<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit to clear the geolocation override.'),
|
|
37
37
|
userAgent: zod
|
|
38
38
|
.string()
|
|
39
39
|
.optional()
|
|
@@ -49,9 +49,10 @@ export const emulate = definePageTool({
|
|
|
49
49
|
.describe(`Emulate device viewports '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]'. 'touch' and 'mobile' to emulate mobile devices. 'landscape' to emulate landscape mode.`),
|
|
50
50
|
},
|
|
51
51
|
blockedByDialog: true,
|
|
52
|
-
handler: async (request,
|
|
52
|
+
handler: async (request, response, context) => {
|
|
53
53
|
const page = request.page;
|
|
54
54
|
await context.emulate(request.params, page.pptrPage);
|
|
55
|
+
response.appendResponseLine('Emulation configured successfully');
|
|
55
56
|
},
|
|
56
57
|
});
|
|
57
58
|
//# sourceMappingURL=emulation.js.map
|
package/build/src/tools/input.js
CHANGED
|
@@ -85,7 +85,7 @@ export const click = definePageTool({
|
|
|
85
85
|
const aXNode = request.page.getAXNodeByUid(uid);
|
|
86
86
|
const shouldSelectNativeOption = !request.params.dblClick && aXNode?.role === 'option';
|
|
87
87
|
try {
|
|
88
|
-
await request.page.waitForEventsAfterAction(async () => {
|
|
88
|
+
const result = await request.page.waitForEventsAfterAction(async () => {
|
|
89
89
|
if (shouldSelectNativeOption &&
|
|
90
90
|
(await selectNativeSelectOption(handle))) {
|
|
91
91
|
return;
|
|
@@ -97,6 +97,7 @@ export const click = definePageTool({
|
|
|
97
97
|
response.appendResponseLine(request.params.dblClick
|
|
98
98
|
? `Successfully double clicked on the element`
|
|
99
99
|
: `Successfully clicked on the element`);
|
|
100
|
+
response.attachWaitForResult(result);
|
|
100
101
|
if (request.params.includeSnapshot) {
|
|
101
102
|
response.includeSnapshot();
|
|
102
103
|
}
|
|
@@ -126,14 +127,15 @@ export const clickAt = definePageTool({
|
|
|
126
127
|
blockedByDialog: true,
|
|
127
128
|
handler: async (request, response) => {
|
|
128
129
|
const page = request.page;
|
|
129
|
-
await page.waitForEventsAfterAction(async () => {
|
|
130
|
+
const result = await page.waitForEventsAfterAction(async () => {
|
|
130
131
|
await page.pptrPage.mouse.click(request.params.x, request.params.y, {
|
|
131
|
-
|
|
132
|
+
count: request.params.dblClick ? 2 : 1,
|
|
132
133
|
});
|
|
133
134
|
});
|
|
134
135
|
response.appendResponseLine(request.params.dblClick
|
|
135
136
|
? `Successfully double clicked at the coordinates`
|
|
136
137
|
: `Successfully clicked at the coordinates`);
|
|
138
|
+
response.attachWaitForResult(result);
|
|
137
139
|
if (request.params.includeSnapshot) {
|
|
138
140
|
response.includeSnapshot();
|
|
139
141
|
}
|
|
@@ -157,10 +159,11 @@ export const hover = definePageTool({
|
|
|
157
159
|
const uid = request.params.uid;
|
|
158
160
|
const handle = await request.page.getElementByUid(uid);
|
|
159
161
|
try {
|
|
160
|
-
await request.page.waitForEventsAfterAction(async () => {
|
|
162
|
+
const result = await request.page.waitForEventsAfterAction(async () => {
|
|
161
163
|
await handle.asLocator().hover();
|
|
162
164
|
});
|
|
163
165
|
response.appendResponseLine(`Successfully hovered over the element`);
|
|
166
|
+
response.attachWaitForResult(result);
|
|
164
167
|
if (request.params.includeSnapshot) {
|
|
165
168
|
response.includeSnapshot();
|
|
166
169
|
}
|
|
@@ -269,10 +272,11 @@ export const fill = definePageTool({
|
|
|
269
272
|
blockedByDialog: true,
|
|
270
273
|
handler: async (request, response, context) => {
|
|
271
274
|
const page = request.page;
|
|
272
|
-
await page.waitForEventsAfterAction(async () => {
|
|
275
|
+
const result = await page.waitForEventsAfterAction(async () => {
|
|
273
276
|
await fillFormElement(request.params.uid, request.params.value, context, page);
|
|
274
277
|
});
|
|
275
278
|
response.appendResponseLine(`Successfully filled out the element`);
|
|
279
|
+
response.attachWaitForResult(result);
|
|
276
280
|
if (request.params.includeSnapshot) {
|
|
277
281
|
response.includeSnapshot();
|
|
278
282
|
}
|
|
@@ -292,13 +296,14 @@ export const typeText = definePageTool({
|
|
|
292
296
|
blockedByDialog: true,
|
|
293
297
|
handler: async (request, response) => {
|
|
294
298
|
const page = request.page;
|
|
295
|
-
await page.waitForEventsAfterAction(async () => {
|
|
299
|
+
const result = await page.waitForEventsAfterAction(async () => {
|
|
296
300
|
await page.pptrPage.keyboard.type(request.params.text);
|
|
297
301
|
if (request.params.submitKey) {
|
|
298
302
|
await page.pptrPage.keyboard.press(request.params.submitKey);
|
|
299
303
|
}
|
|
300
304
|
});
|
|
301
305
|
response.appendResponseLine(`Typed text "${request.params.text}${request.params.submitKey ? ` + ${request.params.submitKey}` : ''}"`);
|
|
306
|
+
response.attachWaitForResult(result);
|
|
302
307
|
},
|
|
303
308
|
});
|
|
304
309
|
export const drag = definePageTool({
|
|
@@ -318,12 +323,13 @@ export const drag = definePageTool({
|
|
|
318
323
|
const fromHandle = await request.page.getElementByUid(request.params.from_uid);
|
|
319
324
|
const toHandle = await request.page.getElementByUid(request.params.to_uid);
|
|
320
325
|
try {
|
|
321
|
-
await request.page.waitForEventsAfterAction(async () => {
|
|
326
|
+
const result = await request.page.waitForEventsAfterAction(async () => {
|
|
322
327
|
await fromHandle.drag(toHandle);
|
|
323
328
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
324
329
|
await toHandle.drop(fromHandle);
|
|
325
330
|
});
|
|
326
331
|
response.appendResponseLine(`Successfully dragged an element`);
|
|
332
|
+
response.attachWaitForResult(result);
|
|
327
333
|
if (request.params.includeSnapshot) {
|
|
328
334
|
response.includeSnapshot();
|
|
329
335
|
}
|
|
@@ -357,12 +363,14 @@ export const fillForm = definePageTool({
|
|
|
357
363
|
blockedByDialog: true,
|
|
358
364
|
handler: async (request, response, context) => {
|
|
359
365
|
const page = request.page;
|
|
366
|
+
let lastResult = {};
|
|
360
367
|
for (const element of request.params.elements) {
|
|
361
|
-
await page.waitForEventsAfterAction(async () => {
|
|
368
|
+
lastResult = await page.waitForEventsAfterAction(async () => {
|
|
362
369
|
await fillFormElement(element.uid, element.value, context, page);
|
|
363
370
|
});
|
|
364
371
|
}
|
|
365
372
|
response.appendResponseLine(`Successfully filled out the form`);
|
|
373
|
+
response.attachWaitForResult(lastResult);
|
|
366
374
|
if (request.params.includeSnapshot) {
|
|
367
375
|
response.includeSnapshot();
|
|
368
376
|
}
|
|
@@ -434,7 +442,7 @@ export const pressKey = definePageTool({
|
|
|
434
442
|
const page = request.page;
|
|
435
443
|
const tokens = parseKey(request.params.key);
|
|
436
444
|
const [key, ...modifiers] = tokens;
|
|
437
|
-
await page.waitForEventsAfterAction(async () => {
|
|
445
|
+
const result = await page.waitForEventsAfterAction(async () => {
|
|
438
446
|
for (const modifier of modifiers) {
|
|
439
447
|
await page.pptrPage.keyboard.down(modifier);
|
|
440
448
|
}
|
|
@@ -444,6 +452,7 @@ export const pressKey = definePageTool({
|
|
|
444
452
|
}
|
|
445
453
|
});
|
|
446
454
|
response.appendResponseLine(`Successfully pressed key: ${request.params.key}`);
|
|
455
|
+
response.attachWaitForResult(result);
|
|
447
456
|
if (request.params.includeSnapshot) {
|
|
448
457
|
response.includeSnapshot();
|
|
449
458
|
}
|
|
@@ -103,4 +103,28 @@ export const getNodesByClass = defineTool({
|
|
|
103
103
|
});
|
|
104
104
|
},
|
|
105
105
|
});
|
|
106
|
+
export const getNodeRetainers = defineTool({
|
|
107
|
+
name: 'get_node_retainers',
|
|
108
|
+
description: 'Loads a memory heapsnapshot and returns retainers for a specific node ID.',
|
|
109
|
+
annotations: {
|
|
110
|
+
category: ToolCategory.MEMORY,
|
|
111
|
+
readOnlyHint: true,
|
|
112
|
+
conditions: ['experimentalMemory'],
|
|
113
|
+
},
|
|
114
|
+
blockedByDialog: false,
|
|
115
|
+
schema: {
|
|
116
|
+
filePath: zod.string().describe('A path to a .heapsnapshot file to read.'),
|
|
117
|
+
nodeId: zod.number().describe('The stable node ID to get retainers for.'),
|
|
118
|
+
pageIdx: zod.number().optional().describe('The page index for pagination.'),
|
|
119
|
+
pageSize: zod.number().optional().describe('The page size for pagination.'),
|
|
120
|
+
},
|
|
121
|
+
handler: async (request, response, context) => {
|
|
122
|
+
context.validatePath(request.params.filePath);
|
|
123
|
+
const retainers = await context.getHeapSnapshotRetainers(request.params.filePath, request.params.nodeId);
|
|
124
|
+
response.setHeapSnapshotNodes(retainers, {
|
|
125
|
+
pageIdx: request.params.pageIdx,
|
|
126
|
+
pageSize: request.params.pageSize,
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
});
|
|
106
130
|
//# sourceMappingURL=memory.js.map
|
|
@@ -32,6 +32,10 @@ Example with arguments: \`(el) => {
|
|
|
32
32
|
.describe('The uid of an element on the page from the page content snapshot'))
|
|
33
33
|
.optional()
|
|
34
34
|
.describe(`An optional list of arguments to pass to the function.`),
|
|
35
|
+
filePath: zod
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('The absolute or relative path to a file to save the script output to. If omitted, the output is returned inline.'),
|
|
35
39
|
dialogAction: zod
|
|
36
40
|
.string()
|
|
37
41
|
.optional()
|
|
@@ -48,7 +52,8 @@ Example with arguments: \`(el) => {
|
|
|
48
52
|
},
|
|
49
53
|
blockedByDialog: true,
|
|
50
54
|
handler: async (request, response, context) => {
|
|
51
|
-
const { serviceWorkerId, args: uidArgs, function: fnString, pageId, dialogAction, } = request.params;
|
|
55
|
+
const { serviceWorkerId, args: uidArgs, function: fnString, pageId, dialogAction, filePath, } = request.params;
|
|
56
|
+
context.validatePath(filePath);
|
|
52
57
|
if (cliArgs?.categoryExtensions && serviceWorkerId) {
|
|
53
58
|
if (uidArgs && uidArgs.length > 0) {
|
|
54
59
|
throw new Error('args (element uids) cannot be used when evaluating in a service worker.');
|
|
@@ -57,9 +62,15 @@ Example with arguments: \`(el) => {
|
|
|
57
62
|
throw new Error('specify either a pageId or a serviceWorkerId.');
|
|
58
63
|
}
|
|
59
64
|
const worker = await getWebWorker(context, serviceWorkerId);
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
const result = await context
|
|
66
|
+
.getSelectedMcpPage()
|
|
67
|
+
.waitForEventsAfterAction(async () => {
|
|
68
|
+
await performEvaluation(worker, fnString, [], response, {
|
|
69
|
+
filePath,
|
|
70
|
+
context,
|
|
71
|
+
});
|
|
62
72
|
}, { handleDialog: dialogAction ?? 'accept' });
|
|
73
|
+
response.attachWaitForResult(result);
|
|
63
74
|
return;
|
|
64
75
|
}
|
|
65
76
|
const mcpPage = cliArgs?.experimentalPageIdRouting
|
|
@@ -75,9 +86,13 @@ Example with arguments: \`(el) => {
|
|
|
75
86
|
args.push(handle);
|
|
76
87
|
}
|
|
77
88
|
const evaluatable = await getPageOrFrame(page, frames);
|
|
78
|
-
await mcpPage.waitForEventsAfterAction(async () => {
|
|
79
|
-
await performEvaluation(evaluatable, fnString, args, response
|
|
89
|
+
const result = await mcpPage.waitForEventsAfterAction(async () => {
|
|
90
|
+
await performEvaluation(evaluatable, fnString, args, response, {
|
|
91
|
+
filePath,
|
|
92
|
+
context,
|
|
93
|
+
});
|
|
80
94
|
}, { handleDialog: dialogAction ?? 'accept' });
|
|
95
|
+
response.attachWaitForResult(result);
|
|
81
96
|
}
|
|
82
97
|
finally {
|
|
83
98
|
void Promise.allSettled(args.map(arg => arg.dispose()));
|
|
@@ -85,17 +100,24 @@ Example with arguments: \`(el) => {
|
|
|
85
100
|
},
|
|
86
101
|
};
|
|
87
102
|
});
|
|
88
|
-
const performEvaluation = async (evaluatable, fnString, args, response) => {
|
|
103
|
+
const performEvaluation = async (evaluatable, fnString, args, response, options) => {
|
|
89
104
|
const fn = await evaluatable.evaluateHandle(`(${fnString})`);
|
|
90
105
|
try {
|
|
91
106
|
const result = await evaluatable.evaluate(async (fn, ...args) => {
|
|
92
107
|
// @ts-expect-error no types for function fn
|
|
93
108
|
return JSON.stringify(await fn(...args));
|
|
94
109
|
}, fn, ...args);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
if (options?.filePath) {
|
|
111
|
+
const data = new TextEncoder().encode(result ?? 'undefined');
|
|
112
|
+
const { filename } = await options.context.saveFile(data, options.filePath, '.json');
|
|
113
|
+
response.appendResponseLine(`Script ran on page. Output saved to ${filename}.`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
response.appendResponseLine('Script ran on page and returned:');
|
|
117
|
+
response.appendResponseLine('```json');
|
|
118
|
+
response.appendResponseLine(`${result}`);
|
|
119
|
+
response.appendResponseLine('```');
|
|
120
|
+
}
|
|
99
121
|
}
|
|
100
122
|
finally {
|
|
101
123
|
void fn.dispose();
|
package/build/src/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-devtools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "MCP server for Chrome DevTools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@types/yargs": "^17.0.33",
|
|
63
63
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
64
64
|
"@typescript-eslint/parser": "^8.43.0",
|
|
65
|
-
"chrome-devtools-frontend": "1.0.
|
|
65
|
+
"chrome-devtools-frontend": "1.0.1631386",
|
|
66
66
|
"core-js": "3.49.0",
|
|
67
67
|
"debug": "4.4.3",
|
|
68
68
|
"eslint": "^9.35.0",
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
"globals": "^17.0.0",
|
|
72
72
|
"lighthouse": "13.3.0",
|
|
73
73
|
"prettier": "^3.6.2",
|
|
74
|
-
"puppeteer": "
|
|
75
|
-
"rollup": "4.60.
|
|
74
|
+
"puppeteer": "25.0.4",
|
|
75
|
+
"rollup": "4.60.4",
|
|
76
76
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
77
77
|
"rollup-plugin-license": "^3.6.0",
|
|
78
78
|
"semver": "^7.7.4",
|
|
@@ -84,5 +84,8 @@
|
|
|
84
84
|
},
|
|
85
85
|
"engines": {
|
|
86
86
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
|
87
|
+
},
|
|
88
|
+
"overrides": {
|
|
89
|
+
"puppeteer-core": "$puppeteer"
|
|
87
90
|
}
|
|
88
91
|
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
const LATENCY_BUCKETS = [50, 100, 250, 500, 1000, 2500, 5000, 10000];
|
|
7
|
-
export function bucketizeLatency(latencyMs) {
|
|
8
|
-
for (const bucket of LATENCY_BUCKETS) {
|
|
9
|
-
if (latencyMs <= bucket) {
|
|
10
|
-
return bucket;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return LATENCY_BUCKETS[LATENCY_BUCKETS.length - 1];
|
|
14
|
-
}
|
|
15
|
-
//# sourceMappingURL=metricUtils.js.map
|