chrome-devtools-mcp 0.18.1 → 0.19.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 +4 -3
- package/build/src/McpContext.js +215 -266
- package/build/src/McpPage.js +95 -0
- package/build/src/McpResponse.js +73 -29
- package/build/src/bin/chrome-devtools.js +184 -0
- package/build/src/bin/cliDefinitions.js +651 -0
- package/build/src/browser.js +5 -3
- package/build/src/cli.js +11 -1
- package/build/src/daemon/client.js +151 -0
- package/build/src/daemon/daemon.js +49 -15
- package/build/src/daemon/types.js +6 -0
- package/build/src/daemon/utils.js +56 -15
- package/build/src/main.js +5 -173
- package/build/src/server.js +209 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +2 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +1480 -111
- package/build/src/third_party/bundled-packages.json +4 -3
- package/build/src/third_party/devtools-formatter-worker.js +5 -13
- package/build/src/third_party/index.js +1980 -396
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +54183 -0
- package/build/src/tools/ToolDefinition.js +52 -0
- package/build/src/tools/console.js +3 -3
- package/build/src/tools/emulation.js +13 -45
- package/build/src/tools/extensions.js +17 -0
- package/build/src/tools/input.js +33 -33
- package/build/src/tools/lighthouse.js +123 -0
- package/build/src/tools/memory.js +5 -5
- package/build/src/tools/network.js +7 -7
- package/build/src/tools/pages.js +32 -32
- package/build/src/tools/performance.js +16 -14
- package/build/src/tools/screencast.js +5 -5
- package/build/src/tools/screenshot.js +6 -6
- package/build/src/tools/script.js +99 -49
- package/build/src/tools/slim/tools.js +18 -18
- package/build/src/tools/snapshot.js +5 -4
- package/build/src/tools/tools.js +2 -0
- package/build/src/types.js +6 -0
- package/build/src/utils/files.js +19 -0
- package/build/src/version.js +1 -1
- package/package.json +11 -8
package/build/src/tools/pages.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { logger } from '../logger.js';
|
|
7
7
|
import { zod } from '../third_party/index.js';
|
|
8
8
|
import { ToolCategory } from './categories.js';
|
|
9
|
-
import { CLOSE_PAGE_ERROR, defineTool, timeoutSchema } from './ToolDefinition.js';
|
|
10
|
-
export const listPages =
|
|
9
|
+
import { CLOSE_PAGE_ERROR, definePageTool, defineTool, timeoutSchema, } from './ToolDefinition.js';
|
|
10
|
+
export const listPages = definePageTool(args => {
|
|
11
11
|
return {
|
|
12
12
|
name: 'list_pages',
|
|
13
13
|
description: `Get a list of pages ${args?.categoryExtensions ? 'including extension service workers' : ''} open in the browser.`,
|
|
@@ -42,7 +42,7 @@ export const selectPage = defineTool({
|
|
|
42
42
|
context.selectPage(page);
|
|
43
43
|
response.setIncludePages(true);
|
|
44
44
|
if (request.params.bringToFront) {
|
|
45
|
-
await page.bringToFront();
|
|
45
|
+
await page.pptrPage.bringToFront();
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
});
|
|
@@ -75,7 +75,7 @@ export const closePage = defineTool({
|
|
|
75
75
|
});
|
|
76
76
|
export const newPage = defineTool({
|
|
77
77
|
name: 'new_page',
|
|
78
|
-
description: `
|
|
78
|
+
description: `Open a new tab and load a URL. Use project URL if not specified otherwise.`,
|
|
79
79
|
annotations: {
|
|
80
80
|
category: ToolCategory.NAVIGATION,
|
|
81
81
|
readOnlyHint: false,
|
|
@@ -97,16 +97,16 @@ export const newPage = defineTool({
|
|
|
97
97
|
handler: async (request, response, context) => {
|
|
98
98
|
const page = await context.newPage(request.params.background, request.params.isolatedContext);
|
|
99
99
|
await context.waitForEventsAfterAction(async () => {
|
|
100
|
-
await page.goto(request.params.url, {
|
|
100
|
+
await page.pptrPage.goto(request.params.url, {
|
|
101
101
|
timeout: request.params.timeout,
|
|
102
102
|
});
|
|
103
103
|
}, { timeout: request.params.timeout });
|
|
104
104
|
response.setIncludePages(true);
|
|
105
105
|
},
|
|
106
106
|
});
|
|
107
|
-
export const navigatePage =
|
|
107
|
+
export const navigatePage = definePageTool({
|
|
108
108
|
name: 'navigate_page',
|
|
109
|
-
description: `
|
|
109
|
+
description: `Go to a URL, or back, forward, or reload. Use project URL if not specified otherwise.`,
|
|
110
110
|
annotations: {
|
|
111
111
|
category: ToolCategory.NAVIGATION,
|
|
112
112
|
readOnlyHint: false,
|
|
@@ -132,7 +132,7 @@ export const navigatePage = defineTool({
|
|
|
132
132
|
...timeoutSchema,
|
|
133
133
|
},
|
|
134
134
|
handler: async (request, response, context) => {
|
|
135
|
-
const page =
|
|
135
|
+
const page = request.page;
|
|
136
136
|
const options = {
|
|
137
137
|
timeout: request.params.timeout,
|
|
138
138
|
};
|
|
@@ -154,15 +154,15 @@ export const navigatePage = defineTool({
|
|
|
154
154
|
void dialog.dismiss();
|
|
155
155
|
}
|
|
156
156
|
// We are not going to report the dialog like regular dialogs.
|
|
157
|
-
|
|
157
|
+
page.clearDialog();
|
|
158
158
|
}
|
|
159
159
|
};
|
|
160
160
|
let initScriptId;
|
|
161
161
|
if (request.params.initScript) {
|
|
162
|
-
const { identifier } = await page.evaluateOnNewDocument(request.params.initScript);
|
|
162
|
+
const { identifier } = await page.pptrPage.evaluateOnNewDocument(request.params.initScript);
|
|
163
163
|
initScriptId = identifier;
|
|
164
164
|
}
|
|
165
|
-
page.on('dialog', dialogHandler);
|
|
165
|
+
page.pptrPage.on('dialog', dialogHandler);
|
|
166
166
|
try {
|
|
167
167
|
await context.waitForEventsAfterAction(async () => {
|
|
168
168
|
switch (request.params.type) {
|
|
@@ -171,7 +171,7 @@ export const navigatePage = defineTool({
|
|
|
171
171
|
throw new Error('A URL is required for navigation of type=url.');
|
|
172
172
|
}
|
|
173
173
|
try {
|
|
174
|
-
await page.goto(request.params.url, options);
|
|
174
|
+
await page.pptrPage.goto(request.params.url, options);
|
|
175
175
|
response.appendResponseLine(`Successfully navigated to ${request.params.url}.`);
|
|
176
176
|
}
|
|
177
177
|
catch (error) {
|
|
@@ -180,8 +180,8 @@ export const navigatePage = defineTool({
|
|
|
180
180
|
break;
|
|
181
181
|
case 'back':
|
|
182
182
|
try {
|
|
183
|
-
await page.goBack(options);
|
|
184
|
-
response.appendResponseLine(`Successfully navigated back to ${page.url()}.`);
|
|
183
|
+
await page.pptrPage.goBack(options);
|
|
184
|
+
response.appendResponseLine(`Successfully navigated back to ${page.pptrPage.url()}.`);
|
|
185
185
|
}
|
|
186
186
|
catch (error) {
|
|
187
187
|
response.appendResponseLine(`Unable to navigate back in the selected page: ${error.message}.`);
|
|
@@ -189,8 +189,8 @@ export const navigatePage = defineTool({
|
|
|
189
189
|
break;
|
|
190
190
|
case 'forward':
|
|
191
191
|
try {
|
|
192
|
-
await page.goForward(options);
|
|
193
|
-
response.appendResponseLine(`Successfully navigated forward to ${page.url()}.`);
|
|
192
|
+
await page.pptrPage.goForward(options);
|
|
193
|
+
response.appendResponseLine(`Successfully navigated forward to ${page.pptrPage.url()}.`);
|
|
194
194
|
}
|
|
195
195
|
catch (error) {
|
|
196
196
|
response.appendResponseLine(`Unable to navigate forward in the selected page: ${error.message}.`);
|
|
@@ -198,7 +198,7 @@ export const navigatePage = defineTool({
|
|
|
198
198
|
break;
|
|
199
199
|
case 'reload':
|
|
200
200
|
try {
|
|
201
|
-
await page.reload({
|
|
201
|
+
await page.pptrPage.reload({
|
|
202
202
|
...options,
|
|
203
203
|
ignoreCache: request.params.ignoreCache,
|
|
204
204
|
});
|
|
@@ -212,9 +212,9 @@ export const navigatePage = defineTool({
|
|
|
212
212
|
}, { timeout: request.params.timeout });
|
|
213
213
|
}
|
|
214
214
|
finally {
|
|
215
|
-
page.off('dialog', dialogHandler);
|
|
215
|
+
page.pptrPage.off('dialog', dialogHandler);
|
|
216
216
|
if (initScriptId) {
|
|
217
|
-
await page
|
|
217
|
+
await page.pptrPage
|
|
218
218
|
.removeScriptToEvaluateOnNewDocument(initScriptId)
|
|
219
219
|
.catch(error => {
|
|
220
220
|
logger(`Failed to remove init script`, error);
|
|
@@ -224,7 +224,7 @@ export const navigatePage = defineTool({
|
|
|
224
224
|
response.setIncludePages(true);
|
|
225
225
|
},
|
|
226
226
|
});
|
|
227
|
-
export const resizePage =
|
|
227
|
+
export const resizePage = definePageTool({
|
|
228
228
|
name: 'resize_page',
|
|
229
229
|
description: `Resizes the selected page's window so that the page has specified dimension`,
|
|
230
230
|
annotations: {
|
|
@@ -235,11 +235,11 @@ export const resizePage = defineTool({
|
|
|
235
235
|
width: zod.number().describe('Page width'),
|
|
236
236
|
height: zod.number().describe('Page height'),
|
|
237
237
|
},
|
|
238
|
-
handler: async (request, response,
|
|
239
|
-
const page =
|
|
238
|
+
handler: async (request, response, _context) => {
|
|
239
|
+
const page = request.page;
|
|
240
240
|
try {
|
|
241
|
-
const browser = page.browser();
|
|
242
|
-
const windowId = await page.windowId();
|
|
241
|
+
const browser = page.pptrPage.browser();
|
|
242
|
+
const windowId = await page.pptrPage.windowId();
|
|
243
243
|
const bounds = await browser.getWindowBounds(windowId);
|
|
244
244
|
if (bounds.windowState === 'fullscreen') {
|
|
245
245
|
// Have to call this twice on Ubuntu when the window is in fullscreen mode.
|
|
@@ -253,14 +253,14 @@ export const resizePage = defineTool({
|
|
|
253
253
|
catch {
|
|
254
254
|
// Window APIs are not supported on all platforms
|
|
255
255
|
}
|
|
256
|
-
await page.resize({
|
|
256
|
+
await page.pptrPage.resize({
|
|
257
257
|
contentWidth: request.params.width,
|
|
258
258
|
contentHeight: request.params.height,
|
|
259
259
|
});
|
|
260
260
|
response.setIncludePages(true);
|
|
261
261
|
},
|
|
262
262
|
});
|
|
263
|
-
export const handleDialog =
|
|
263
|
+
export const handleDialog = definePageTool({
|
|
264
264
|
name: 'handle_dialog',
|
|
265
265
|
description: `If a browser dialog was opened, use this command to handle it`,
|
|
266
266
|
annotations: {
|
|
@@ -276,8 +276,9 @@ export const handleDialog = defineTool({
|
|
|
276
276
|
.optional()
|
|
277
277
|
.describe('Optional prompt text to enter into the dialog.'),
|
|
278
278
|
},
|
|
279
|
-
handler: async (request, response,
|
|
280
|
-
const
|
|
279
|
+
handler: async (request, response, _context) => {
|
|
280
|
+
const page = request.page;
|
|
281
|
+
const dialog = page.getDialog();
|
|
281
282
|
if (!dialog) {
|
|
282
283
|
throw new Error('No open dialog found');
|
|
283
284
|
}
|
|
@@ -305,11 +306,11 @@ export const handleDialog = defineTool({
|
|
|
305
306
|
break;
|
|
306
307
|
}
|
|
307
308
|
}
|
|
308
|
-
|
|
309
|
+
page.clearDialog();
|
|
309
310
|
response.setIncludePages(true);
|
|
310
311
|
},
|
|
311
312
|
});
|
|
312
|
-
export const getTabId =
|
|
313
|
+
export const getTabId = definePageTool({
|
|
313
314
|
name: 'get_tab_id',
|
|
314
315
|
description: `Get the tab ID of the page`,
|
|
315
316
|
annotations: {
|
|
@@ -324,8 +325,7 @@ export const getTabId = defineTool({
|
|
|
324
325
|
},
|
|
325
326
|
handler: async (request, response, context) => {
|
|
326
327
|
const page = context.getPageById(request.params.pageId);
|
|
327
|
-
|
|
328
|
-
const tabId = page._tabId;
|
|
328
|
+
const tabId = page.pptrPage._tabId;
|
|
329
329
|
response.setTabId(tabId);
|
|
330
330
|
},
|
|
331
331
|
});
|
|
@@ -8,14 +8,14 @@ import { logger } from '../logger.js';
|
|
|
8
8
|
import { zod, DevTools } from '../third_party/index.js';
|
|
9
9
|
import { parseRawTraceBuffer, traceResultIsSuccess, } from '../trace-processing/parse.js';
|
|
10
10
|
import { ToolCategory } from './categories.js';
|
|
11
|
-
import {
|
|
11
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
12
12
|
const filePathSchema = zod
|
|
13
13
|
.string()
|
|
14
14
|
.optional()
|
|
15
15
|
.describe('The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed).');
|
|
16
|
-
export const startTrace =
|
|
16
|
+
export const startTrace = definePageTool({
|
|
17
17
|
name: 'performance_start_trace',
|
|
18
|
-
description: `
|
|
18
|
+
description: `Start a performance trace on the selected webpage. Use to find frontend performance issues, Core Web Vitals (LCP, INP, CLS), and improve page load speed.`,
|
|
19
19
|
annotations: {
|
|
20
20
|
category: ToolCategory.PERFORMANCE,
|
|
21
21
|
readOnlyHint: false,
|
|
@@ -23,9 +23,11 @@ export const startTrace = defineTool({
|
|
|
23
23
|
schema: {
|
|
24
24
|
reload: zod
|
|
25
25
|
.boolean()
|
|
26
|
+
.default(true)
|
|
26
27
|
.describe('Determines if, once tracing has started, the current selected page should be automatically reloaded. Navigate the page to the right URL using the navigate_page tool BEFORE starting the trace if reload or autoStop is set to true.'),
|
|
27
28
|
autoStop: zod
|
|
28
29
|
.boolean()
|
|
30
|
+
.default(true)
|
|
29
31
|
.describe('Determines if the trace recording should be automatically stopped.'),
|
|
30
32
|
filePath: filePathSchema,
|
|
31
33
|
},
|
|
@@ -35,11 +37,11 @@ export const startTrace = defineTool({
|
|
|
35
37
|
return;
|
|
36
38
|
}
|
|
37
39
|
context.setIsRunningPerformanceTrace(true);
|
|
38
|
-
const page =
|
|
39
|
-
const pageUrlForTracing = page.url();
|
|
40
|
+
const page = request.page;
|
|
41
|
+
const pageUrlForTracing = page.pptrPage.url();
|
|
40
42
|
if (request.params.reload) {
|
|
41
43
|
// Before starting the recording, navigate to about:blank to clear out any state.
|
|
42
|
-
await page.goto('about:blank', {
|
|
44
|
+
await page.pptrPage.goto('about:blank', {
|
|
43
45
|
waitUntil: ['networkidle0'],
|
|
44
46
|
});
|
|
45
47
|
}
|
|
@@ -64,26 +66,26 @@ export const startTrace = defineTool({
|
|
|
64
66
|
'v8.execute',
|
|
65
67
|
'v8',
|
|
66
68
|
];
|
|
67
|
-
await page.tracing.start({
|
|
69
|
+
await page.pptrPage.tracing.start({
|
|
68
70
|
categories,
|
|
69
71
|
});
|
|
70
72
|
if (request.params.reload) {
|
|
71
|
-
await page.goto(pageUrlForTracing, {
|
|
73
|
+
await page.pptrPage.goto(pageUrlForTracing, {
|
|
72
74
|
waitUntil: ['load'],
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
77
|
if (request.params.autoStop) {
|
|
76
78
|
await new Promise(resolve => setTimeout(resolve, 5_000));
|
|
77
|
-
await stopTracingAndAppendOutput(page, response, context, request.params.filePath);
|
|
79
|
+
await stopTracingAndAppendOutput(page.pptrPage, response, context, request.params.filePath);
|
|
78
80
|
}
|
|
79
81
|
else {
|
|
80
82
|
response.appendResponseLine(`The performance trace is being recorded. Use performance_stop_trace to stop it.`);
|
|
81
83
|
}
|
|
82
84
|
},
|
|
83
85
|
});
|
|
84
|
-
export const stopTrace =
|
|
86
|
+
export const stopTrace = definePageTool({
|
|
85
87
|
name: 'performance_stop_trace',
|
|
86
|
-
description: '
|
|
88
|
+
description: 'Stop the active performance trace recording on the selected webpage.',
|
|
87
89
|
annotations: {
|
|
88
90
|
category: ToolCategory.PERFORMANCE,
|
|
89
91
|
readOnlyHint: false,
|
|
@@ -95,11 +97,11 @@ export const stopTrace = defineTool({
|
|
|
95
97
|
if (!context.isRunningPerformanceTrace()) {
|
|
96
98
|
return;
|
|
97
99
|
}
|
|
98
|
-
const page =
|
|
99
|
-
await stopTracingAndAppendOutput(page, response, context, request.params.filePath);
|
|
100
|
+
const page = request.page;
|
|
101
|
+
await stopTracingAndAppendOutput(page.pptrPage, response, context, request.params.filePath);
|
|
100
102
|
},
|
|
101
103
|
});
|
|
102
|
-
export const analyzeInsight =
|
|
104
|
+
export const analyzeInsight = definePageTool({
|
|
103
105
|
name: 'performance_analyze_insight',
|
|
104
106
|
description: 'Provides more detailed information on a specific Performance Insight of an insight set that was highlighted in the results of a trace recording.',
|
|
105
107
|
annotations: {
|
|
@@ -8,12 +8,12 @@ import os from 'node:os';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { zod } from '../third_party/index.js';
|
|
10
10
|
import { ToolCategory } from './categories.js';
|
|
11
|
-
import {
|
|
11
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
12
12
|
async function generateTempFilePath() {
|
|
13
13
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'chrome-devtools-mcp-'));
|
|
14
14
|
return path.join(dir, `screencast.mp4`);
|
|
15
15
|
}
|
|
16
|
-
export const startScreencast =
|
|
16
|
+
export const startScreencast = definePageTool({
|
|
17
17
|
name: 'screencast_start',
|
|
18
18
|
description: 'Starts recording a screencast (video) of the selected page in mp4 format.',
|
|
19
19
|
annotations: {
|
|
@@ -34,10 +34,10 @@ export const startScreencast = defineTool({
|
|
|
34
34
|
}
|
|
35
35
|
const filePath = request.params.path ?? (await generateTempFilePath());
|
|
36
36
|
const resolvedPath = path.resolve(filePath);
|
|
37
|
-
const page =
|
|
37
|
+
const page = request.page;
|
|
38
38
|
let recorder;
|
|
39
39
|
try {
|
|
40
|
-
recorder = await page.screencast({
|
|
40
|
+
recorder = await page.pptrPage.screencast({
|
|
41
41
|
path: resolvedPath,
|
|
42
42
|
format: 'mp4',
|
|
43
43
|
});
|
|
@@ -54,7 +54,7 @@ export const startScreencast = defineTool({
|
|
|
54
54
|
response.appendResponseLine(`Screencast recording started. The recording will be saved to ${resolvedPath}. Use ${stopScreencast.name} to stop recording.`);
|
|
55
55
|
},
|
|
56
56
|
});
|
|
57
|
-
export const stopScreencast =
|
|
57
|
+
export const stopScreencast = definePageTool({
|
|
58
58
|
name: 'screencast_stop',
|
|
59
59
|
description: 'Stops the active screencast recording on the selected page.',
|
|
60
60
|
annotations: {
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
7
|
import { ToolCategory } from './categories.js';
|
|
8
|
-
import {
|
|
9
|
-
export const screenshot =
|
|
8
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
9
|
+
export const screenshot = definePageTool({
|
|
10
10
|
name: 'take_screenshot',
|
|
11
11
|
description: `Take a screenshot of the page or element.`,
|
|
12
12
|
annotations: {
|
|
@@ -44,10 +44,10 @@ export const screenshot = defineTool({
|
|
|
44
44
|
}
|
|
45
45
|
let pageOrHandle;
|
|
46
46
|
if (request.params.uid) {
|
|
47
|
-
pageOrHandle = await
|
|
47
|
+
pageOrHandle = await request.page.getElementByUid(request.params.uid);
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
|
-
pageOrHandle =
|
|
50
|
+
pageOrHandle = request.page.pptrPage;
|
|
51
51
|
}
|
|
52
52
|
const format = request.params.format;
|
|
53
53
|
const quality = format === 'png' ? undefined : request.params.quality;
|
|
@@ -71,8 +71,8 @@ export const screenshot = defineTool({
|
|
|
71
71
|
response.appendResponseLine(`Saved screenshot to ${file.filename}.`);
|
|
72
72
|
}
|
|
73
73
|
else if (screenshot.length >= 2_000_000) {
|
|
74
|
-
const {
|
|
75
|
-
response.appendResponseLine(`Saved screenshot to ${
|
|
74
|
+
const { filepath } = await context.saveTemporaryFile(screenshot, `screenshot.${request.params.format}`);
|
|
75
|
+
response.appendResponseLine(`Saved screenshot to ${filepath}.`);
|
|
76
76
|
}
|
|
77
77
|
else {
|
|
78
78
|
response.attachImage({
|
|
@@ -5,17 +5,18 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
7
|
import { ToolCategory } from './categories.js';
|
|
8
|
-
import { defineTool } from './ToolDefinition.js';
|
|
9
|
-
export const evaluateScript = defineTool({
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import { defineTool, pageIdSchema } from './ToolDefinition.js';
|
|
9
|
+
export const evaluateScript = defineTool(cliArgs => {
|
|
10
|
+
return {
|
|
11
|
+
name: 'evaluate_script',
|
|
12
|
+
description: `Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON,
|
|
12
13
|
so returned values have to be JSON-serializable.`,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
annotations: {
|
|
15
|
+
category: ToolCategory.DEBUGGING,
|
|
16
|
+
readOnlyHint: false,
|
|
17
|
+
},
|
|
18
|
+
schema: {
|
|
19
|
+
function: zod.string().describe(`A JavaScript function declaration to be executed by the tool in the currently selected page.
|
|
19
20
|
Example without arguments: \`() => {
|
|
20
21
|
return document.title
|
|
21
22
|
}\` or \`async () => {
|
|
@@ -25,47 +26,96 @@ Example with arguments: \`(el) => {
|
|
|
25
26
|
return el.innerText;
|
|
26
27
|
}\`
|
|
27
28
|
`),
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
uid: zod
|
|
29
|
+
args: zod
|
|
30
|
+
.array(zod
|
|
31
31
|
.string()
|
|
32
|
-
.describe('The uid of an element on the page from the page content snapshot')
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
.describe('The uid of an element on the page from the page content snapshot'))
|
|
33
|
+
.optional()
|
|
34
|
+
.describe(`An optional list of arguments to pass to the function.`),
|
|
35
|
+
...(cliArgs?.experimentalPageIdRouting ? pageIdSchema : {}),
|
|
36
|
+
...(cliArgs?.categoryExtensions
|
|
37
|
+
? {
|
|
38
|
+
serviceWorkerId: zod
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe(`An optional service worker id to evaluate the script in.`),
|
|
42
|
+
}
|
|
43
|
+
: {}),
|
|
44
|
+
},
|
|
45
|
+
handler: async (request, response, context) => {
|
|
46
|
+
const { serviceWorkerId, args: uidArgs, function: fnString, pageId, } = request.params;
|
|
47
|
+
if (cliArgs?.categoryExtensions && serviceWorkerId) {
|
|
48
|
+
if (uidArgs && uidArgs.length > 0) {
|
|
49
|
+
throw new Error('args (element uids) cannot be used when evaluating in a service worker.');
|
|
50
|
+
}
|
|
51
|
+
if (pageId) {
|
|
52
|
+
throw new Error('specify either a pageId or a serviceWorkerId.');
|
|
53
|
+
}
|
|
54
|
+
const worker = await getWebWorker(context, serviceWorkerId);
|
|
55
|
+
await performEvaluation(worker, fnString, [], response, context);
|
|
56
|
+
return;
|
|
45
57
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
const mcpPage = cliArgs?.experimentalPageIdRouting
|
|
59
|
+
? context.getPageById(request.params.pageId)
|
|
60
|
+
: context.getSelectedMcpPage();
|
|
61
|
+
const page = mcpPage.pptrPage;
|
|
62
|
+
const args = [];
|
|
63
|
+
try {
|
|
64
|
+
const frames = new Set();
|
|
65
|
+
for (const uid of uidArgs ?? []) {
|
|
66
|
+
const handle = await mcpPage.getElementByUid(uid);
|
|
67
|
+
frames.add(handle.frame);
|
|
68
|
+
args.push(handle);
|
|
69
|
+
}
|
|
70
|
+
const evaluatable = await getPageOrFrame(page, frames);
|
|
71
|
+
await performEvaluation(evaluatable, fnString, args, response, context);
|
|
50
72
|
}
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
finally {
|
|
74
|
+
void Promise.allSettled(args.map(arg => arg.dispose()));
|
|
53
75
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
await context.waitForEventsAfterAction(async () => {
|
|
57
|
-
const result = await pageOrFrame.evaluate(async (fn, ...args) => {
|
|
58
|
-
// @ts-expect-error no types.
|
|
59
|
-
return JSON.stringify(await fn(...args));
|
|
60
|
-
}, ...args);
|
|
61
|
-
response.appendResponseLine('Script ran on page and returned:');
|
|
62
|
-
response.appendResponseLine('```json');
|
|
63
|
-
response.appendResponseLine(`${result}`);
|
|
64
|
-
response.appendResponseLine('```');
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
finally {
|
|
68
|
-
void Promise.allSettled(args.map(arg => arg.dispose()));
|
|
69
|
-
}
|
|
70
|
-
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
71
78
|
});
|
|
79
|
+
const performEvaluation = async (evaluatable, fnString, args, response, context) => {
|
|
80
|
+
const fn = await evaluatable.evaluateHandle(`(${fnString})`);
|
|
81
|
+
try {
|
|
82
|
+
await context.waitForEventsAfterAction(async () => {
|
|
83
|
+
const result = await evaluatable.evaluate(async (fn, ...args) => {
|
|
84
|
+
// @ts-expect-error no types for function fn
|
|
85
|
+
return JSON.stringify(await fn(...args));
|
|
86
|
+
}, fn, ...args);
|
|
87
|
+
response.appendResponseLine('Script ran on page and returned:');
|
|
88
|
+
response.appendResponseLine('```json');
|
|
89
|
+
response.appendResponseLine(`${result}`);
|
|
90
|
+
response.appendResponseLine('```');
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
void fn.dispose();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const getPageOrFrame = async (page, frames) => {
|
|
98
|
+
let pageOrFrame;
|
|
99
|
+
// We can't evaluate the element handle across frames
|
|
100
|
+
if (frames.size > 1) {
|
|
101
|
+
throw new Error("Elements from different frames can't be evaluated together.");
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
pageOrFrame = [...frames.values()][0] ?? page;
|
|
105
|
+
}
|
|
106
|
+
return pageOrFrame;
|
|
107
|
+
};
|
|
108
|
+
const getWebWorker = async (context, serviceWorkerId) => {
|
|
109
|
+
const serviceWorkers = context.getExtensionServiceWorkers();
|
|
110
|
+
const serviceWorker = serviceWorkers.find((sw) => context.getExtensionServiceWorkerId(sw) === serviceWorkerId);
|
|
111
|
+
if (serviceWorker && serviceWorker.target) {
|
|
112
|
+
const worker = await serviceWorker.target.worker();
|
|
113
|
+
if (!worker) {
|
|
114
|
+
throw new Error('Service worker target not found.');
|
|
115
|
+
}
|
|
116
|
+
return worker;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
throw new Error('Service worker not found.');
|
|
120
|
+
}
|
|
121
|
+
};
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../../third_party/index.js';
|
|
7
7
|
import { ToolCategory } from '../categories.js';
|
|
8
|
-
import {
|
|
9
|
-
export const screenshot =
|
|
8
|
+
import { definePageTool } from '../ToolDefinition.js';
|
|
9
|
+
export const screenshot = definePageTool({
|
|
10
10
|
name: 'screenshot',
|
|
11
11
|
description: `Takes a screenshot`,
|
|
12
12
|
annotations: {
|
|
@@ -16,16 +16,16 @@ export const screenshot = defineTool({
|
|
|
16
16
|
},
|
|
17
17
|
schema: {},
|
|
18
18
|
handler: async (request, response, context) => {
|
|
19
|
-
const page =
|
|
20
|
-
const screenshot = await page.screenshot({
|
|
19
|
+
const page = request.page;
|
|
20
|
+
const screenshot = await page.pptrPage.screenshot({
|
|
21
21
|
type: 'png',
|
|
22
22
|
optimizeForSpeed: true,
|
|
23
23
|
});
|
|
24
|
-
const {
|
|
25
|
-
response.appendResponseLine(
|
|
24
|
+
const { filepath } = await context.saveTemporaryFile(screenshot, `screenshot.png`);
|
|
25
|
+
response.appendResponseLine(filepath);
|
|
26
26
|
},
|
|
27
27
|
});
|
|
28
|
-
export const navigate =
|
|
28
|
+
export const navigate = definePageTool({
|
|
29
29
|
name: 'navigate',
|
|
30
30
|
description: `Loads a URL`,
|
|
31
31
|
annotations: {
|
|
@@ -35,8 +35,8 @@ export const navigate = defineTool({
|
|
|
35
35
|
schema: {
|
|
36
36
|
url: zod.string().describe('URL to navigate to'),
|
|
37
37
|
},
|
|
38
|
-
handler: async (request, response
|
|
39
|
-
const page =
|
|
38
|
+
handler: async (request, response) => {
|
|
39
|
+
const page = request.page;
|
|
40
40
|
const options = {
|
|
41
41
|
timeout: 30_000,
|
|
42
42
|
};
|
|
@@ -45,20 +45,20 @@ export const navigate = defineTool({
|
|
|
45
45
|
response.appendResponseLine(`Accepted a beforeunload dialog.`);
|
|
46
46
|
void dialog.accept();
|
|
47
47
|
// We are not going to report the dialog like regular dialogs.
|
|
48
|
-
|
|
48
|
+
page.clearDialog();
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
-
page.on('dialog', dialogHandler);
|
|
51
|
+
page.pptrPage.on('dialog', dialogHandler);
|
|
52
52
|
try {
|
|
53
|
-
await page.goto(request.params.url, options);
|
|
54
|
-
response.appendResponseLine(`Navigated to ${page.url()}.`);
|
|
53
|
+
await page.pptrPage.goto(request.params.url, options);
|
|
54
|
+
response.appendResponseLine(`Navigated to ${page.pptrPage.url()}.`);
|
|
55
55
|
}
|
|
56
56
|
finally {
|
|
57
|
-
page.off('dialog', dialogHandler);
|
|
57
|
+
page.pptrPage.off('dialog', dialogHandler);
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
});
|
|
61
|
-
export const evaluate =
|
|
61
|
+
export const evaluate = definePageTool({
|
|
62
62
|
name: 'evaluate',
|
|
63
63
|
description: `Evaluates a JavaScript script`,
|
|
64
64
|
annotations: {
|
|
@@ -68,10 +68,10 @@ export const evaluate = defineTool({
|
|
|
68
68
|
schema: {
|
|
69
69
|
script: zod.string().describe(`JS script to run on the page`),
|
|
70
70
|
},
|
|
71
|
-
handler: async (request, response
|
|
72
|
-
const page =
|
|
71
|
+
handler: async (request, response) => {
|
|
72
|
+
const page = request.page;
|
|
73
73
|
try {
|
|
74
|
-
const result = await page.evaluate(request.params.script);
|
|
74
|
+
const result = await page.pptrPage.evaluate(request.params.script);
|
|
75
75
|
response.appendResponseLine(JSON.stringify(result));
|
|
76
76
|
}
|
|
77
77
|
catch (err) {
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
7
|
import { ToolCategory } from './categories.js';
|
|
8
|
-
import {
|
|
9
|
-
export const takeSnapshot =
|
|
8
|
+
import { definePageTool, timeoutSchema } from './ToolDefinition.js';
|
|
9
|
+
export const takeSnapshot = definePageTool({
|
|
10
10
|
name: 'take_snapshot',
|
|
11
11
|
description: `Take a text snapshot of the currently selected page based on the a11y tree. The snapshot lists page elements along with a unique
|
|
12
12
|
identifier (uid). Always use the latest snapshot. Prefer taking a snapshot over taking a screenshot. The snapshot indicates the element selected
|
|
@@ -33,7 +33,7 @@ in the DevTools Elements panel (if any).`,
|
|
|
33
33
|
});
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
|
-
export const waitFor =
|
|
36
|
+
export const waitFor = definePageTool({
|
|
37
37
|
name: 'wait_for',
|
|
38
38
|
description: `Wait for the specified text to appear on the selected page.`,
|
|
39
39
|
annotations: {
|
|
@@ -48,7 +48,8 @@ export const waitFor = defineTool({
|
|
|
48
48
|
...timeoutSchema,
|
|
49
49
|
},
|
|
50
50
|
handler: async (request, response, context) => {
|
|
51
|
-
|
|
51
|
+
const page = request.page;
|
|
52
|
+
await context.waitForTextOnPage(request.params.text, request.params.timeout, page.pptrPage);
|
|
52
53
|
response.appendResponseLine(`Element matching one of ${JSON.stringify(request.params.text)} found.`);
|
|
53
54
|
response.includeSnapshot();
|
|
54
55
|
},
|
package/build/src/tools/tools.js
CHANGED
|
@@ -7,6 +7,7 @@ import * as consoleTools from './console.js';
|
|
|
7
7
|
import * as emulationTools from './emulation.js';
|
|
8
8
|
import * as extensionTools from './extensions.js';
|
|
9
9
|
import * as inputTools from './input.js';
|
|
10
|
+
import * as lighthouseTools from './lighthouse.js';
|
|
10
11
|
import * as memoryTools from './memory.js';
|
|
11
12
|
import * as networkTools from './network.js';
|
|
12
13
|
import * as pagesTools from './pages.js';
|
|
@@ -24,6 +25,7 @@ export const createTools = (args) => {
|
|
|
24
25
|
...Object.values(emulationTools),
|
|
25
26
|
...Object.values(extensionTools),
|
|
26
27
|
...Object.values(inputTools),
|
|
28
|
+
...Object.values(lighthouseTools),
|
|
27
29
|
...Object.values(memoryTools),
|
|
28
30
|
...Object.values(networkTools),
|
|
29
31
|
...Object.values(pagesTools),
|