opera-devtools-mcp 0.6.1 → 0.7.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/build/src/McpContext.js +78 -41
- package/build/src/McpPage.js +5 -1
- package/build/src/ToolHandler.js +45 -74
- package/build/src/bin/chrome-devtools-mcp-main.js +4 -4
- package/build/src/bin/chrome-devtools.js +29 -30
- package/build/src/bin/opera-devtools-cli-options.js +1 -1
- package/build/src/bin/opera-devtools-mcp-cli-options.js +1 -1
- package/build/src/browser.js +5 -16
- package/build/src/config/browser-options.js +126 -0
- package/build/src/config/category-options.js +81 -0
- package/build/src/{bin/chrome-devtools-cli-options.js → config/cli-options.js} +368 -26
- package/build/src/{bin/chrome-devtools-mcp-cli-options.js → config/mcp-options.js} +143 -164
- package/build/src/daemon/client.js +9 -0
- package/build/src/daemon/utils.js +6 -0
- package/build/src/devtools/DevtoolsUtils.js +27 -21
- package/build/src/formatters/NetworkFormatter.js +5 -2
- package/build/src/index.js +166 -100
- package/build/src/processors/HeapSnapshotManager.js +12 -0
- package/build/src/telemetry/ClearcutLogger.js +19 -6
- package/build/src/telemetry/transformation.js +4 -0
- package/build/src/telemetry/types.js +4 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +5 -5
- package/build/src/third_party/bundled-packages.json +3 -3
- package/build/src/third_party/devtools-formatter-worker.js +22 -0
- package/build/src/third_party/devtools-heap-snapshot-worker.js +100 -20
- package/build/src/third_party/index.js +15459 -14256
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsBlockedByConnectionAllowlist.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestConfigBlockedByConnectionAllowlist.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenBlockedByConnectionAllowlist.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestWellKnownBlockedByConnectionAllowlist.md +1 -0
- package/build/src/tools/ToolDefinition.js +15 -0
- package/build/src/tools/categories.js +0 -6
- package/build/src/tools/comments.js +90 -0
- package/build/src/tools/console.js +1 -1
- package/build/src/tools/emulation.js +1 -1
- package/build/src/tools/extensions.js +1 -1
- package/build/src/tools/memory.js +60 -6
- package/build/src/tools/network.js +2 -2
- package/build/src/tools/pages.js +26 -15
- package/build/src/tools/performance.js +4 -3
- package/build/src/tools/screencast.js +3 -2
- package/build/src/tools/screenshot.js +39 -8
- package/build/src/tools/script.js +17 -4
- package/build/src/tools/slim/tools.js +41 -33
- package/build/src/tools/snapshot.js +1 -1
- package/build/src/tools/tools.js +2 -0
- package/build/src/utils/WaitForHelper.js +12 -1
- package/build/src/utils/bytes.js +105 -0
- package/build/src/utils/url.js +73 -0
- package/build/src/version.js +1 -1
- package/package.json +23 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Fetching the provider's accounts list was blocked by the request's initiator's connection allowlists.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Fetching the provider's FedCM manifest configuration was blocked by the request's initiator's connection allowlists.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Fetching the provider's token was blocked by the request's initiator's connection allowlists.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Fetching the provider's well-known file was blocked by the request's initiator's connection allowlists.
|
|
@@ -53,6 +53,15 @@ export function viewportTransform(arg) {
|
|
|
53
53
|
const hasTouch = tags.includes('touch');
|
|
54
54
|
const isLandscape = tags.includes('landscape');
|
|
55
55
|
const [width, height, dpr] = dimensions.split('x').map(Number);
|
|
56
|
+
if (!Number.isFinite(width) || width <= 0) {
|
|
57
|
+
throw new Error(`Invalid viewport width "${width}". Expected format '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]' with a positive width.`);
|
|
58
|
+
}
|
|
59
|
+
if (!Number.isFinite(height) || height <= 0) {
|
|
60
|
+
throw new Error(`Invalid viewport height "${height}". Expected format '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]' with a positive height.`);
|
|
61
|
+
}
|
|
62
|
+
if (dpr !== undefined && (!Number.isFinite(dpr) || dpr <= 0)) {
|
|
63
|
+
throw new Error(`Invalid devicePixelRatio "${dpr}". Expected a positive number.`);
|
|
64
|
+
}
|
|
56
65
|
return {
|
|
57
66
|
width,
|
|
58
67
|
height,
|
|
@@ -67,6 +76,12 @@ export function geolocationTransform(arg) {
|
|
|
67
76
|
return undefined;
|
|
68
77
|
}
|
|
69
78
|
const [latitude, longitude] = arg.split(',').map(Number);
|
|
79
|
+
if (!Number.isFinite(latitude) || latitude < -90 || latitude > 90) {
|
|
80
|
+
throw new Error(`Invalid latitude "${latitude}". Latitude must be a number between -90 and 90.`);
|
|
81
|
+
}
|
|
82
|
+
if (!Number.isFinite(longitude) || longitude < -180 || longitude > 180) {
|
|
83
|
+
throw new Error(`Invalid longitude "${longitude}". Longitude must be a number between -180 and 180.`);
|
|
84
|
+
}
|
|
70
85
|
return {
|
|
71
86
|
latitude,
|
|
72
87
|
longitude,
|
|
@@ -32,10 +32,4 @@ export const labels = {
|
|
|
32
32
|
[ToolCategory.OPERA]: 'Opera',
|
|
33
33
|
[ToolCategory.PWA]: 'Progressive Web Apps',
|
|
34
34
|
};
|
|
35
|
-
export const OFF_BY_DEFAULT_CATEGORIES = [
|
|
36
|
-
ToolCategory.EXTENSIONS,
|
|
37
|
-
ToolCategory.THIRD_PARTY,
|
|
38
|
-
ToolCategory.WEBMCP,
|
|
39
|
-
ToolCategory.PWA,
|
|
40
|
-
];
|
|
41
35
|
//# sourceMappingURL=categories.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { ToolCategory } from './categories.js';
|
|
8
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
9
|
+
export const openDevtools = definePageTool({
|
|
10
|
+
name: 'open_devtools',
|
|
11
|
+
description: 'Open a DevTools window for the selected page.',
|
|
12
|
+
annotations: {
|
|
13
|
+
category: ToolCategory.DEBUGGING,
|
|
14
|
+
readOnlyHint: true,
|
|
15
|
+
conditions: ['devtoolsComments'],
|
|
16
|
+
},
|
|
17
|
+
schema: {},
|
|
18
|
+
blockedByDialog: false,
|
|
19
|
+
verifyFilesSchema: [],
|
|
20
|
+
handler: async () => {
|
|
21
|
+
throw new Error('Not implemented');
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
export const getDevtoolsComments = definePageTool({
|
|
25
|
+
name: 'get_devtools_comments',
|
|
26
|
+
description: 'Retrieve user comments from the DevTools window for the page.',
|
|
27
|
+
annotations: {
|
|
28
|
+
category: ToolCategory.DEBUGGING,
|
|
29
|
+
readOnlyHint: true,
|
|
30
|
+
conditions: ['devtoolsComments'],
|
|
31
|
+
},
|
|
32
|
+
schema: {},
|
|
33
|
+
blockedByDialog: false,
|
|
34
|
+
verifyFilesSchema: {},
|
|
35
|
+
handler: async () => {
|
|
36
|
+
throw new Error('Not implemented');
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
export const resolveDevtoolsComment = definePageTool({
|
|
40
|
+
name: 'resolve_devtools_comment',
|
|
41
|
+
description: 'Append an agent reply to a DevTools comment thread and mark it as resolved.',
|
|
42
|
+
annotations: {
|
|
43
|
+
category: ToolCategory.DEBUGGING,
|
|
44
|
+
readOnlyHint: false,
|
|
45
|
+
conditions: ['devtoolsComments'],
|
|
46
|
+
},
|
|
47
|
+
schema: {
|
|
48
|
+
threadId: zod
|
|
49
|
+
.string()
|
|
50
|
+
.describe('The unique identifier of the comment thread to resolve (e.g. "comment-1").'),
|
|
51
|
+
replyText: zod
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Optional reply explanation from the AI agent to append to the resolved comment thread.'),
|
|
55
|
+
},
|
|
56
|
+
blockedByDialog: false,
|
|
57
|
+
verifyFilesSchema: {},
|
|
58
|
+
handler: async () => {
|
|
59
|
+
throw new Error('Not implemented');
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
export const revealInDevtools = definePageTool({
|
|
63
|
+
name: 'reveal_in_devtools',
|
|
64
|
+
description: 'Navigate DevTools to a specified panel and highlight a target DOM node or network request. The parameters uid and reqid are mutually exclusive.',
|
|
65
|
+
annotations: {
|
|
66
|
+
category: ToolCategory.DEBUGGING,
|
|
67
|
+
readOnlyHint: true,
|
|
68
|
+
conditions: ['devtoolsComments'],
|
|
69
|
+
},
|
|
70
|
+
schema: {
|
|
71
|
+
panelName: zod
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('The target DevTools panel (e.g. "elements", "network", "sources", "console").'),
|
|
75
|
+
uid: zod
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe('Optional snapshot element UID to reveal in the Elements panel.'),
|
|
79
|
+
reqid: zod
|
|
80
|
+
.number()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('Optional network request ID to reveal in the Network panel.'),
|
|
83
|
+
},
|
|
84
|
+
blockedByDialog: false,
|
|
85
|
+
verifyFilesSchema: {},
|
|
86
|
+
handler: async () => {
|
|
87
|
+
throw new Error('Not implemented');
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=comments.js.map
|
|
@@ -32,7 +32,7 @@ const LIST_CONSOLE_MESSAGES_TOOL_NAME = 'list_console_messages';
|
|
|
32
32
|
export const listConsoleMessages = definePageTool(cliArgs => {
|
|
33
33
|
return {
|
|
34
34
|
name: LIST_CONSOLE_MESSAGES_TOOL_NAME,
|
|
35
|
-
description: `List all console messages for the
|
|
35
|
+
description: `List all console messages for the target page since the last navigation.${cliArgs?.categoryExtensions ? ' This includes console messages originating from extensions content scripts.' : ''}`,
|
|
36
36
|
annotations: {
|
|
37
37
|
category: ToolCategory.DEBUGGING,
|
|
38
38
|
readOnlyHint: true,
|
|
@@ -33,7 +33,7 @@ const throttlingOptions = [
|
|
|
33
33
|
];
|
|
34
34
|
export const emulate = definePageTool({
|
|
35
35
|
name: 'emulate',
|
|
36
|
-
description: `Emulates various features on the
|
|
36
|
+
description: `Emulates various features on the target page.`,
|
|
37
37
|
annotations: {
|
|
38
38
|
category: ToolCategory.EMULATION,
|
|
39
39
|
readOnlyHint: false,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { byteSizeRangeSchema } from '../utils/bytes.js';
|
|
7
8
|
import { ToolCategory } from './categories.js';
|
|
8
9
|
import { definePageTool, defineTool } from './ToolDefinition.js';
|
|
9
10
|
const HEAP_SNAPSHOT_FILTERS = [
|
|
@@ -17,7 +18,7 @@ const HEAP_SNAPSHOT_FILTERS = [
|
|
|
17
18
|
];
|
|
18
19
|
export const takeHeapSnapshot = definePageTool({
|
|
19
20
|
name: 'take_heapsnapshot',
|
|
20
|
-
description: `Capture a heap snapshot of the
|
|
21
|
+
description: `Capture a heap snapshot of the target page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.`,
|
|
21
22
|
annotations: {
|
|
22
23
|
category: ToolCategory.MEMORY,
|
|
23
24
|
readOnlyHint: false,
|
|
@@ -239,10 +240,7 @@ export const getHeapSnapshotEdges = defineTool({
|
|
|
239
240
|
.enum(['retainedSize', 'selfSize', 'name'])
|
|
240
241
|
.optional()
|
|
241
242
|
.describe('Sort order for edges. Default is retainedSize.'),
|
|
242
|
-
|
|
243
|
-
.number()
|
|
244
|
-
.optional()
|
|
245
|
-
.describe('Minimum retained size in bytes for target nodes.'),
|
|
243
|
+
retainedSize: byteSizeRangeSchema('Inclusive retained size range (e.g. "1MB-2MB", "-1MB", or "1MB-") for target nodes. A single value is treated as a minimum. Currently, only the lower bound is applied.').optional(),
|
|
246
244
|
excludePrimitives: zod
|
|
247
245
|
.boolean()
|
|
248
246
|
.optional()
|
|
@@ -253,7 +251,8 @@ export const getHeapSnapshotEdges = defineTool({
|
|
|
253
251
|
handler: async (request, response, context) => {
|
|
254
252
|
const edges = await context.getHeapSnapshotEdges(request.params.filePath, request.params.nodeId, {
|
|
255
253
|
sortBy: request.params.sortBy ?? 'retainedSize',
|
|
256
|
-
|
|
254
|
+
// DevTools currently only supports a lower retained-size bound here.
|
|
255
|
+
minRetainedSize: request.params.retainedSize?.min,
|
|
257
256
|
excludePrimitives: request.params.excludePrimitives ?? true,
|
|
258
257
|
});
|
|
259
258
|
response.setHeapSnapshotNodes(edges, {
|
|
@@ -367,4 +366,59 @@ export const getHeapSnapshotObjectDetails = defineTool({
|
|
|
367
366
|
response.setHeapSnapshotObjectDetails(objectInfo);
|
|
368
367
|
},
|
|
369
368
|
});
|
|
369
|
+
export const queryHeapSnapshotObjects = defineTool({
|
|
370
|
+
name: 'query_heapsnapshot_objects',
|
|
371
|
+
description: 'Loads a memory heapsnapshot and queries objects matching specific filters (className, propertyName, nodeType, retainedSize, selfSize, isDetached, sortBy).',
|
|
372
|
+
annotations: {
|
|
373
|
+
category: ToolCategory.MEMORY,
|
|
374
|
+
readOnlyHint: true,
|
|
375
|
+
conditions: ['memoryDebugging'],
|
|
376
|
+
},
|
|
377
|
+
blockedByDialog: false,
|
|
378
|
+
verifyFilesSchema: { filePath: true },
|
|
379
|
+
schema: {
|
|
380
|
+
filePath: zod.string().describe('A path to a .heapsnapshot file to read.'),
|
|
381
|
+
className: zod
|
|
382
|
+
.string()
|
|
383
|
+
.optional()
|
|
384
|
+
.describe('Optional regex or text matching object class name.'),
|
|
385
|
+
propertyName: zod
|
|
386
|
+
.string()
|
|
387
|
+
.optional()
|
|
388
|
+
.describe('Optional property name filter for outgoing reference edges.'),
|
|
389
|
+
nodeType: zod
|
|
390
|
+
.string()
|
|
391
|
+
.optional()
|
|
392
|
+
.describe('Optional V8 node type filter (e.g. object, closure, string, array, code).'),
|
|
393
|
+
retainedSize: byteSizeRangeSchema('Inclusive retained size range (e.g. "1MB-2MB", "-1MB", or "1MB-"). A single value is treated as a minimum.').optional(),
|
|
394
|
+
selfSize: byteSizeRangeSchema('Inclusive self size range (e.g. "1MB-2MB", "-1MB", or "1MB-"). A single value is treated as a minimum.').optional(),
|
|
395
|
+
isDetached: zod
|
|
396
|
+
.boolean()
|
|
397
|
+
.optional()
|
|
398
|
+
.describe('Whether to filter for detached DOM nodes.'),
|
|
399
|
+
sortBy: zod
|
|
400
|
+
.enum(['retainedSize', 'selfSize', 'id'])
|
|
401
|
+
.optional()
|
|
402
|
+
.describe('Sort order for results. Default is retainedSize.'),
|
|
403
|
+
pageIdx: zod.number().optional().describe('The page index for pagination.'),
|
|
404
|
+
pageSize: zod.number().optional().describe('The page size for pagination.'),
|
|
405
|
+
},
|
|
406
|
+
handler: async (request, response, context) => {
|
|
407
|
+
const range = await context.queryHeapSnapshotObjects(request.params.filePath, {
|
|
408
|
+
className: request.params.className,
|
|
409
|
+
propertyName: request.params.propertyName,
|
|
410
|
+
nodeType: request.params.nodeType,
|
|
411
|
+
minRetainedSize: request.params.retainedSize?.min,
|
|
412
|
+
maxRetainedSize: request.params.retainedSize?.max,
|
|
413
|
+
minSelfSize: request.params.selfSize?.min,
|
|
414
|
+
maxSelfSize: request.params.selfSize?.max,
|
|
415
|
+
isDetached: request.params.isDetached,
|
|
416
|
+
sortBy: request.params.sortBy,
|
|
417
|
+
});
|
|
418
|
+
response.setHeapSnapshotNodes(range, {
|
|
419
|
+
pageIdx: request.params.pageIdx,
|
|
420
|
+
pageSize: request.params.pageSize,
|
|
421
|
+
});
|
|
422
|
+
},
|
|
423
|
+
});
|
|
370
424
|
//# sourceMappingURL=memory.js.map
|
|
@@ -29,7 +29,7 @@ const FILTERABLE_RESOURCE_TYPES = [
|
|
|
29
29
|
];
|
|
30
30
|
export const listNetworkRequests = definePageTool({
|
|
31
31
|
name: 'list_network_requests',
|
|
32
|
-
description: `Lists the most recent requests for the
|
|
32
|
+
description: `Lists the most recent requests for the target page since the last navigation.`,
|
|
33
33
|
annotations: {
|
|
34
34
|
category: ToolCategory.NETWORK,
|
|
35
35
|
readOnlyHint: true,
|
|
@@ -76,7 +76,7 @@ export const listNetworkRequests = definePageTool({
|
|
|
76
76
|
});
|
|
77
77
|
export const getNetworkRequest = definePageTool({
|
|
78
78
|
name: 'get_network_request',
|
|
79
|
-
description: `Gets a network request by an optional reqid, if omitted returns the currently selected request in the DevTools Network panel.`,
|
|
79
|
+
description: `Gets a network request by an optional reqid, if omitted returns the currently selected request in the DevTools Network panel. Useful for inspecting request headers (including 'Cookie') and response headers (including 'Set-Cookie' and directives).`,
|
|
80
80
|
annotations: {
|
|
81
81
|
category: ToolCategory.NETWORK,
|
|
82
82
|
readOnlyHint: false,
|
package/build/src/tools/pages.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
7
|
import { logger } from '../utils/logger.js';
|
|
8
|
+
import { validateUrl } from '../utils/url.js';
|
|
8
9
|
import { ToolCategory } from './categories.js';
|
|
9
10
|
import { CLOSE_PAGE_ERROR, definePageTool, defineTool, timeoutSchema, } from './ToolDefinition.js';
|
|
10
11
|
export const listPages = defineTool(args => {
|
|
@@ -84,7 +85,7 @@ export const closePage = defineTool({
|
|
|
84
85
|
response.setListThirdPartyDeveloperTools();
|
|
85
86
|
},
|
|
86
87
|
});
|
|
87
|
-
export const newPage = defineTool(
|
|
88
|
+
export const newPage = defineTool(args => {
|
|
88
89
|
return {
|
|
89
90
|
name: 'new_page',
|
|
90
91
|
description: `Open a new tab and load a URL. Use project URL if not specified otherwise.`,
|
|
@@ -103,12 +104,16 @@ export const newPage = defineTool(() => {
|
|
|
103
104
|
.optional()
|
|
104
105
|
.describe('If specified, the page is created in an isolated browser context with the given name. ' +
|
|
105
106
|
'Pages in the same browser context share cookies and storage. ' +
|
|
106
|
-
'Pages in different browser contexts are fully isolated.'),
|
|
107
|
+
'Pages in different browser contexts are fully isolated (useful for clean-slate testing of cookies and authentication).'),
|
|
107
108
|
...timeoutSchema,
|
|
108
109
|
},
|
|
109
110
|
blockedByDialog: false,
|
|
110
111
|
verifyFilesSchema: {},
|
|
111
112
|
handler: async (request, response, context) => {
|
|
113
|
+
validateUrl(request.params.url, {
|
|
114
|
+
javascriptEvaluation: args?.javascriptEvaluation,
|
|
115
|
+
categoryExtensions: args?.categoryExtensions,
|
|
116
|
+
});
|
|
112
117
|
const page = await context.newPage(request.params.background, request.params.isolatedContext);
|
|
113
118
|
await page.waitForEventsAfterAction(async () => {
|
|
114
119
|
await page.pptrPage.goto(request.params.url, {
|
|
@@ -120,7 +125,7 @@ export const newPage = defineTool(() => {
|
|
|
120
125
|
},
|
|
121
126
|
};
|
|
122
127
|
});
|
|
123
|
-
export const navigatePage = definePageTool(
|
|
128
|
+
export const navigatePage = definePageTool(args => {
|
|
124
129
|
return {
|
|
125
130
|
name: 'navigate_page',
|
|
126
131
|
description: `Go to a URL, or back, forward, or reload. Use project URL if not specified otherwise.`,
|
|
@@ -142,10 +147,14 @@ export const navigatePage = definePageTool(() => {
|
|
|
142
147
|
.enum(['accept', 'dismiss'])
|
|
143
148
|
.optional()
|
|
144
149
|
.describe('Whether to auto accept or beforeunload dialogs triggered by this navigation. Default is accept.'),
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
...(args?.javascriptEvaluation !== false
|
|
151
|
+
? {
|
|
152
|
+
initScript: zod
|
|
153
|
+
.string()
|
|
154
|
+
.optional()
|
|
155
|
+
.describe('A JavaScript script to be executed on each new document before any other scripts for the next navigation.'),
|
|
156
|
+
}
|
|
157
|
+
: {}),
|
|
149
158
|
...timeoutSchema,
|
|
150
159
|
},
|
|
151
160
|
blockedByDialog: false,
|
|
@@ -161,6 +170,12 @@ export const navigatePage = definePageTool(() => {
|
|
|
161
170
|
if (!request.params.type) {
|
|
162
171
|
request.params.type = 'url';
|
|
163
172
|
}
|
|
173
|
+
if (request.params.url) {
|
|
174
|
+
validateUrl(request.params.url, {
|
|
175
|
+
javascriptEvaluation: args?.javascriptEvaluation,
|
|
176
|
+
categoryExtensions: args?.categoryExtensions,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
164
179
|
let initScriptId;
|
|
165
180
|
if (request.params.initScript) {
|
|
166
181
|
const { identifier } = await page.pptrPage.evaluateOnNewDocument(request.params.initScript);
|
|
@@ -239,7 +254,7 @@ export const navigatePage = definePageTool(() => {
|
|
|
239
254
|
});
|
|
240
255
|
export const resizePage = definePageTool({
|
|
241
256
|
name: 'resize_page',
|
|
242
|
-
description: `Resizes the
|
|
257
|
+
description: `Resizes the page's window so that the page has specified dimension`,
|
|
243
258
|
annotations: {
|
|
244
259
|
category: ToolCategory.EMULATION,
|
|
245
260
|
readOnlyHint: false,
|
|
@@ -335,15 +350,11 @@ export const getTabId = definePageTool({
|
|
|
335
350
|
readOnlyHint: true,
|
|
336
351
|
conditions: ['experimentalInteropTools'],
|
|
337
352
|
},
|
|
338
|
-
schema: {
|
|
339
|
-
pageId: zod
|
|
340
|
-
.number()
|
|
341
|
-
.describe(`The ID of the page to get the tab ID for. Call ${listPages().name} to get available pages.`),
|
|
342
|
-
},
|
|
353
|
+
schema: {},
|
|
343
354
|
blockedByDialog: false,
|
|
344
355
|
verifyFilesSchema: {},
|
|
345
|
-
handler: async (request, response
|
|
346
|
-
const page =
|
|
356
|
+
handler: async (request, response) => {
|
|
357
|
+
const page = request.page;
|
|
347
358
|
const tabId = page.pptrPage._tabId;
|
|
348
359
|
response.setTabId(tabId);
|
|
349
360
|
response.appendResponseLine(`Tab ID: ${tabId}`);
|
|
@@ -15,7 +15,7 @@ const filePathSchema = zod
|
|
|
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
16
|
export const startTrace = definePageTool({
|
|
17
17
|
name: 'performance_start_trace',
|
|
18
|
-
description: `Start a performance trace on the
|
|
18
|
+
description: `Start a performance trace on the target 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,
|
|
@@ -24,7 +24,7 @@ export const startTrace = definePageTool({
|
|
|
24
24
|
reload: zod
|
|
25
25
|
.boolean()
|
|
26
26
|
.default(true)
|
|
27
|
-
.describe('Determines if, once tracing has started, the
|
|
27
|
+
.describe('Determines if, once tracing has started, the target 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.'),
|
|
28
28
|
autoStop: zod
|
|
29
29
|
.boolean()
|
|
30
30
|
.default(true)
|
|
@@ -60,6 +60,7 @@ export const startTrace = definePageTool({
|
|
|
60
60
|
];
|
|
61
61
|
await page.pptrPage.tracing.start({
|
|
62
62
|
categories,
|
|
63
|
+
bufferSize: 1200 * 1000,
|
|
63
64
|
});
|
|
64
65
|
if (request.params.reload) {
|
|
65
66
|
await page.pptrPage.goto(pageUrlForTracing, {
|
|
@@ -96,7 +97,7 @@ export const startTrace = definePageTool({
|
|
|
96
97
|
});
|
|
97
98
|
export const stopTrace = definePageTool({
|
|
98
99
|
name: 'performance_stop_trace',
|
|
99
|
-
description: 'Stop the active performance trace recording on the
|
|
100
|
+
description: 'Stop the active performance trace recording on the target webpage.',
|
|
100
101
|
annotations: {
|
|
101
102
|
category: ToolCategory.PERFORMANCE,
|
|
102
103
|
readOnlyHint: false,
|
|
@@ -16,7 +16,7 @@ async function generateTempFilePath() {
|
|
|
16
16
|
const supportedExtensions = ['.webm', '.mp4'];
|
|
17
17
|
export const startScreencast = definePageTool(args => ({
|
|
18
18
|
name: 'screencast_start',
|
|
19
|
-
description: `Starts recording a screencast (video) of the
|
|
19
|
+
description: `Starts recording a screencast (video) of the target page in specified format.`,
|
|
20
20
|
annotations: {
|
|
21
21
|
category: ToolCategory.DEBUGGING,
|
|
22
22
|
readOnlyHint: false,
|
|
@@ -62,6 +62,7 @@ export const startScreencast = definePageTool(args => ({
|
|
|
62
62
|
path: resolvedPath,
|
|
63
63
|
format: format,
|
|
64
64
|
ffmpegPath: args?.experimentalFfmpegPath,
|
|
65
|
+
fps: args?.experimentalScreencastFps,
|
|
65
66
|
});
|
|
66
67
|
}
|
|
67
68
|
catch (err) {
|
|
@@ -91,7 +92,7 @@ export const startScreencast = definePageTool(args => ({
|
|
|
91
92
|
}));
|
|
92
93
|
export const stopScreencast = definePageTool({
|
|
93
94
|
name: 'screencast_stop',
|
|
94
|
-
description: 'Stops the active screencast recording on the
|
|
95
|
+
description: 'Stops the active screencast recording on the target page.',
|
|
95
96
|
annotations: {
|
|
96
97
|
category: ToolCategory.DEBUGGING,
|
|
97
98
|
readOnlyHint: false,
|
|
@@ -60,22 +60,41 @@ import { ToolCategory } from './categories.js';
|
|
|
60
60
|
import { definePageTool } from './ToolDefinition.js';
|
|
61
61
|
async function getSourceBox(page, element, fullPage) {
|
|
62
62
|
if (element) {
|
|
63
|
-
const
|
|
64
|
-
|
|
63
|
+
const viewport = page.viewport();
|
|
64
|
+
const [box, devicePixelRatio] = await Promise.all([
|
|
65
|
+
element.boundingBox(),
|
|
66
|
+
viewport
|
|
67
|
+
? (viewport.deviceScaleFactor ?? 1)
|
|
68
|
+
: page.evaluate(() => window.devicePixelRatio),
|
|
69
|
+
]);
|
|
70
|
+
return box ? { ...box, devicePixelRatio } : undefined;
|
|
65
71
|
}
|
|
66
72
|
if (fullPage) {
|
|
67
73
|
const dims = await page.evaluate(() => ({
|
|
68
74
|
width: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
|
|
69
75
|
height: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),
|
|
76
|
+
devicePixelRatio: window.devicePixelRatio,
|
|
70
77
|
}));
|
|
71
78
|
if (dims.width <= 0 || dims.height <= 0) {
|
|
72
79
|
return undefined;
|
|
73
80
|
}
|
|
74
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
x: 0,
|
|
83
|
+
y: 0,
|
|
84
|
+
width: dims.width,
|
|
85
|
+
height: dims.height,
|
|
86
|
+
devicePixelRatio: dims.devicePixelRatio,
|
|
87
|
+
};
|
|
75
88
|
}
|
|
76
89
|
const viewport = page.viewport();
|
|
77
90
|
if (viewport) {
|
|
78
|
-
return {
|
|
91
|
+
return {
|
|
92
|
+
x: 0,
|
|
93
|
+
y: 0,
|
|
94
|
+
width: viewport.width,
|
|
95
|
+
height: viewport.height,
|
|
96
|
+
devicePixelRatio: viewport.deviceScaleFactor ?? 1,
|
|
97
|
+
};
|
|
79
98
|
}
|
|
80
99
|
// The browser is launched and connected with `defaultViewport: null`, so
|
|
81
100
|
// `page.viewport()` stays null until something emulates one. Fall back to the
|
|
@@ -83,21 +102,33 @@ async function getSourceBox(page, element, fullPage) {
|
|
|
83
102
|
const dims = await page.evaluate(() => ({
|
|
84
103
|
width: window.innerWidth,
|
|
85
104
|
height: window.innerHeight,
|
|
105
|
+
devicePixelRatio: window.devicePixelRatio,
|
|
86
106
|
}));
|
|
87
107
|
if (dims.width <= 0 || dims.height <= 0) {
|
|
88
108
|
return undefined;
|
|
89
109
|
}
|
|
90
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
x: 0,
|
|
112
|
+
y: 0,
|
|
113
|
+
width: dims.width,
|
|
114
|
+
height: dims.height,
|
|
115
|
+
devicePixelRatio: dims.devicePixelRatio,
|
|
116
|
+
};
|
|
91
117
|
}
|
|
92
118
|
function computeDownscaleClip(box, maxWidth, maxHeight) {
|
|
93
|
-
const widthScale = maxWidth !== undefined
|
|
94
|
-
|
|
119
|
+
const widthScale = maxWidth !== undefined
|
|
120
|
+
? Math.min(1, maxWidth / (box.width * box.devicePixelRatio))
|
|
121
|
+
: 1;
|
|
122
|
+
const heightScale = maxHeight !== undefined
|
|
123
|
+
? Math.min(1, maxHeight / (box.height * box.devicePixelRatio))
|
|
124
|
+
: 1;
|
|
95
125
|
const scale = Math.min(widthScale, heightScale);
|
|
96
126
|
if (scale >= 1) {
|
|
97
127
|
return undefined;
|
|
98
128
|
}
|
|
99
129
|
// Skip degenerate sub-pixel results.
|
|
100
|
-
if (Math.round(box.width *
|
|
130
|
+
if (Math.round(box.width * box.devicePixelRatio * scale) < 1 ||
|
|
131
|
+
Math.round(box.height * box.devicePixelRatio * scale) < 1) {
|
|
101
132
|
return undefined;
|
|
102
133
|
}
|
|
103
134
|
return {
|
|
@@ -61,14 +61,24 @@ import { defineTool, pageIdSchema } from './ToolDefinition.js';
|
|
|
61
61
|
export const evaluateScript = defineTool(cliArgs => {
|
|
62
62
|
return {
|
|
63
63
|
name: 'evaluate_script',
|
|
64
|
-
description: `Evaluate a JavaScript function inside the
|
|
64
|
+
description: `Evaluate a JavaScript function inside the target page${cliArgs?.categoryExtensions ? ' or service worker' : ''}. Returns the response as JSON, so returned values have to be JSON-serializable.`,
|
|
65
65
|
annotations: {
|
|
66
66
|
category: ToolCategory.DEBUGGING,
|
|
67
67
|
readOnlyHint: false,
|
|
68
|
+
conditions: ['javascriptEvaluation'],
|
|
68
69
|
},
|
|
69
70
|
schema: {
|
|
70
|
-
...(cliArgs?.
|
|
71
|
-
|
|
71
|
+
...(cliArgs?.pageIdRouting
|
|
72
|
+
? cliArgs.categoryExtensions
|
|
73
|
+
? {
|
|
74
|
+
pageId: zod
|
|
75
|
+
.number()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Targets a specific page by ID. Required when not evaluating in a service worker.'),
|
|
78
|
+
}
|
|
79
|
+
: pageIdSchema
|
|
80
|
+
: {}),
|
|
81
|
+
function: zod.string().describe(`A JavaScript function declaration to be executed by the tool in the target page.
|
|
72
82
|
Example without arguments: \`() => document.title\` or \`async () => await fetch("example.com")\`.
|
|
73
83
|
Example with arguments: \`(el) => el.innerText\`
|
|
74
84
|
`),
|
|
@@ -131,7 +141,10 @@ Example with arguments: \`(el) => el.innerText\`
|
|
|
131
141
|
response.attachWaitForResult(result);
|
|
132
142
|
return;
|
|
133
143
|
}
|
|
134
|
-
|
|
144
|
+
if (cliArgs?.categoryExtensions && cliArgs?.pageIdRouting && !pageId) {
|
|
145
|
+
throw new Error('specify either a pageId or a serviceWorkerId.');
|
|
146
|
+
}
|
|
147
|
+
const mcpPage = cliArgs?.pageIdRouting && request.params.pageId
|
|
135
148
|
? context.getPageById(request.params.pageId)
|
|
136
149
|
: context.getSelectedMcpPage();
|
|
137
150
|
const page = mcpPage.pptrPage;
|