@mcp-b/chrome-devtools-mcp 2.3.0 → 2.3.1-beta.20260528050333
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/package.json +1 -1
- package/build/src/DevToolsConnectionAdapter.js +0 -70
- package/build/src/DevtoolsUtils.js +0 -290
- package/build/src/McpContext.js +0 -687
- package/build/src/McpPage.js +0 -95
- package/build/src/McpResponse.js +0 -588
- package/build/src/Mutex.js +0 -37
- package/build/src/PageCollector.js +0 -308
- package/build/src/SlimMcpResponse.js +0 -18
- package/build/src/WaitForHelper.js +0 -135
- package/build/src/bin/chrome-devtools-cli-options.js +0 -651
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +0 -317
- package/build/src/bin/chrome-devtools-mcp-main.js +0 -35
- package/build/src/bin/chrome-devtools-mcp.js +0 -21
- package/build/src/bin/chrome-devtools.js +0 -185
- package/build/src/bin/cliDefinitions.js +0 -615
- package/build/src/browser.js +0 -198
- package/build/src/daemon/client.js +0 -152
- package/build/src/daemon/daemon.js +0 -206
- package/build/src/daemon/types.js +0 -6
- package/build/src/daemon/utils.js +0 -108
- package/build/src/formatters/ConsoleFormatter.js +0 -234
- package/build/src/formatters/IssueFormatter.js +0 -192
- package/build/src/formatters/NetworkFormatter.js +0 -215
- package/build/src/formatters/SnapshotFormatter.js +0 -131
- package/build/src/index.js +0 -202
- package/build/src/issue-descriptions.js +0 -39
- package/build/src/logger.js +0 -36
- package/build/src/polyfill.js +0 -7
- package/build/src/telemetry/ClearcutLogger.js +0 -102
- package/build/src/telemetry/WatchdogClient.js +0 -60
- package/build/src/telemetry/flagUtils.js +0 -45
- package/build/src/telemetry/metricUtils.js +0 -14
- package/build/src/telemetry/persistence.js +0 -53
- package/build/src/telemetry/types.js +0 -33
- package/build/src/telemetry/watchdog/ClearcutSender.js +0 -203
- package/build/src/telemetry/watchdog/main.js +0 -127
- package/build/src/third_party/devtools-formatter-worker.js +0 -7
- package/build/src/third_party/index.js +0 -26
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +0 -54183
- package/build/src/tools/ToolDefinition.js +0 -72
- package/build/src/tools/categories.js +0 -24
- package/build/src/tools/console.js +0 -85
- package/build/src/tools/emulation.js +0 -55
- package/build/src/tools/extensions.js +0 -96
- package/build/src/tools/input.js +0 -368
- package/build/src/tools/lighthouse.js +0 -123
- package/build/src/tools/memory.js +0 -28
- package/build/src/tools/network.js +0 -120
- package/build/src/tools/pages.js +0 -319
- package/build/src/tools/performance.js +0 -190
- package/build/src/tools/screencast.js +0 -79
- package/build/src/tools/screenshot.js +0 -84
- package/build/src/tools/script.js +0 -119
- package/build/src/tools/slim/tools.js +0 -81
- package/build/src/tools/snapshot.js +0 -56
- package/build/src/tools/tools.js +0 -52
- package/build/src/tools/webmcp.js +0 -416
- package/build/src/trace-processing/parse.js +0 -84
- package/build/src/types.js +0 -6
- package/build/src/utils/ExtensionRegistry.js +0 -35
- package/build/src/utils/files.js +0 -19
- package/build/src/utils/keyboard.js +0 -296
- package/build/src/utils/pagination.js +0 -49
- package/build/src/utils/string.js +0 -36
- package/build/src/utils/types.js +0 -6
- package/build/src/version.js +0 -9
|
@@ -1,416 +0,0 @@
|
|
|
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 { defineTool } from './ToolDefinition.js';
|
|
9
|
-
const DEFAULT_INPUT_SCHEMA = {
|
|
10
|
-
type: 'object',
|
|
11
|
-
properties: {},
|
|
12
|
-
};
|
|
13
|
-
function globToRegex(pattern) {
|
|
14
|
-
const escaped = pattern
|
|
15
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
16
|
-
.replace(/\*/g, '.*')
|
|
17
|
-
.replace(/\?/g, '.');
|
|
18
|
-
return new RegExp(`^${escaped}$`, 'i');
|
|
19
|
-
}
|
|
20
|
-
function isJsonObject(value) {
|
|
21
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
22
|
-
}
|
|
23
|
-
function jsonStringify(value) {
|
|
24
|
-
return JSON.stringify(value, null, 2) ?? 'null';
|
|
25
|
-
}
|
|
26
|
-
function normalizeToolMetadata(pageId, tools) {
|
|
27
|
-
return tools.map(tool => ({
|
|
28
|
-
...tool,
|
|
29
|
-
pageId,
|
|
30
|
-
}));
|
|
31
|
-
}
|
|
32
|
-
function summarizeDescription(description) {
|
|
33
|
-
const firstLine = description.split('\n', 1)[0] ?? '';
|
|
34
|
-
const firstSentence = firstLine.split('. ', 1)[0] ?? '';
|
|
35
|
-
const summary = firstSentence || firstLine || description;
|
|
36
|
-
return summary.length > 120 ? `${summary.slice(0, 117)}...` : summary;
|
|
37
|
-
}
|
|
38
|
-
function resolvePage(context, pageId) {
|
|
39
|
-
if (pageId !== undefined) {
|
|
40
|
-
return {
|
|
41
|
-
id: pageId,
|
|
42
|
-
pptrPage: context.getPageById(pageId).pptrPage,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
return context.getSelectedMcpPage();
|
|
46
|
-
}
|
|
47
|
-
async function evaluateListTools(page) {
|
|
48
|
-
return page.evaluate(async (defaultInputSchema) => {
|
|
49
|
-
function isObject(value) {
|
|
50
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
51
|
-
}
|
|
52
|
-
function normalizeCoreSchema(value) {
|
|
53
|
-
return isObject(value) ? value : defaultInputSchema;
|
|
54
|
-
}
|
|
55
|
-
function normalizeTestingSchema(value) {
|
|
56
|
-
if (typeof value !== 'string' || value.length === 0) {
|
|
57
|
-
return defaultInputSchema;
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const parsed = JSON.parse(value);
|
|
61
|
-
return isObject(parsed) ? parsed : defaultInputSchema;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return defaultInputSchema;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const nav = navigator;
|
|
68
|
-
if (nav.modelContext && typeof nav.modelContext.listTools === 'function') {
|
|
69
|
-
const tools = await nav.modelContext.listTools();
|
|
70
|
-
return {
|
|
71
|
-
kind: 'available',
|
|
72
|
-
api: 'modelContext',
|
|
73
|
-
tools: Array.isArray(tools)
|
|
74
|
-
? tools.map(tool => {
|
|
75
|
-
const raw = isObject(tool) ? tool : {};
|
|
76
|
-
return {
|
|
77
|
-
name: typeof raw.name === 'string' ? raw.name : '',
|
|
78
|
-
description: typeof raw.description === 'string' ? raw.description : '',
|
|
79
|
-
inputSchema: normalizeCoreSchema(raw.inputSchema),
|
|
80
|
-
};
|
|
81
|
-
})
|
|
82
|
-
: [],
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
if (nav.modelContextTesting &&
|
|
86
|
-
typeof nav.modelContextTesting.listTools === 'function') {
|
|
87
|
-
const tools = await nav.modelContextTesting.listTools();
|
|
88
|
-
return {
|
|
89
|
-
kind: 'available',
|
|
90
|
-
api: 'modelContextTesting',
|
|
91
|
-
tools: Array.isArray(tools)
|
|
92
|
-
? tools.map(tool => {
|
|
93
|
-
const raw = isObject(tool) ? tool : {};
|
|
94
|
-
return {
|
|
95
|
-
name: typeof raw.name === 'string' ? raw.name : '',
|
|
96
|
-
description: typeof raw.description === 'string' ? raw.description : '',
|
|
97
|
-
inputSchema: normalizeTestingSchema(raw.inputSchema),
|
|
98
|
-
};
|
|
99
|
-
})
|
|
100
|
-
: [],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
kind: 'unavailable',
|
|
105
|
-
message: 'Page does not expose navigator.modelContext.listTools() or navigator.modelContextTesting.listTools().',
|
|
106
|
-
};
|
|
107
|
-
}, DEFAULT_INPUT_SCHEMA);
|
|
108
|
-
}
|
|
109
|
-
async function evaluateCallTool(page, name, args) {
|
|
110
|
-
return page.evaluate(async ({ toolName, toolArgs }) => {
|
|
111
|
-
const nav = navigator;
|
|
112
|
-
try {
|
|
113
|
-
if (nav.modelContext &&
|
|
114
|
-
typeof nav.modelContext.callTool === 'function') {
|
|
115
|
-
return {
|
|
116
|
-
kind: 'success',
|
|
117
|
-
api: 'modelContext',
|
|
118
|
-
value: await nav.modelContext.callTool({
|
|
119
|
-
name: toolName,
|
|
120
|
-
arguments: toolArgs,
|
|
121
|
-
}),
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
if (nav.modelContextTesting &&
|
|
125
|
-
typeof nav.modelContextTesting.executeTool === 'function') {
|
|
126
|
-
const serialized = await nav.modelContextTesting.executeTool(toolName, JSON.stringify(toolArgs));
|
|
127
|
-
if (serialized === null) {
|
|
128
|
-
return {
|
|
129
|
-
kind: 'error',
|
|
130
|
-
message: 'Tool execution was interrupted by navigation or the page became unavailable.',
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
const trimmed = serialized.trim();
|
|
134
|
-
try {
|
|
135
|
-
return {
|
|
136
|
-
kind: 'success',
|
|
137
|
-
api: 'modelContextTesting',
|
|
138
|
-
value: JSON.parse(serialized),
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
// Native modelContextTesting can return a plain text string for
|
|
143
|
-
// simple tool results. Preserve invalid JSON errors for payloads
|
|
144
|
-
// that look like structured data but fail to parse.
|
|
145
|
-
if (trimmed.length > 0 &&
|
|
146
|
-
!['{', '[', '"'].includes(trimmed[0]) &&
|
|
147
|
-
!/^[-\d]/.test(trimmed[0])) {
|
|
148
|
-
return {
|
|
149
|
-
kind: 'success',
|
|
150
|
-
api: 'modelContextTesting',
|
|
151
|
-
value: serialized,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
return {
|
|
155
|
-
kind: 'error',
|
|
156
|
-
message: `Testing tool returned invalid JSON: ${serialized.slice(0, 200)}`,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return {
|
|
161
|
-
kind: 'error',
|
|
162
|
-
message: 'Page does not expose navigator.modelContext.callTool() or navigator.modelContextTesting.executeTool().',
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
return {
|
|
167
|
-
kind: 'error',
|
|
168
|
-
message: error instanceof Error ? error.message : String(error),
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}, {
|
|
172
|
-
toolName: name,
|
|
173
|
-
toolArgs: args,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
function normalizeResource(value) {
|
|
177
|
-
if (!isJsonObject(value) || typeof value.uri !== 'string') {
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
if (typeof value.text === 'string') {
|
|
181
|
-
return {
|
|
182
|
-
uri: value.uri,
|
|
183
|
-
text: value.text,
|
|
184
|
-
mimeType: typeof value.mimeType === 'string' ? value.mimeType : undefined,
|
|
185
|
-
_meta: isJsonObject(value._meta) ? value._meta : undefined,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
if (typeof value.blob === 'string') {
|
|
189
|
-
return {
|
|
190
|
-
uri: value.uri,
|
|
191
|
-
blob: value.blob,
|
|
192
|
-
mimeType: typeof value.mimeType === 'string' ? value.mimeType : undefined,
|
|
193
|
-
_meta: isJsonObject(value._meta) ? value._meta : undefined,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
function normalizeContentBlock(value) {
|
|
199
|
-
if (isJsonObject(value)) {
|
|
200
|
-
if (value.type === 'text' && typeof value.text === 'string') {
|
|
201
|
-
return {
|
|
202
|
-
type: 'text',
|
|
203
|
-
text: value.text,
|
|
204
|
-
annotations: isJsonObject(value.annotations)
|
|
205
|
-
? value.annotations
|
|
206
|
-
: undefined,
|
|
207
|
-
_meta: isJsonObject(value._meta) ? value._meta : undefined,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
if (value.type === 'image' &&
|
|
211
|
-
typeof value.data === 'string' &&
|
|
212
|
-
typeof value.mimeType === 'string') {
|
|
213
|
-
return {
|
|
214
|
-
type: 'image',
|
|
215
|
-
data: value.data,
|
|
216
|
-
mimeType: value.mimeType,
|
|
217
|
-
annotations: isJsonObject(value.annotations)
|
|
218
|
-
? value.annotations
|
|
219
|
-
: undefined,
|
|
220
|
-
_meta: isJsonObject(value._meta) ? value._meta : undefined,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
if (value.type === 'resource') {
|
|
224
|
-
const resource = normalizeResource(value.resource);
|
|
225
|
-
if (resource) {
|
|
226
|
-
return {
|
|
227
|
-
type: 'resource',
|
|
228
|
-
resource,
|
|
229
|
-
annotations: isJsonObject(value.annotations)
|
|
230
|
-
? value.annotations
|
|
231
|
-
: undefined,
|
|
232
|
-
_meta: isJsonObject(value._meta) ? value._meta : undefined,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return {
|
|
238
|
-
type: 'text',
|
|
239
|
-
text: typeof value === 'string' ? value : jsonStringify(value),
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
function normalizeToolResult(value) {
|
|
243
|
-
if (!isJsonObject(value)) {
|
|
244
|
-
return {
|
|
245
|
-
content: [normalizeContentBlock(value)],
|
|
246
|
-
isError: false,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
const content = Array.isArray(value.content)
|
|
250
|
-
? value.content.map(block => normalizeContentBlock(block))
|
|
251
|
-
: [];
|
|
252
|
-
if (!content.length) {
|
|
253
|
-
if (value.structuredContent !== undefined) {
|
|
254
|
-
content.push({
|
|
255
|
-
type: 'text',
|
|
256
|
-
text: jsonStringify(value.structuredContent),
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
content.push({
|
|
261
|
-
type: 'text',
|
|
262
|
-
text: '',
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return {
|
|
267
|
-
content,
|
|
268
|
-
isError: value.isError === true,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
function formatListedTools(tools, summary) {
|
|
272
|
-
if (summary) {
|
|
273
|
-
return tools.map(tool => ({
|
|
274
|
-
name: tool.name,
|
|
275
|
-
description: summarizeDescription(tool.description),
|
|
276
|
-
pageId: tool.pageId,
|
|
277
|
-
}));
|
|
278
|
-
}
|
|
279
|
-
return tools;
|
|
280
|
-
}
|
|
281
|
-
export const listWebMCPTools = defineTool({
|
|
282
|
-
name: 'list_webmcp_tools',
|
|
283
|
-
description: 'List WebMCP tools exposed by the selected page, a specific pageId, or across all open pages. Supports compact summaries and glob filtering.',
|
|
284
|
-
annotations: {
|
|
285
|
-
category: ToolCategory.DEBUGGING,
|
|
286
|
-
readOnlyHint: true,
|
|
287
|
-
},
|
|
288
|
-
schema: {
|
|
289
|
-
pageId: zod
|
|
290
|
-
.number()
|
|
291
|
-
.optional()
|
|
292
|
-
.describe('Targets a specific page by ID. Defaults to the selected page.'),
|
|
293
|
-
allPages: zod
|
|
294
|
-
.boolean()
|
|
295
|
-
.optional()
|
|
296
|
-
.describe('If true, search across all open pages instead of only the selected page.'),
|
|
297
|
-
pattern: zod
|
|
298
|
-
.string()
|
|
299
|
-
.optional()
|
|
300
|
-
.describe('Optional glob pattern to filter tool names, for example "list_*" or "*_entity".'),
|
|
301
|
-
summary: zod
|
|
302
|
-
.boolean()
|
|
303
|
-
.optional()
|
|
304
|
-
.describe('If true, omit input schemas and return compact tool summaries.'),
|
|
305
|
-
},
|
|
306
|
-
handler: async (request, response, context) => {
|
|
307
|
-
const { allPages = false, pageId, pattern, summary = false } = request.params;
|
|
308
|
-
if (allPages && pageId !== undefined) {
|
|
309
|
-
throw new Error('Specify either pageId or allPages, not both.');
|
|
310
|
-
}
|
|
311
|
-
const regex = pattern ? globToRegex(pattern) : null;
|
|
312
|
-
if (!allPages) {
|
|
313
|
-
const page = resolvePage(context, pageId);
|
|
314
|
-
const result = await evaluateListTools(page.pptrPage);
|
|
315
|
-
if (result.kind === 'unavailable') {
|
|
316
|
-
response.appendResponseLine(jsonStringify({
|
|
317
|
-
pageId: page.id,
|
|
318
|
-
count: 0,
|
|
319
|
-
tools: [],
|
|
320
|
-
message: result.message,
|
|
321
|
-
}));
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
let tools = normalizeToolMetadata(page.id, result.tools);
|
|
325
|
-
if (regex) {
|
|
326
|
-
tools = tools.filter(tool => regex.test(tool.name));
|
|
327
|
-
}
|
|
328
|
-
if (!tools.length) {
|
|
329
|
-
response.appendResponseLine(jsonStringify({
|
|
330
|
-
pageId: page.id,
|
|
331
|
-
api: result.api,
|
|
332
|
-
count: 0,
|
|
333
|
-
tools: [],
|
|
334
|
-
message: pattern
|
|
335
|
-
? `No WebMCP tools matched pattern "${pattern}" on page ${page.id}.`
|
|
336
|
-
: 'No WebMCP tools were exposed on the selected page.',
|
|
337
|
-
}));
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
response.appendResponseLine(jsonStringify({
|
|
341
|
-
pageId: page.id,
|
|
342
|
-
api: result.api,
|
|
343
|
-
count: tools.length,
|
|
344
|
-
tools: formatListedTools(tools, summary),
|
|
345
|
-
}));
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const selectedPageId = context.getSelectedMcpPage().id;
|
|
349
|
-
const pages = context
|
|
350
|
-
.getPages()
|
|
351
|
-
.map(pptrPage => {
|
|
352
|
-
const id = context.getPageId(pptrPage);
|
|
353
|
-
return id === undefined ? null : { id, pptrPage };
|
|
354
|
-
})
|
|
355
|
-
.filter(page => page !== null);
|
|
356
|
-
const pageResults = await Promise.all(pages.map(async (page) => ({
|
|
357
|
-
page,
|
|
358
|
-
result: await evaluateListTools(page.pptrPage),
|
|
359
|
-
})));
|
|
360
|
-
const tools = pageResults.flatMap(({ page, result }) => {
|
|
361
|
-
if (result.kind !== 'available') {
|
|
362
|
-
return [];
|
|
363
|
-
}
|
|
364
|
-
return normalizeToolMetadata(page.id, result.tools);
|
|
365
|
-
});
|
|
366
|
-
const filteredTools = regex
|
|
367
|
-
? tools.filter(tool => regex.test(tool.name))
|
|
368
|
-
: tools;
|
|
369
|
-
const unavailablePageIds = pageResults
|
|
370
|
-
.filter(entry => entry.result.kind === 'unavailable')
|
|
371
|
-
.map(entry => entry.page.id);
|
|
372
|
-
response.appendResponseLine(jsonStringify({
|
|
373
|
-
selectedPageId,
|
|
374
|
-
pagesScanned: pages.length,
|
|
375
|
-
count: filteredTools.length,
|
|
376
|
-
tools: formatListedTools(filteredTools, summary),
|
|
377
|
-
message: filteredTools.length > 0
|
|
378
|
-
? undefined
|
|
379
|
-
: pattern
|
|
380
|
-
? `No WebMCP tools matched pattern "${pattern}" across ${pages.length} page(s).`
|
|
381
|
-
: 'No WebMCP tools were exposed across the scanned pages.',
|
|
382
|
-
unavailablePageIds: unavailablePageIds.length > 0 ? unavailablePageIds : undefined,
|
|
383
|
-
}));
|
|
384
|
-
},
|
|
385
|
-
});
|
|
386
|
-
export const callWebMCPTool = defineTool({
|
|
387
|
-
name: 'call_webmcp_tool',
|
|
388
|
-
description: 'Call a WebMCP tool exposed by the selected page or a specific pageId.',
|
|
389
|
-
annotations: {
|
|
390
|
-
category: ToolCategory.DEBUGGING,
|
|
391
|
-
readOnlyHint: false,
|
|
392
|
-
},
|
|
393
|
-
schema: {
|
|
394
|
-
name: zod.string().describe('The WebMCP tool name to invoke.'),
|
|
395
|
-
arguments: zod
|
|
396
|
-
.record(zod.unknown())
|
|
397
|
-
.optional()
|
|
398
|
-
.describe('Tool arguments. Defaults to an empty object.'),
|
|
399
|
-
pageId: zod
|
|
400
|
-
.number()
|
|
401
|
-
.optional()
|
|
402
|
-
.describe('Targets a specific page by ID. Defaults to the selected page.'),
|
|
403
|
-
},
|
|
404
|
-
handler: async (request, response, context) => {
|
|
405
|
-
const page = resolvePage(context, request.params.pageId);
|
|
406
|
-
const result = await evaluateCallTool(page.pptrPage, request.params.name, request.params.arguments ?? {});
|
|
407
|
-
if (result.kind === 'error') {
|
|
408
|
-
throw new Error(result.message);
|
|
409
|
-
}
|
|
410
|
-
const normalized = normalizeToolResult(result.value);
|
|
411
|
-
response.setToolResultError(normalized.isError);
|
|
412
|
-
for (const block of normalized.content) {
|
|
413
|
-
response.appendMcpContent(block);
|
|
414
|
-
}
|
|
415
|
-
},
|
|
416
|
-
});
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
import { logger } from '../logger.js';
|
|
7
|
-
import { DevTools } from '../third_party/index.js';
|
|
8
|
-
const engine = DevTools.TraceEngine.TraceModel.Model.createWithAllHandlers();
|
|
9
|
-
export function traceResultIsSuccess(x) {
|
|
10
|
-
return 'parsedTrace' in x;
|
|
11
|
-
}
|
|
12
|
-
export async function parseRawTraceBuffer(buffer) {
|
|
13
|
-
engine.resetProcessor();
|
|
14
|
-
if (!buffer) {
|
|
15
|
-
return {
|
|
16
|
-
error: 'No buffer was provided.',
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
const asString = new TextDecoder().decode(buffer);
|
|
20
|
-
if (!asString) {
|
|
21
|
-
return {
|
|
22
|
-
error: 'Decoding the trace buffer returned an empty string.',
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
const data = JSON.parse(asString);
|
|
27
|
-
const events = Array.isArray(data) ? data : data.traceEvents;
|
|
28
|
-
await engine.parse(events);
|
|
29
|
-
const parsedTrace = engine.parsedTrace();
|
|
30
|
-
if (!parsedTrace) {
|
|
31
|
-
return {
|
|
32
|
-
error: 'No parsed trace was returned from the trace engine.',
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
const insights = parsedTrace?.insights ?? null;
|
|
36
|
-
return {
|
|
37
|
-
parsedTrace,
|
|
38
|
-
insights,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
catch (e) {
|
|
42
|
-
const errorText = e instanceof Error ? e.message : JSON.stringify(e);
|
|
43
|
-
logger(`Unexpected error parsing trace: ${errorText}`);
|
|
44
|
-
return {
|
|
45
|
-
error: errorText,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const extraFormatDescriptions = `Information on performance traces may contain main thread activity represented as call frames and network requests.
|
|
50
|
-
|
|
51
|
-
${DevTools.PerformanceTraceFormatter.callFrameDataFormatDescription}
|
|
52
|
-
|
|
53
|
-
${DevTools.PerformanceTraceFormatter.networkDataFormatDescription}`;
|
|
54
|
-
export function getTraceSummary(result) {
|
|
55
|
-
const focus = DevTools.AgentFocus.fromParsedTrace(result.parsedTrace);
|
|
56
|
-
const formatter = new DevTools.PerformanceTraceFormatter(focus);
|
|
57
|
-
const summaryText = formatter.formatTraceSummary();
|
|
58
|
-
return `## Summary of Performance trace findings:
|
|
59
|
-
${summaryText}
|
|
60
|
-
|
|
61
|
-
## Details on call tree & network request formats:
|
|
62
|
-
${extraFormatDescriptions}`;
|
|
63
|
-
}
|
|
64
|
-
export function getInsightOutput(result, insightSetId, insightName) {
|
|
65
|
-
if (!result.insights) {
|
|
66
|
-
return {
|
|
67
|
-
error: 'No Performance insights are available for this trace.',
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
const insightSet = result.insights.get(insightSetId);
|
|
71
|
-
if (!insightSet) {
|
|
72
|
-
return {
|
|
73
|
-
error: 'No Performance Insights for the given insight set id. Only use ids given in the "Available insight sets" list.',
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
const matchingInsight = insightName in insightSet.model ? insightSet.model[insightName] : null;
|
|
77
|
-
if (!matchingInsight) {
|
|
78
|
-
return {
|
|
79
|
-
error: `No Insight with the name ${insightName} found. Double check the name you provided is accurate and try again.`,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
const formatter = new DevTools.PerformanceInsightFormatter(DevTools.AgentFocus.fromParsedTrace(result.parsedTrace), matchingInsight);
|
|
83
|
-
return { output: formatter.formatInsight() };
|
|
84
|
-
}
|
package/build/src/types.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
import fs from 'node:fs/promises';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
export class ExtensionRegistry {
|
|
9
|
-
#extensions = new Map();
|
|
10
|
-
async registerExtension(id, extensionPath) {
|
|
11
|
-
const manifestPath = path.join(extensionPath, 'manifest.json');
|
|
12
|
-
const manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
13
|
-
const manifest = JSON.parse(manifestContent);
|
|
14
|
-
const name = manifest.name ?? 'Unknown';
|
|
15
|
-
const version = manifest.version ?? 'Unknown';
|
|
16
|
-
const extension = {
|
|
17
|
-
id,
|
|
18
|
-
name,
|
|
19
|
-
version,
|
|
20
|
-
isEnabled: true,
|
|
21
|
-
path: extensionPath,
|
|
22
|
-
};
|
|
23
|
-
this.#extensions.set(extension.id, extension);
|
|
24
|
-
return extension;
|
|
25
|
-
}
|
|
26
|
-
remove(id) {
|
|
27
|
-
this.#extensions.delete(id);
|
|
28
|
-
}
|
|
29
|
-
list() {
|
|
30
|
-
return Array.from(this.#extensions.values());
|
|
31
|
-
}
|
|
32
|
-
getById(id) {
|
|
33
|
-
return this.#extensions.get(id);
|
|
34
|
-
}
|
|
35
|
-
}
|
package/build/src/utils/files.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
import fs from 'node:fs/promises';
|
|
7
|
-
import os from 'node:os';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
export async function saveTemporaryFile(data, filename) {
|
|
10
|
-
try {
|
|
11
|
-
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'chrome-devtools-mcp-'));
|
|
12
|
-
const filepath = path.join(dir, filename);
|
|
13
|
-
await fs.writeFile(filepath, data);
|
|
14
|
-
return { filepath };
|
|
15
|
-
}
|
|
16
|
-
catch (err) {
|
|
17
|
-
throw new Error('Could not save a file', { cause: err });
|
|
18
|
-
}
|
|
19
|
-
}
|