chrome-devtools-mcp 0.24.0 → 0.26.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 +23 -6
- package/build/src/McpPage.js +4 -4
- package/build/src/McpResponse.js +18 -16
- package/build/src/TextSnapshot.js +2 -2
- package/build/src/ToolHandler.js +188 -0
- package/build/src/bin/chrome-devtools-cli-options.js +28 -5
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +8 -5
- package/build/src/bin/chrome-devtools-mcp-main.js +4 -8
- package/build/src/bin/chrome-devtools.js +0 -2
- package/build/src/index.js +9 -164
- package/build/src/telemetry/ClearcutLogger.js +27 -0
- package/build/src/telemetry/errors.js +14 -0
- package/build/src/telemetry/types.js +0 -8
- package/build/src/third_party/THIRD_PARTY_NOTICES +12 -14
- package/build/src/third_party/bundled-packages.json +3 -3
- package/build/src/third_party/devtools-formatter-worker.js +49 -33
- package/build/src/third_party/devtools-heap-snapshot-worker.js +60 -44
- package/build/src/third_party/index.js +2138 -1352
- package/build/src/third_party/issue-descriptions/genericFormModelContextMissingToolDescription.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormModelContextMissingToolName.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingName.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingTitleAndDescription.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormModelContextRequiredParameterMissingName.md +5 -0
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +4236 -4219
- package/build/src/tools/categories.js +6 -3
- package/build/src/tools/input.js +72 -7
- package/build/src/tools/lighthouse.js +7 -7
- package/build/src/tools/pages.js +5 -5
- package/build/src/tools/{inPage.js → thirdPartyDeveloper.js} +15 -15
- package/build/src/tools/tools.js +2 -2
- package/build/src/tools/webmcp.js +2 -4
- package/build/src/version.js +1 -1
- package/package.json +7 -8
|
@@ -12,8 +12,9 @@ export var ToolCategory;
|
|
|
12
12
|
ToolCategory["NETWORK"] = "network";
|
|
13
13
|
ToolCategory["DEBUGGING"] = "debugging";
|
|
14
14
|
ToolCategory["EXTENSIONS"] = "extensions";
|
|
15
|
-
ToolCategory["
|
|
15
|
+
ToolCategory["THIRD_PARTY"] = "experimentalThirdParty";
|
|
16
16
|
ToolCategory["MEMORY"] = "memory";
|
|
17
|
+
ToolCategory["WEBMCP"] = "experimentalWebmcp";
|
|
17
18
|
})(ToolCategory || (ToolCategory = {}));
|
|
18
19
|
export const labels = {
|
|
19
20
|
[ToolCategory.INPUT]: 'Input automation',
|
|
@@ -23,11 +24,13 @@ export const labels = {
|
|
|
23
24
|
[ToolCategory.NETWORK]: 'Network',
|
|
24
25
|
[ToolCategory.DEBUGGING]: 'Debugging',
|
|
25
26
|
[ToolCategory.EXTENSIONS]: 'Extensions',
|
|
26
|
-
[ToolCategory.
|
|
27
|
+
[ToolCategory.THIRD_PARTY]: 'Third-party',
|
|
27
28
|
[ToolCategory.MEMORY]: 'Memory',
|
|
29
|
+
[ToolCategory.WEBMCP]: 'WebMCP',
|
|
28
30
|
};
|
|
29
31
|
export const OFF_BY_DEFAULT_CATEGORIES = [
|
|
30
32
|
ToolCategory.EXTENSIONS,
|
|
31
|
-
ToolCategory.
|
|
33
|
+
ToolCategory.THIRD_PARTY,
|
|
34
|
+
ToolCategory.WEBMCP,
|
|
32
35
|
];
|
|
33
36
|
//# sourceMappingURL=categories.js.map
|
package/build/src/tools/input.js
CHANGED
|
@@ -26,6 +26,44 @@ function handleActionError(error, uid) {
|
|
|
26
26
|
cause: error,
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
|
+
async function selectNativeSelectOption(handle) {
|
|
30
|
+
const selectHandle = await handle.evaluateHandle(node => {
|
|
31
|
+
if (!(node instanceof HTMLOptionElement)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const select = node.closest('select');
|
|
35
|
+
if (!select || select.multiple || select.disabled || node.disabled) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const parentElement = node.parentElement;
|
|
39
|
+
if (parentElement instanceof HTMLOptGroupElement &&
|
|
40
|
+
parentElement.disabled) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return select;
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
const select = selectHandle.asElement();
|
|
47
|
+
if (!select) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const valueHandle = await handle.getProperty('value');
|
|
51
|
+
try {
|
|
52
|
+
const value = await valueHandle.jsonValue();
|
|
53
|
+
if (typeof value !== 'string') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
await select.asLocator().fill(value);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
void valueHandle.dispose();
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
void selectHandle.dispose();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
29
67
|
export const click = definePageTool({
|
|
30
68
|
name: 'click',
|
|
31
69
|
description: `Clicks on the provided element`,
|
|
@@ -44,8 +82,14 @@ export const click = definePageTool({
|
|
|
44
82
|
handler: async (request, response) => {
|
|
45
83
|
const uid = request.params.uid;
|
|
46
84
|
const handle = await request.page.getElementByUid(uid);
|
|
85
|
+
const aXNode = request.page.getAXNodeByUid(uid);
|
|
86
|
+
const shouldSelectNativeOption = !request.params.dblClick && aXNode?.role === 'option';
|
|
47
87
|
try {
|
|
48
88
|
await request.page.waitForEventsAfterAction(async () => {
|
|
89
|
+
if (shouldSelectNativeOption &&
|
|
90
|
+
(await selectNativeSelectOption(handle))) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
49
93
|
await handle.asLocator().click({
|
|
50
94
|
count: request.params.dblClick ? 2 : 1,
|
|
51
95
|
});
|
|
@@ -176,10 +220,27 @@ async function fillFormElement(uid, value, context, page) {
|
|
|
176
220
|
await selectOption(handle, aXNode, value);
|
|
177
221
|
}
|
|
178
222
|
else {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
223
|
+
const isToggle = await handle.evaluate(el => {
|
|
224
|
+
if (el instanceof HTMLInputElement) {
|
|
225
|
+
return el.type === 'checkbox' || el.type === 'radio';
|
|
226
|
+
}
|
|
227
|
+
const role = el.getAttribute('role');
|
|
228
|
+
return role === 'checkbox' || role === 'radio' || role === 'switch';
|
|
229
|
+
});
|
|
230
|
+
if (isToggle) {
|
|
231
|
+
if (['true', 'false'].includes(value)) {
|
|
232
|
+
await handle.asLocator().fill(value === 'true');
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
throw new Error(`Checkboxes, radio boxes and toggles require "true" or "false" value, but ${value} was used`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// Increase timeout for longer input values.
|
|
240
|
+
const timeoutPerChar = 10; // ms
|
|
241
|
+
const fillTimeout = page.pptrPage.getDefaultTimeout() + value.length * timeoutPerChar;
|
|
242
|
+
await handle.asLocator().setTimeout(fillTimeout).fill(value);
|
|
243
|
+
}
|
|
183
244
|
}
|
|
184
245
|
}
|
|
185
246
|
catch (error) {
|
|
@@ -200,7 +261,9 @@ export const fill = definePageTool({
|
|
|
200
261
|
uid: zod
|
|
201
262
|
.string()
|
|
202
263
|
.describe('The uid of an element on the page from the page content snapshot'),
|
|
203
|
-
value: zod
|
|
264
|
+
value: zod
|
|
265
|
+
.string()
|
|
266
|
+
.describe('The value to fill in. "true" or "false" for checkboxes and toggles, "true" for radio buttons.'),
|
|
204
267
|
includeSnapshot: includeSnapshotSchema,
|
|
205
268
|
},
|
|
206
269
|
blockedByDialog: true,
|
|
@@ -273,7 +336,7 @@ export const drag = definePageTool({
|
|
|
273
336
|
});
|
|
274
337
|
export const fillForm = definePageTool({
|
|
275
338
|
name: 'fill_form',
|
|
276
|
-
description: `Fill out multiple form elements at once
|
|
339
|
+
description: `Fill out multiple form elements (inputs, selects, checkboxes, radios) at once. ALWAYS prefer this tool over multiple individual 'fill' or 'click' calls when interacting with forms. It is significantly faster, more reliable, and reduces turn count. Example: Fill username, password, and check "Remember Me" in one call.`,
|
|
277
340
|
annotations: {
|
|
278
341
|
category: ToolCategory.INPUT,
|
|
279
342
|
readOnlyHint: false,
|
|
@@ -284,7 +347,9 @@ export const fillForm = definePageTool({
|
|
|
284
347
|
// eslint-disable-next-line @local/enforce-zod-schema
|
|
285
348
|
zod.object({
|
|
286
349
|
uid: zod.string().describe('The uid of the element to fill out'),
|
|
287
|
-
value: zod
|
|
350
|
+
value: zod
|
|
351
|
+
.string()
|
|
352
|
+
.describe('Value for the element. "true" or "false" for checkboxes and toggles, "true" for radio buttons.'),
|
|
288
353
|
}))
|
|
289
354
|
.describe('Elements from snapshot to fill out.'),
|
|
290
355
|
includeSnapshot: includeSnapshotSchema,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import { snapshot, navigation, generateReport, zod,
|
|
7
|
+
import { snapshot, navigation, generateReport, zod, } from '../third_party/index.js';
|
|
8
8
|
import { ToolCategory } from './categories.js';
|
|
9
9
|
import { startTrace } from './performance.js';
|
|
10
10
|
import { definePageTool } from './ToolDefinition.js';
|
|
@@ -67,17 +67,17 @@ export const lighthouseAudit = definePageTool({
|
|
|
67
67
|
disabled: false,
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
const options = {
|
|
71
|
-
flags,
|
|
72
|
-
config: agenticBrowsingConfig,
|
|
73
|
-
};
|
|
74
70
|
let result;
|
|
75
71
|
try {
|
|
76
72
|
if (mode === 'navigation') {
|
|
77
|
-
result = await navigation(page.pptrPage, page.pptrPage.url(),
|
|
73
|
+
result = await navigation(page.pptrPage, page.pptrPage.url(), {
|
|
74
|
+
flags,
|
|
75
|
+
});
|
|
78
76
|
}
|
|
79
77
|
else {
|
|
80
|
-
result = await snapshot(page.pptrPage,
|
|
78
|
+
result = await snapshot(page.pptrPage, {
|
|
79
|
+
flags,
|
|
80
|
+
});
|
|
81
81
|
}
|
|
82
82
|
if (!result) {
|
|
83
83
|
throw new Error('Lighthouse audit failed.');
|
package/build/src/tools/pages.js
CHANGED
|
@@ -64,7 +64,7 @@ export const listPages = defineTool(args => {
|
|
|
64
64
|
blockedByDialog: false,
|
|
65
65
|
handler: async (_request, response) => {
|
|
66
66
|
response.setIncludePages(true);
|
|
67
|
-
response.
|
|
67
|
+
response.setListThirdPartyDeveloperTools();
|
|
68
68
|
response.setListWebMcpTools();
|
|
69
69
|
},
|
|
70
70
|
};
|
|
@@ -90,7 +90,7 @@ export const selectPage = defineTool({
|
|
|
90
90
|
const page = context.getPageById(request.params.pageId);
|
|
91
91
|
context.selectPage(page);
|
|
92
92
|
response.setIncludePages(true);
|
|
93
|
-
response.
|
|
93
|
+
response.setListThirdPartyDeveloperTools();
|
|
94
94
|
response.setListWebMcpTools();
|
|
95
95
|
if (request.params.bringToFront) {
|
|
96
96
|
await page.pptrPage.bringToFront();
|
|
@@ -123,7 +123,7 @@ export const closePage = defineTool({
|
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
response.setIncludePages(true);
|
|
126
|
-
response.
|
|
126
|
+
response.setListThirdPartyDeveloperTools();
|
|
127
127
|
},
|
|
128
128
|
});
|
|
129
129
|
export const newPage = defineTool(args => {
|
|
@@ -163,7 +163,7 @@ export const newPage = defineTool(args => {
|
|
|
163
163
|
timeout: request.params.timeout,
|
|
164
164
|
}), request.params.allowList, request.params.timeout);
|
|
165
165
|
response.setIncludePages(true);
|
|
166
|
-
response.
|
|
166
|
+
response.setListThirdPartyDeveloperTools();
|
|
167
167
|
},
|
|
168
168
|
};
|
|
169
169
|
});
|
|
@@ -295,7 +295,7 @@ export const navigatePage = definePageTool(args => {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
response.setIncludePages(true);
|
|
298
|
-
response.
|
|
298
|
+
response.setListThirdPartyDeveloperTools();
|
|
299
299
|
response.setListWebMcpTools();
|
|
300
300
|
},
|
|
301
301
|
};
|
|
@@ -6,30 +6,30 @@
|
|
|
6
6
|
import { zod, ajv } from '../third_party/index.js';
|
|
7
7
|
import { ToolCategory } from './categories.js';
|
|
8
8
|
import { definePageTool } from './ToolDefinition.js';
|
|
9
|
-
export const
|
|
10
|
-
name: '
|
|
11
|
-
description: `Lists all
|
|
12
|
-
|
|
13
|
-
Alternatively,
|
|
9
|
+
export const listThirdPartyDeveloperTools = definePageTool({
|
|
10
|
+
name: 'list_3p_developer_tools',
|
|
11
|
+
description: `Lists all third-party developer tools the page exposes for providing runtime information.
|
|
12
|
+
Third-party developer tools can be called via the 'execute_3p_developer_tool()' MCP tool.
|
|
13
|
+
Alternatively, third-party developer tools can be executed by calling 'evaluate_script' and adding the
|
|
14
14
|
following command to the script:
|
|
15
15
|
'window.__dtmcp.executeTool(toolName, params)'
|
|
16
|
-
This might be helpful when the
|
|
17
|
-
|
|
16
|
+
This might be helpful when the third-party developer tools return non-serializable values or when composing
|
|
17
|
+
third-party developer tools with additional functionality.`,
|
|
18
18
|
annotations: {
|
|
19
|
-
category: ToolCategory.
|
|
19
|
+
category: ToolCategory.THIRD_PARTY,
|
|
20
20
|
readOnlyHint: true,
|
|
21
21
|
},
|
|
22
22
|
schema: {},
|
|
23
23
|
blockedByDialog: false,
|
|
24
24
|
handler: async (_request, response, _context) => {
|
|
25
|
-
response.
|
|
25
|
+
response.setListThirdPartyDeveloperTools();
|
|
26
26
|
},
|
|
27
27
|
});
|
|
28
|
-
export const
|
|
29
|
-
name: '
|
|
28
|
+
export const executeThirdPartyDeveloperTool = definePageTool({
|
|
29
|
+
name: 'execute_3p_developer_tool',
|
|
30
30
|
description: `Executes a tool exposed by the page.`,
|
|
31
31
|
annotations: {
|
|
32
|
-
category: ToolCategory.
|
|
32
|
+
category: ToolCategory.THIRD_PARTY,
|
|
33
33
|
readOnlyHint: false,
|
|
34
34
|
},
|
|
35
35
|
schema: {
|
|
@@ -58,7 +58,7 @@ export const executeInPageTool = definePageTool({
|
|
|
58
58
|
throw new Error(`Failed to parse params as JSON: ${errorMessage}`);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
const toolGroup = request.page.
|
|
61
|
+
const toolGroup = request.page.getThirdPartyDeveloperTools();
|
|
62
62
|
const tool = toolGroup?.tools.find(t => t.name === toolName);
|
|
63
63
|
if (!tool) {
|
|
64
64
|
throw new Error(`Tool ${toolName} not found`);
|
|
@@ -69,7 +69,7 @@ export const executeInPageTool = definePageTool({
|
|
|
69
69
|
if (!valid) {
|
|
70
70
|
throw new Error(`Invalid parameters for tool ${toolName}: ${ajvInstance.errorsText(validate.errors)}`);
|
|
71
71
|
}
|
|
72
|
-
await request.page.
|
|
72
|
+
await request.page.executeThirdPartyDeveloperTool(toolName, params, response);
|
|
73
73
|
},
|
|
74
74
|
});
|
|
75
|
-
//# sourceMappingURL=
|
|
75
|
+
//# sourceMappingURL=thirdPartyDeveloper.js.map
|
package/build/src/tools/tools.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import * as consoleTools from './console.js';
|
|
7
7
|
import * as emulationTools from './emulation.js';
|
|
8
8
|
import * as extensionTools from './extensions.js';
|
|
9
|
-
import * as inPageTools from './inPage.js';
|
|
10
9
|
import * as inputTools from './input.js';
|
|
11
10
|
import * as lighthouseTools from './lighthouse.js';
|
|
12
11
|
import * as memoryTools from './memory.js';
|
|
@@ -18,6 +17,7 @@ import * as screenshotTools from './screenshot.js';
|
|
|
18
17
|
import * as scriptTools from './script.js';
|
|
19
18
|
import * as slimTools from './slim/tools.js';
|
|
20
19
|
import * as snapshotTools from './snapshot.js';
|
|
20
|
+
import * as thirdPartyDeveloperTools from './thirdPartyDeveloper.js';
|
|
21
21
|
import * as webmcpTools from './webmcp.js';
|
|
22
22
|
export const createTools = (args) => {
|
|
23
23
|
const rawTools = args.slim
|
|
@@ -26,7 +26,6 @@ export const createTools = (args) => {
|
|
|
26
26
|
...Object.values(consoleTools),
|
|
27
27
|
...Object.values(emulationTools),
|
|
28
28
|
...Object.values(extensionTools),
|
|
29
|
-
...Object.values(inPageTools),
|
|
30
29
|
...Object.values(inputTools),
|
|
31
30
|
...Object.values(lighthouseTools),
|
|
32
31
|
...Object.values(memoryTools),
|
|
@@ -37,6 +36,7 @@ export const createTools = (args) => {
|
|
|
37
36
|
...Object.values(screenshotTools),
|
|
38
37
|
...Object.values(scriptTools),
|
|
39
38
|
...Object.values(snapshotTools),
|
|
39
|
+
...Object.values(thirdPartyDeveloperTools),
|
|
40
40
|
...Object.values(webmcpTools),
|
|
41
41
|
];
|
|
42
42
|
const tools = [];
|
|
@@ -10,9 +10,8 @@ export const listWebMcpTools = definePageTool({
|
|
|
10
10
|
name: 'list_webmcp_tools',
|
|
11
11
|
description: `Lists all WebMCP tools the page exposes.`,
|
|
12
12
|
annotations: {
|
|
13
|
-
category: ToolCategory.
|
|
13
|
+
category: ToolCategory.WEBMCP,
|
|
14
14
|
readOnlyHint: true,
|
|
15
|
-
conditions: ['experimentalWebmcp'],
|
|
16
15
|
},
|
|
17
16
|
schema: {},
|
|
18
17
|
blockedByDialog: false,
|
|
@@ -24,9 +23,8 @@ export const executeWebMcpTool = definePageTool({
|
|
|
24
23
|
name: 'execute_webmcp_tool',
|
|
25
24
|
description: `Executes a WebMCP tool exposed by the page.`,
|
|
26
25
|
annotations: {
|
|
27
|
-
category: ToolCategory.
|
|
26
|
+
category: ToolCategory.WEBMCP,
|
|
28
27
|
readOnlyHint: false,
|
|
29
|
-
conditions: ['experimentalWebmcp'],
|
|
30
28
|
},
|
|
31
29
|
schema: {
|
|
32
30
|
toolName: zod.string().describe('The name of the WebMCP tool to execute'),
|
package/build/src/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-devtools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "MCP server for Chrome DevTools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"mcpName": "io.github.ChromeDevTools/chrome-devtools-mcp",
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@eslint/js": "^9.35.0",
|
|
51
|
-
"@google/genai": "^
|
|
51
|
+
"@google/genai": "^2.0.1",
|
|
52
52
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
53
53
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
54
54
|
"@rollup/plugin-json": "^6.1.0",
|
|
@@ -62,22 +62,21 @@
|
|
|
62
62
|
"@types/yargs": "^17.0.33",
|
|
63
63
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
64
64
|
"@typescript-eslint/parser": "^8.43.0",
|
|
65
|
-
"chrome-devtools-frontend": "1.0.
|
|
65
|
+
"chrome-devtools-frontend": "1.0.1626840",
|
|
66
66
|
"core-js": "3.49.0",
|
|
67
67
|
"debug": "4.4.3",
|
|
68
68
|
"eslint": "^9.35.0",
|
|
69
69
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
70
70
|
"eslint-plugin-import": "^2.32.0",
|
|
71
71
|
"globals": "^17.0.0",
|
|
72
|
-
"lighthouse": "13.
|
|
72
|
+
"lighthouse": "13.3.0",
|
|
73
73
|
"prettier": "^3.6.2",
|
|
74
|
-
"puppeteer": "24.
|
|
75
|
-
"rollup": "4.60.
|
|
74
|
+
"puppeteer": "24.43.0",
|
|
75
|
+
"rollup": "4.60.3",
|
|
76
76
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
77
77
|
"rollup-plugin-license": "^3.6.0",
|
|
78
78
|
"semver": "^7.7.4",
|
|
79
|
-
"sinon": "^
|
|
80
|
-
"tiktoken": "^1.0.22",
|
|
79
|
+
"sinon": "^22.0.0",
|
|
81
80
|
"typescript": "^6.0.2",
|
|
82
81
|
"typescript-eslint": "^8.43.0",
|
|
83
82
|
"urlpattern-polyfill": "^10.1.0",
|