chrome-devtools-mcp 0.20.3 → 0.22.0
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 +97 -20
- package/build/src/HeapSnapshotManager.js +94 -0
- package/build/src/McpContext.js +26 -49
- package/build/src/McpPage.js +16 -0
- package/build/src/McpResponse.js +220 -12
- package/build/src/PageCollector.js +14 -28
- package/build/src/WaitForHelper.js +31 -0
- package/build/src/bin/check-latest-version.js +25 -0
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +28 -9
- package/build/src/bin/chrome-devtools-mcp-main.js +2 -0
- package/build/src/bin/chrome-devtools-mcp.js +1 -0
- package/build/src/bin/chrome-devtools.js +9 -3
- package/build/src/bin/cliDefinitions.js +15 -9
- package/build/src/daemon/client.js +1 -1
- package/build/src/daemon/daemon.js +2 -6
- package/build/src/daemon/utils.js +1 -0
- package/build/src/formatters/HeapSnapshotFormatter.js +38 -0
- package/build/src/formatters/NetworkFormatter.js +24 -7
- package/build/src/index.js +22 -1
- package/build/src/telemetry/ClearcutLogger.js +145 -6
- package/build/src/telemetry/flagUtils.js +46 -4
- package/build/src/telemetry/toolMetricsUtils.js +88 -0
- package/build/src/telemetry/types.js +5 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +4 -3
- package/build/src/third_party/THIRD_PARTY_NOTICES +1400 -483
- package/build/src/third_party/bundled-packages.json +6 -5
- package/build/src/third_party/devtools-formatter-worker.js +61 -66
- package/build/src/third_party/devtools-heap-snapshot-worker.js +9690 -0
- package/build/src/third_party/index.js +61622 -52803
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +1 -0
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +10589 -4647
- package/build/src/tools/categories.js +5 -0
- package/build/src/tools/console.js +42 -39
- package/build/src/tools/emulation.js +1 -1
- package/build/src/tools/extensions.js +5 -11
- package/build/src/tools/inPage.js +105 -0
- package/build/src/tools/input.js +18 -16
- package/build/src/tools/lighthouse.js +3 -3
- package/build/src/tools/memory.js +50 -5
- package/build/src/tools/network.js +2 -2
- package/build/src/tools/pages.js +14 -6
- package/build/src/tools/performance.js +1 -1
- package/build/src/tools/screencast.js +2 -1
- package/build/src/tools/screenshot.js +3 -3
- package/build/src/tools/script.js +22 -16
- package/build/src/tools/tools.js +4 -0
- package/build/src/tools/webmcp.js +63 -0
- package/build/src/utils/check-for-updates.js +73 -0
- package/build/src/utils/files.js +4 -0
- package/build/src/utils/id.js +15 -0
- package/build/src/version.js +1 -1
- package/package.json +13 -9
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorNoCorpCrossOriginNoCorsRequest.md +0 -3
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNoCorpCossOriginNoCorsRequest.md +0 -3
- package/build/src/utils/ExtensionRegistry.js +0 -35
|
@@ -12,6 +12,8 @@ export var ToolCategory;
|
|
|
12
12
|
ToolCategory["NETWORK"] = "network";
|
|
13
13
|
ToolCategory["DEBUGGING"] = "debugging";
|
|
14
14
|
ToolCategory["EXTENSIONS"] = "extensions";
|
|
15
|
+
ToolCategory["IN_PAGE"] = "in-page";
|
|
16
|
+
ToolCategory["MEMORY"] = "memory";
|
|
15
17
|
})(ToolCategory || (ToolCategory = {}));
|
|
16
18
|
export const labels = {
|
|
17
19
|
[ToolCategory.INPUT]: 'Input automation',
|
|
@@ -21,4 +23,7 @@ export const labels = {
|
|
|
21
23
|
[ToolCategory.NETWORK]: 'Network',
|
|
22
24
|
[ToolCategory.DEBUGGING]: 'Debugging',
|
|
23
25
|
[ToolCategory.EXTENSIONS]: 'Extensions',
|
|
26
|
+
[ToolCategory.IN_PAGE]: 'In-page tools',
|
|
27
|
+
[ToolCategory.MEMORY]: 'Memory',
|
|
24
28
|
};
|
|
29
|
+
export const OFF_BY_DEFAULT_CATEGORIES = [ToolCategory.EXTENSIONS];
|
|
@@ -28,48 +28,51 @@ const FILTERABLE_MESSAGE_TYPES = [
|
|
|
28
28
|
'verbose',
|
|
29
29
|
'issue',
|
|
30
30
|
];
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
31
|
+
const LIST_CONSOLE_MESSAGES_TOOL_NAME = 'list_console_messages';
|
|
32
|
+
export const listConsoleMessages = definePageTool(cliArgs => {
|
|
33
|
+
return {
|
|
34
|
+
name: LIST_CONSOLE_MESSAGES_TOOL_NAME,
|
|
35
|
+
description: `List all console messages for the currently selected page since the last navigation.${cliArgs?.categoryExtensions ? ' This includes console messages originating from extensions content scripts.' : ''}`,
|
|
36
|
+
annotations: {
|
|
37
|
+
category: ToolCategory.DEBUGGING,
|
|
38
|
+
readOnlyHint: true,
|
|
39
|
+
},
|
|
40
|
+
schema: {
|
|
41
|
+
pageSize: zod
|
|
42
|
+
.number()
|
|
43
|
+
.int()
|
|
44
|
+
.positive()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('Maximum number of messages to return. When omitted, returns all messages.'),
|
|
47
|
+
pageIdx: zod
|
|
48
|
+
.number()
|
|
49
|
+
.int()
|
|
50
|
+
.min(0)
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('Page number to return (0-based). When omitted, returns the first page.'),
|
|
53
|
+
types: zod
|
|
54
|
+
.array(zod.enum(FILTERABLE_MESSAGE_TYPES))
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.'),
|
|
57
|
+
includePreservedMessages: zod
|
|
58
|
+
.boolean()
|
|
59
|
+
.default(false)
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Set to true to return the preserved messages over the last 3 navigations.'),
|
|
62
|
+
},
|
|
63
|
+
handler: async (request, response) => {
|
|
64
|
+
response.setIncludeConsoleData(true, {
|
|
65
|
+
pageSize: request.params.pageSize,
|
|
66
|
+
pageIdx: request.params.pageIdx,
|
|
67
|
+
types: request.params.types,
|
|
68
|
+
includePreservedMessages: request.params.includePreservedMessages,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
69
72
|
});
|
|
70
73
|
export const getConsoleMessage = definePageTool({
|
|
71
74
|
name: 'get_console_message',
|
|
72
|
-
description: `Gets a console message by its ID. You can get all messages by calling ${
|
|
75
|
+
description: `Gets a console message by its ID. You can get all messages by calling ${LIST_CONSOLE_MESSAGES_TOOL_NAME}.`,
|
|
73
76
|
annotations: {
|
|
74
77
|
category: ToolCategory.DEBUGGING,
|
|
75
78
|
readOnlyHint: true,
|
|
@@ -33,7 +33,7 @@ export const emulate = definePageTool({
|
|
|
33
33
|
.string()
|
|
34
34
|
.optional()
|
|
35
35
|
.transform(geolocationTransform)
|
|
36
|
-
.describe('Geolocation (`<latitude>x<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit clear the geolocation override.'),
|
|
36
|
+
.describe('Geolocation (`<latitude>x<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()
|
|
@@ -6,14 +6,12 @@
|
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
7
|
import { ToolCategory } from './categories.js';
|
|
8
8
|
import { defineTool } from './ToolDefinition.js';
|
|
9
|
-
const EXTENSIONS_CONDITION = 'experimentalExtensionSupport';
|
|
10
9
|
export const installExtension = defineTool({
|
|
11
10
|
name: 'install_extension',
|
|
12
11
|
description: 'Installs a Chrome extension from the given path.',
|
|
13
12
|
annotations: {
|
|
14
13
|
category: ToolCategory.EXTENSIONS,
|
|
15
14
|
readOnlyHint: false,
|
|
16
|
-
conditions: [EXTENSIONS_CONDITION],
|
|
17
15
|
},
|
|
18
16
|
schema: {
|
|
19
17
|
path: zod
|
|
@@ -32,7 +30,6 @@ export const uninstallExtension = defineTool({
|
|
|
32
30
|
annotations: {
|
|
33
31
|
category: ToolCategory.EXTENSIONS,
|
|
34
32
|
readOnlyHint: false,
|
|
35
|
-
conditions: [EXTENSIONS_CONDITION],
|
|
36
33
|
},
|
|
37
34
|
schema: {
|
|
38
35
|
id: zod.string().describe('ID of the extension to uninstall.'),
|
|
@@ -45,11 +42,10 @@ export const uninstallExtension = defineTool({
|
|
|
45
42
|
});
|
|
46
43
|
export const listExtensions = defineTool({
|
|
47
44
|
name: 'list_extensions',
|
|
48
|
-
description: 'Lists all extensions
|
|
45
|
+
description: 'Lists all the Chrome extensions installed in the browser. This includes their name, ID, version, and enabled status.',
|
|
49
46
|
annotations: {
|
|
50
47
|
category: ToolCategory.EXTENSIONS,
|
|
51
48
|
readOnlyHint: true,
|
|
52
|
-
conditions: [EXTENSIONS_CONDITION],
|
|
53
49
|
},
|
|
54
50
|
schema: {},
|
|
55
51
|
handler: async (_request, response, _context) => {
|
|
@@ -62,14 +58,13 @@ export const reloadExtension = defineTool({
|
|
|
62
58
|
annotations: {
|
|
63
59
|
category: ToolCategory.EXTENSIONS,
|
|
64
60
|
readOnlyHint: false,
|
|
65
|
-
conditions: [EXTENSIONS_CONDITION],
|
|
66
61
|
},
|
|
67
62
|
schema: {
|
|
68
63
|
id: zod.string().describe('ID of the extension to reload.'),
|
|
69
64
|
},
|
|
70
65
|
handler: async (request, response, context) => {
|
|
71
66
|
const { id } = request.params;
|
|
72
|
-
const extension = context.getExtension(id);
|
|
67
|
+
const extension = await context.getExtension(id);
|
|
73
68
|
if (!extension) {
|
|
74
69
|
throw new Error(`Extension with ID ${id} not found.`);
|
|
75
70
|
}
|
|
@@ -79,18 +74,17 @@ export const reloadExtension = defineTool({
|
|
|
79
74
|
});
|
|
80
75
|
export const triggerExtensionAction = defineTool({
|
|
81
76
|
name: 'trigger_extension_action',
|
|
82
|
-
description: 'Triggers
|
|
77
|
+
description: 'Triggers the default action of an extension by its ID.',
|
|
83
78
|
annotations: {
|
|
84
79
|
category: ToolCategory.EXTENSIONS,
|
|
85
80
|
readOnlyHint: false,
|
|
86
|
-
conditions: [EXTENSIONS_CONDITION],
|
|
87
81
|
},
|
|
88
82
|
schema: {
|
|
89
|
-
id: zod.string().describe('ID of the extension.'),
|
|
83
|
+
id: zod.string().describe('ID of the extension to trigger the action for.'),
|
|
90
84
|
},
|
|
91
85
|
handler: async (request, response, context) => {
|
|
92
86
|
const { id } = request.params;
|
|
93
87
|
await context.triggerExtensionAction(id);
|
|
94
|
-
response.appendResponseLine(`Extension action triggered
|
|
88
|
+
response.appendResponseLine(`Extension action triggered for ID ${id}`);
|
|
95
89
|
},
|
|
96
90
|
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { zod, ajv, } from '../third_party/index.js';
|
|
7
|
+
import { ToolCategory } from './categories.js';
|
|
8
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
9
|
+
export const listInPageTools = definePageTool({
|
|
10
|
+
name: 'list_in_page_tools',
|
|
11
|
+
description: `Lists all in-page tools the page exposes for providing runtime information.
|
|
12
|
+
In-page tools can be called via the 'execute_in_page_tool()' MCP tool.
|
|
13
|
+
Alternatively, in-page tools can be executed by calling 'evaluate_script' and adding the
|
|
14
|
+
following command to the script:
|
|
15
|
+
'window.__dtmcp.executeTool(toolName, params)'
|
|
16
|
+
This might be helpful when the in-page-tools return non-serializable values or when composing
|
|
17
|
+
the in-page-tools with additional functionality.`,
|
|
18
|
+
annotations: {
|
|
19
|
+
category: ToolCategory.IN_PAGE,
|
|
20
|
+
readOnlyHint: true,
|
|
21
|
+
conditions: ['inPageTools'],
|
|
22
|
+
},
|
|
23
|
+
schema: {},
|
|
24
|
+
handler: async (_request, response, _context) => {
|
|
25
|
+
response.setListInPageTools();
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
export const executeInPageTool = definePageTool({
|
|
29
|
+
name: 'execute_in_page_tool',
|
|
30
|
+
description: `Executes a tool exposed by the page.`,
|
|
31
|
+
annotations: {
|
|
32
|
+
category: ToolCategory.IN_PAGE,
|
|
33
|
+
readOnlyHint: false,
|
|
34
|
+
conditions: ['inPageTools'],
|
|
35
|
+
},
|
|
36
|
+
schema: {
|
|
37
|
+
toolName: zod.string().describe('The name of the tool to execute'),
|
|
38
|
+
params: zod
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('The JSON-stringified parameters to pass to the tool'),
|
|
42
|
+
},
|
|
43
|
+
handler: async (request, response) => {
|
|
44
|
+
const toolName = request.params.toolName;
|
|
45
|
+
let params = {};
|
|
46
|
+
if (request.params.params) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(request.params.params);
|
|
49
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
50
|
+
params = parsed;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
throw new Error('Parsed params is not an object');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
58
|
+
throw new Error(`Failed to parse params as JSON: ${errorMessage}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Creates array of ElementHandles from the UIDs in the params.
|
|
62
|
+
// We do not replace the uids with the ElementsHandles yet, because
|
|
63
|
+
// the `evaluate` function only turns them into DOM elements if they
|
|
64
|
+
// are passed as non-nested arguments.
|
|
65
|
+
const handles = [];
|
|
66
|
+
for (const value of Object.values(params)) {
|
|
67
|
+
if (value instanceof Object &&
|
|
68
|
+
'uid' in value &&
|
|
69
|
+
typeof value.uid === 'string' &&
|
|
70
|
+
Object.keys(value).length === 1) {
|
|
71
|
+
handles.push(await request.page.getElementByUid(value.uid));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const toolGroup = request.page.getInPageTools();
|
|
75
|
+
const tool = toolGroup?.tools.find(t => t.name === toolName);
|
|
76
|
+
if (!tool) {
|
|
77
|
+
throw new Error(`Tool ${toolName} not found`);
|
|
78
|
+
}
|
|
79
|
+
const ajvInstance = new ajv();
|
|
80
|
+
const validate = ajvInstance.compile(tool.inputSchema);
|
|
81
|
+
const valid = validate(params);
|
|
82
|
+
if (!valid) {
|
|
83
|
+
throw new Error(`Invalid parameters for tool ${toolName}: ${ajvInstance.errorsText(validate.errors)}`);
|
|
84
|
+
}
|
|
85
|
+
const result = await request.page.pptrPage.evaluate(async (name, args, ...elements) => {
|
|
86
|
+
// Replace the UIDs with DOM elements.
|
|
87
|
+
for (const [key, value] of Object.entries(args)) {
|
|
88
|
+
if (value instanceof Object &&
|
|
89
|
+
'uid' in value &&
|
|
90
|
+
typeof value.uid === 'string' &&
|
|
91
|
+
Object.keys(value).length === 1) {
|
|
92
|
+
args[key] = elements.shift();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!window.__dtmcp?.executeTool) {
|
|
96
|
+
throw new Error('No tools found on the page');
|
|
97
|
+
}
|
|
98
|
+
const toolResult = await window.__dtmcp.executeTool(name, args);
|
|
99
|
+
return {
|
|
100
|
+
result: toolResult,
|
|
101
|
+
};
|
|
102
|
+
}, toolName, params, ...handles);
|
|
103
|
+
response.appendResponseLine(JSON.stringify(result, null, 2));
|
|
104
|
+
},
|
|
105
|
+
});
|
package/build/src/tools/input.js
CHANGED
|
@@ -40,11 +40,11 @@ export const click = definePageTool({
|
|
|
40
40
|
dblClick: dblClickSchema,
|
|
41
41
|
includeSnapshot: includeSnapshotSchema,
|
|
42
42
|
},
|
|
43
|
-
handler: async (request, response
|
|
43
|
+
handler: async (request, response) => {
|
|
44
44
|
const uid = request.params.uid;
|
|
45
45
|
const handle = await request.page.getElementByUid(uid);
|
|
46
46
|
try {
|
|
47
|
-
await
|
|
47
|
+
await request.page.waitForEventsAfterAction(async () => {
|
|
48
48
|
await handle.asLocator().click({
|
|
49
49
|
count: request.params.dblClick ? 2 : 1,
|
|
50
50
|
});
|
|
@@ -78,9 +78,9 @@ export const clickAt = definePageTool({
|
|
|
78
78
|
dblClick: dblClickSchema,
|
|
79
79
|
includeSnapshot: includeSnapshotSchema,
|
|
80
80
|
},
|
|
81
|
-
handler: async (request, response
|
|
81
|
+
handler: async (request, response) => {
|
|
82
82
|
const page = request.page;
|
|
83
|
-
await
|
|
83
|
+
await page.waitForEventsAfterAction(async () => {
|
|
84
84
|
await page.pptrPage.mouse.click(request.params.x, request.params.y, {
|
|
85
85
|
clickCount: request.params.dblClick ? 2 : 1,
|
|
86
86
|
});
|
|
@@ -106,11 +106,11 @@ export const hover = definePageTool({
|
|
|
106
106
|
.describe('The uid of an element on the page from the page content snapshot'),
|
|
107
107
|
includeSnapshot: includeSnapshotSchema,
|
|
108
108
|
},
|
|
109
|
-
handler: async (request, response
|
|
109
|
+
handler: async (request, response) => {
|
|
110
110
|
const uid = request.params.uid;
|
|
111
111
|
const handle = await request.page.getElementByUid(uid);
|
|
112
112
|
try {
|
|
113
|
-
await
|
|
113
|
+
await request.page.waitForEventsAfterAction(async () => {
|
|
114
114
|
await handle.asLocator().hover();
|
|
115
115
|
});
|
|
116
116
|
response.appendResponseLine(`Successfully hovered over the element`);
|
|
@@ -188,7 +188,7 @@ async function fillFormElement(uid, value, context, page) {
|
|
|
188
188
|
}
|
|
189
189
|
export const fill = definePageTool({
|
|
190
190
|
name: 'fill',
|
|
191
|
-
description: `Type text into
|
|
191
|
+
description: `Type text into an input, text area or select an option from a <select> element.`,
|
|
192
192
|
annotations: {
|
|
193
193
|
category: ToolCategory.INPUT,
|
|
194
194
|
readOnlyHint: false,
|
|
@@ -202,7 +202,7 @@ export const fill = definePageTool({
|
|
|
202
202
|
},
|
|
203
203
|
handler: async (request, response, context) => {
|
|
204
204
|
const page = request.page;
|
|
205
|
-
await
|
|
205
|
+
await page.waitForEventsAfterAction(async () => {
|
|
206
206
|
await fillFormElement(request.params.uid, request.params.value, context, page);
|
|
207
207
|
});
|
|
208
208
|
response.appendResponseLine(`Successfully filled out the element`);
|
|
@@ -222,9 +222,9 @@ export const typeText = definePageTool({
|
|
|
222
222
|
text: zod.string().describe('The text to type'),
|
|
223
223
|
submitKey: submitKeySchema,
|
|
224
224
|
},
|
|
225
|
-
handler: async (request, response
|
|
225
|
+
handler: async (request, response) => {
|
|
226
226
|
const page = request.page;
|
|
227
|
-
await
|
|
227
|
+
await page.waitForEventsAfterAction(async () => {
|
|
228
228
|
await page.pptrPage.keyboard.type(request.params.text);
|
|
229
229
|
if (request.params.submitKey) {
|
|
230
230
|
await page.pptrPage.keyboard.press(request.params.submitKey);
|
|
@@ -245,11 +245,11 @@ export const drag = definePageTool({
|
|
|
245
245
|
to_uid: zod.string().describe('The uid of the element to drop into'),
|
|
246
246
|
includeSnapshot: includeSnapshotSchema,
|
|
247
247
|
},
|
|
248
|
-
handler: async (request, response
|
|
248
|
+
handler: async (request, response) => {
|
|
249
249
|
const fromHandle = await request.page.getElementByUid(request.params.from_uid);
|
|
250
250
|
const toHandle = await request.page.getElementByUid(request.params.to_uid);
|
|
251
251
|
try {
|
|
252
|
-
await
|
|
252
|
+
await request.page.waitForEventsAfterAction(async () => {
|
|
253
253
|
await fromHandle.drag(toHandle);
|
|
254
254
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
255
255
|
await toHandle.drop(fromHandle);
|
|
@@ -274,7 +274,9 @@ export const fillForm = definePageTool({
|
|
|
274
274
|
},
|
|
275
275
|
schema: {
|
|
276
276
|
elements: zod
|
|
277
|
-
.array(
|
|
277
|
+
.array(
|
|
278
|
+
// eslint-disable-next-line @local/enforce-zod-schema
|
|
279
|
+
zod.object({
|
|
278
280
|
uid: zod.string().describe('The uid of the element to fill out'),
|
|
279
281
|
value: zod.string().describe('Value for the element'),
|
|
280
282
|
}))
|
|
@@ -284,7 +286,7 @@ export const fillForm = definePageTool({
|
|
|
284
286
|
handler: async (request, response, context) => {
|
|
285
287
|
const page = request.page;
|
|
286
288
|
for (const element of request.params.elements) {
|
|
287
|
-
await
|
|
289
|
+
await page.waitForEventsAfterAction(async () => {
|
|
288
290
|
await fillFormElement(element.uid, element.value, context, page);
|
|
289
291
|
});
|
|
290
292
|
}
|
|
@@ -353,11 +355,11 @@ export const pressKey = definePageTool({
|
|
|
353
355
|
.describe('A key or a combination (e.g., "Enter", "Control+A", "Control++", "Control+Shift+R"). Modifiers: Control, Shift, Alt, Meta'),
|
|
354
356
|
includeSnapshot: includeSnapshotSchema,
|
|
355
357
|
},
|
|
356
|
-
handler: async (request, response
|
|
358
|
+
handler: async (request, response) => {
|
|
357
359
|
const page = request.page;
|
|
358
360
|
const tokens = parseKey(request.params.key);
|
|
359
361
|
const [key, ...modifiers] = tokens;
|
|
360
|
-
await
|
|
362
|
+
await page.waitForEventsAfterAction(async () => {
|
|
361
363
|
for (const modifier of modifiers) {
|
|
362
364
|
await page.pptrPage.keyboard.down(modifier);
|
|
363
365
|
}
|
|
@@ -13,7 +13,7 @@ export const lighthouseAudit = definePageTool({
|
|
|
13
13
|
description: `Get Lighthouse score and reports for accessibility, SEO and best practices. This excludes performance. For performance audits, run ${startTrace.name}`,
|
|
14
14
|
annotations: {
|
|
15
15
|
category: ToolCategory.DEBUGGING,
|
|
16
|
-
readOnlyHint:
|
|
16
|
+
readOnlyHint: false,
|
|
17
17
|
},
|
|
18
18
|
schema: {
|
|
19
19
|
mode: zod
|
|
@@ -86,8 +86,8 @@ export const lighthouseAudit = definePageTool({
|
|
|
86
86
|
const report = generateReport(lhr, format);
|
|
87
87
|
const data = encoder.encode(report);
|
|
88
88
|
if (outputDirPath) {
|
|
89
|
-
const reportPath = path.join(outputDirPath, `report
|
|
90
|
-
const { filename } = await context.saveFile(data, reportPath);
|
|
89
|
+
const reportPath = path.join(outputDirPath, `report`);
|
|
90
|
+
const { filename } = await context.saveFile(data, reportPath, `.${format}`);
|
|
91
91
|
reportPaths.push(filename);
|
|
92
92
|
}
|
|
93
93
|
else {
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { ensureExtension } from '../utils/files.js';
|
|
7
8
|
import { ToolCategory } from './categories.js';
|
|
8
|
-
import { definePageTool } from './ToolDefinition.js';
|
|
9
|
+
import { definePageTool, defineTool } from './ToolDefinition.js';
|
|
9
10
|
export const takeMemorySnapshot = definePageTool({
|
|
10
11
|
name: 'take_memory_snapshot',
|
|
11
|
-
description: `Capture a
|
|
12
|
+
description: `Capture a heap snapshot of the currently selected page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.`,
|
|
12
13
|
annotations: {
|
|
13
|
-
category: ToolCategory.
|
|
14
|
-
readOnlyHint:
|
|
14
|
+
category: ToolCategory.MEMORY,
|
|
15
|
+
readOnlyHint: false,
|
|
15
16
|
},
|
|
16
17
|
schema: {
|
|
17
18
|
filePath: zod
|
|
@@ -21,8 +22,52 @@ export const takeMemorySnapshot = definePageTool({
|
|
|
21
22
|
handler: async (request, response, _context) => {
|
|
22
23
|
const page = request.page;
|
|
23
24
|
await page.pptrPage.captureHeapSnapshot({
|
|
24
|
-
path: request.params.filePath,
|
|
25
|
+
path: ensureExtension(request.params.filePath, '.heapsnapshot'),
|
|
25
26
|
});
|
|
26
27
|
response.appendResponseLine(`Heap snapshot saved to ${request.params.filePath}`);
|
|
27
28
|
},
|
|
28
29
|
});
|
|
30
|
+
export const exploreMemorySnapshot = defineTool({
|
|
31
|
+
name: 'load_memory_snapshot',
|
|
32
|
+
description: 'Loads a memory heapsnapshot and returns snapshot summary stats.',
|
|
33
|
+
annotations: {
|
|
34
|
+
category: ToolCategory.MEMORY,
|
|
35
|
+
readOnlyHint: true,
|
|
36
|
+
conditions: ['experimentalMemory'],
|
|
37
|
+
},
|
|
38
|
+
schema: {
|
|
39
|
+
filePath: zod.string().describe('A path to a .heapsnapshot file to read.'),
|
|
40
|
+
},
|
|
41
|
+
handler: async (request, response, context) => {
|
|
42
|
+
const stats = await context.getHeapSnapshotStats(request.params.filePath);
|
|
43
|
+
const staticData = await context.getHeapSnapshotStaticData(request.params.filePath);
|
|
44
|
+
response.setHeapSnapshotStats(stats, staticData);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
export const getMemorySnapshotDetails = defineTool({
|
|
48
|
+
name: 'get_memory_snapshot_details',
|
|
49
|
+
description: 'Loads a memory heapsnapshot and returns all available information including statistics, static data, and aggregated node information. Supports pagination for aggregates.',
|
|
50
|
+
annotations: {
|
|
51
|
+
category: ToolCategory.MEMORY,
|
|
52
|
+
readOnlyHint: true,
|
|
53
|
+
conditions: ['experimentalMemory'],
|
|
54
|
+
},
|
|
55
|
+
schema: {
|
|
56
|
+
filePath: zod.string().describe('A path to a .heapsnapshot file to read.'),
|
|
57
|
+
pageIdx: zod
|
|
58
|
+
.number()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('The page index for pagination of aggregates.'),
|
|
61
|
+
pageSize: zod
|
|
62
|
+
.number()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('The page size for pagination of aggregates.'),
|
|
65
|
+
},
|
|
66
|
+
handler: async (request, response, context) => {
|
|
67
|
+
const aggregates = await context.getHeapSnapshotAggregates(request.params.filePath);
|
|
68
|
+
response.setHeapSnapshotAggregates(aggregates, {
|
|
69
|
+
pageIdx: request.params.pageIdx,
|
|
70
|
+
pageSize: request.params.pageSize,
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
});
|
|
@@ -87,11 +87,11 @@ export const getNetworkRequest = definePageTool({
|
|
|
87
87
|
requestFilePath: zod
|
|
88
88
|
.string()
|
|
89
89
|
.optional()
|
|
90
|
-
.describe('The absolute or relative path to save the request body to. If omitted, the body is returned inline.'),
|
|
90
|
+
.describe('The absolute or relative path to a .network-request file to save the request body to. If omitted, the body is returned inline.'),
|
|
91
91
|
responseFilePath: zod
|
|
92
92
|
.string()
|
|
93
93
|
.optional()
|
|
94
|
-
.describe('The absolute or relative path to save the response body to. If omitted, the body is returned inline.'),
|
|
94
|
+
.describe('The absolute or relative path to a .network-response file to save the response body to. If omitted, the body is returned inline.'),
|
|
95
95
|
},
|
|
96
96
|
handler: async (request, response, context) => {
|
|
97
97
|
if (request.params.reqid) {
|
package/build/src/tools/pages.js
CHANGED
|
@@ -7,10 +7,10 @@ import { logger } from '../logger.js';
|
|
|
7
7
|
import { zod } from '../third_party/index.js';
|
|
8
8
|
import { ToolCategory } from './categories.js';
|
|
9
9
|
import { CLOSE_PAGE_ERROR, definePageTool, defineTool, timeoutSchema, } from './ToolDefinition.js';
|
|
10
|
-
export const listPages =
|
|
10
|
+
export const listPages = defineTool(args => {
|
|
11
11
|
return {
|
|
12
12
|
name: 'list_pages',
|
|
13
|
-
description: `Get a list of pages
|
|
13
|
+
description: `Get a list of pages${args?.categoryExtensions ? ' including extension service workers' : ''} open in the browser.`,
|
|
14
14
|
annotations: {
|
|
15
15
|
category: ToolCategory.NAVIGATION,
|
|
16
16
|
readOnlyHint: true,
|
|
@@ -18,6 +18,8 @@ export const listPages = definePageTool(args => {
|
|
|
18
18
|
schema: {},
|
|
19
19
|
handler: async (_request, response) => {
|
|
20
20
|
response.setIncludePages(true);
|
|
21
|
+
response.setListInPageTools();
|
|
22
|
+
response.setListWebMcpTools();
|
|
21
23
|
},
|
|
22
24
|
};
|
|
23
25
|
});
|
|
@@ -41,6 +43,8 @@ export const selectPage = defineTool({
|
|
|
41
43
|
const page = context.getPageById(request.params.pageId);
|
|
42
44
|
context.selectPage(page);
|
|
43
45
|
response.setIncludePages(true);
|
|
46
|
+
response.setListInPageTools();
|
|
47
|
+
response.setListWebMcpTools();
|
|
44
48
|
if (request.params.bringToFront) {
|
|
45
49
|
await page.pptrPage.bringToFront();
|
|
46
50
|
}
|
|
@@ -71,6 +75,7 @@ export const closePage = defineTool({
|
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
response.setIncludePages(true);
|
|
78
|
+
response.setListInPageTools();
|
|
74
79
|
},
|
|
75
80
|
});
|
|
76
81
|
export const newPage = defineTool({
|
|
@@ -96,12 +101,13 @@ export const newPage = defineTool({
|
|
|
96
101
|
},
|
|
97
102
|
handler: async (request, response, context) => {
|
|
98
103
|
const page = await context.newPage(request.params.background, request.params.isolatedContext);
|
|
99
|
-
await
|
|
104
|
+
await page.waitForEventsAfterAction(async () => {
|
|
100
105
|
await page.pptrPage.goto(request.params.url, {
|
|
101
106
|
timeout: request.params.timeout,
|
|
102
107
|
});
|
|
103
108
|
}, { timeout: request.params.timeout });
|
|
104
109
|
response.setIncludePages(true);
|
|
110
|
+
response.setListInPageTools();
|
|
105
111
|
},
|
|
106
112
|
});
|
|
107
113
|
export const navigatePage = definePageTool({
|
|
@@ -131,7 +137,7 @@ export const navigatePage = definePageTool({
|
|
|
131
137
|
.describe('A JavaScript script to be executed on each new document before any other scripts for the next navigation.'),
|
|
132
138
|
...timeoutSchema,
|
|
133
139
|
},
|
|
134
|
-
handler: async (request, response
|
|
140
|
+
handler: async (request, response) => {
|
|
135
141
|
const page = request.page;
|
|
136
142
|
const options = {
|
|
137
143
|
timeout: request.params.timeout,
|
|
@@ -164,7 +170,7 @@ export const navigatePage = definePageTool({
|
|
|
164
170
|
}
|
|
165
171
|
page.pptrPage.on('dialog', dialogHandler);
|
|
166
172
|
try {
|
|
167
|
-
await
|
|
173
|
+
await page.waitForEventsAfterAction(async () => {
|
|
168
174
|
switch (request.params.type) {
|
|
169
175
|
case 'url':
|
|
170
176
|
if (!request.params.url) {
|
|
@@ -175,7 +181,7 @@ export const navigatePage = definePageTool({
|
|
|
175
181
|
response.appendResponseLine(`Successfully navigated to ${request.params.url}.`);
|
|
176
182
|
}
|
|
177
183
|
catch (error) {
|
|
178
|
-
response.appendResponseLine(`Unable to navigate in the
|
|
184
|
+
response.appendResponseLine(`Unable to navigate in the selected page: ${error.message}.`);
|
|
179
185
|
}
|
|
180
186
|
break;
|
|
181
187
|
case 'back':
|
|
@@ -222,6 +228,8 @@ export const navigatePage = definePageTool({
|
|
|
222
228
|
}
|
|
223
229
|
}
|
|
224
230
|
response.setIncludePages(true);
|
|
231
|
+
response.setListInPageTools();
|
|
232
|
+
response.setListWebMcpTools();
|
|
225
233
|
},
|
|
226
234
|
});
|
|
227
235
|
export const resizePage = definePageTool({
|
|
@@ -142,7 +142,7 @@ async function stopTracingAndAppendOutput(page, response, context, filePath) {
|
|
|
142
142
|
});
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
|
-
const file = await context.saveFile(dataToWrite, filePath);
|
|
145
|
+
const file = await context.saveFile(dataToWrite, filePath, filePath.endsWith('.gz') ? '.json.gz' : '.json');
|
|
146
146
|
response.appendResponseLine(`The raw trace data was saved to ${file.filename}.`);
|
|
147
147
|
}
|
|
148
148
|
const result = await parseRawTraceBuffer(traceEventsBuffer);
|
|
@@ -7,6 +7,7 @@ import fs from 'node:fs/promises';
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { zod } from '../third_party/index.js';
|
|
10
|
+
import { ensureExtension } from '../utils/files.js';
|
|
10
11
|
import { ToolCategory } from './categories.js';
|
|
11
12
|
import { definePageTool } from './ToolDefinition.js';
|
|
12
13
|
async function generateTempFilePath() {
|
|
@@ -33,7 +34,7 @@ export const startScreencast = definePageTool({
|
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
35
36
|
const filePath = request.params.path ?? (await generateTempFilePath());
|
|
36
|
-
const resolvedPath = path.resolve(filePath);
|
|
37
|
+
const resolvedPath = ensureExtension(path.resolve(filePath), '.mp4');
|
|
37
38
|
const page = request.page;
|
|
38
39
|
let recorder;
|
|
39
40
|
try {
|
|
@@ -28,7 +28,7 @@ export const screenshot = definePageTool({
|
|
|
28
28
|
uid: zod
|
|
29
29
|
.string()
|
|
30
30
|
.optional()
|
|
31
|
-
.describe('The uid of an element on the page from the page content snapshot. If omitted takes a
|
|
31
|
+
.describe('The uid of an element on the page from the page content snapshot. If omitted, takes a page screenshot.'),
|
|
32
32
|
fullPage: zod
|
|
33
33
|
.boolean()
|
|
34
34
|
.optional()
|
|
@@ -67,8 +67,8 @@ export const screenshot = definePageTool({
|
|
|
67
67
|
response.appendResponseLine("Took a screenshot of the current page's viewport.");
|
|
68
68
|
}
|
|
69
69
|
if (request.params.filePath) {
|
|
70
|
-
const
|
|
71
|
-
response.appendResponseLine(`Saved screenshot to ${
|
|
70
|
+
const result = await context.saveFile(screenshot, request.params.filePath, `.${format}`);
|
|
71
|
+
response.appendResponseLine(`Saved screenshot to ${result.filename}.`);
|
|
72
72
|
}
|
|
73
73
|
else if (screenshot.length >= 2_000_000) {
|
|
74
74
|
const { filepath } = await context.saveTemporaryFile(screenshot, `screenshot.${request.params.format}`);
|