chrome-devtools-mcp 0.23.0 → 0.24.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/DevToolsConnectionAdapter.js +1 -0
- package/build/src/DevtoolsUtils.js +1 -0
- package/build/src/HeapSnapshotManager.js +16 -0
- package/build/src/McpContext.js +57 -4
- package/build/src/McpPage.js +6 -0
- package/build/src/McpResponse.js +38 -4
- package/build/src/Mutex.js +1 -0
- package/build/src/PageCollector.js +1 -0
- package/build/src/SlimMcpResponse.js +1 -0
- package/build/src/TextSnapshot.js +11 -5
- package/build/src/WaitForHelper.js +6 -0
- package/build/src/bin/check-latest-version.js +1 -0
- package/build/src/bin/chrome-devtools-cli-options.js +206 -46
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +3 -1
- package/build/src/bin/chrome-devtools-mcp-main.js +1 -0
- package/build/src/bin/chrome-devtools-mcp.js +1 -0
- package/build/src/bin/chrome-devtools.js +5 -13
- package/build/src/browser.js +1 -0
- package/build/src/daemon/client.js +4 -2
- package/build/src/daemon/daemon.js +1 -0
- package/build/src/daemon/types.js +1 -0
- package/build/src/daemon/utils.js +1 -0
- package/build/src/formatters/ConsoleFormatter.js +48 -1
- package/build/src/formatters/HeapSnapshotFormatter.js +18 -2
- package/build/src/formatters/IssueFormatter.js +1 -0
- package/build/src/formatters/NetworkFormatter.js +1 -0
- package/build/src/formatters/SnapshotFormatter.js +2 -1
- package/build/src/index.js +114 -51
- package/build/src/issue-descriptions.js +1 -0
- package/build/src/logger.js +1 -0
- package/build/src/polyfill.js +1 -0
- package/build/src/telemetry/ClearcutLogger.js +13 -1
- package/build/src/telemetry/WatchdogClient.js +1 -0
- package/build/src/telemetry/flagUtils.js +1 -0
- package/build/src/telemetry/metricUtils.js +1 -0
- package/build/src/telemetry/persistence.js +1 -0
- package/build/src/telemetry/toolMetricsUtils.js +2 -1
- package/build/src/telemetry/types.js +1 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +1 -0
- package/build/src/telemetry/watchdog/main.js +1 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +5 -5
- package/build/src/third_party/bundled-packages.json +2 -2
- package/build/src/third_party/devtools-formatter-worker.js +2451 -2933
- package/build/src/third_party/devtools-heap-snapshot-worker.js +32 -26
- package/build/src/third_party/index.js +535 -135
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +21717 -20261
- package/build/src/tools/ToolDefinition.js +1 -0
- package/build/src/tools/categories.js +6 -2
- package/build/src/tools/console.js +3 -0
- package/build/src/tools/emulation.js +2 -0
- package/build/src/tools/extensions.js +6 -0
- package/build/src/tools/inPage.js +3 -2
- package/build/src/tools/input.js +13 -2
- package/build/src/tools/lighthouse.js +17 -9
- package/build/src/tools/memory.js +34 -1
- package/build/src/tools/network.js +5 -0
- package/build/src/tools/pages.js +9 -0
- package/build/src/tools/performance.js +6 -0
- package/build/src/tools/screencast.js +6 -2
- package/build/src/tools/screenshot.js +3 -0
- package/build/src/tools/script.js +2 -0
- package/build/src/tools/slim/tools.js +4 -0
- package/build/src/tools/snapshot.js +5 -1
- package/build/src/tools/tools.js +1 -0
- package/build/src/tools/webmcp.js +3 -0
- package/build/src/trace-processing/parse.js +1 -0
- package/build/src/types.js +1 -0
- package/build/src/utils/check-for-updates.js +1 -0
- package/build/src/utils/files.js +5 -10
- package/build/src/utils/id.js +1 -0
- package/build/src/utils/keyboard.js +1 -0
- package/build/src/utils/pagination.js +1 -0
- package/build/src/utils/string.js +1 -0
- package/build/src/utils/types.js +1 -0
- package/build/src/version.js +2 -1
- package/package.json +9 -9
- package/build/src/bin/cliDefinitions.js +0 -621
|
@@ -56,6 +56,17 @@ export class HeapSnapshotManager {
|
|
|
56
56
|
}
|
|
57
57
|
return uid;
|
|
58
58
|
}
|
|
59
|
+
async getNodesByUid(filePath, uid) {
|
|
60
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
61
|
+
const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
|
|
62
|
+
const className = await this.resolveClassKeyFromUid(filePath, uid);
|
|
63
|
+
if (!className) {
|
|
64
|
+
throw new Error(`Class with UID ${uid} not found in heap snapshot`);
|
|
65
|
+
}
|
|
66
|
+
const provider = snapshot.createNodesProviderForClass(className, filter);
|
|
67
|
+
const range = await provider.serializeItemsRange(0, 1);
|
|
68
|
+
return await provider.serializeItemsRange(0, range.totalLength);
|
|
69
|
+
}
|
|
59
70
|
#getCachedSnapshot(filePath) {
|
|
60
71
|
const absolutePath = path.resolve(filePath);
|
|
61
72
|
const cached = this.#snapshots.get(absolutePath);
|
|
@@ -64,6 +75,10 @@ export class HeapSnapshotManager {
|
|
|
64
75
|
}
|
|
65
76
|
return cached;
|
|
66
77
|
}
|
|
78
|
+
async resolveClassKeyFromUid(filePath, uid) {
|
|
79
|
+
const cached = this.#getCachedSnapshot(filePath);
|
|
80
|
+
return cached.uidToClassKey.get(uid);
|
|
81
|
+
}
|
|
67
82
|
async #loadSnapshot(absolutePath) {
|
|
68
83
|
const workerProxy = new DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotWorkerProxy(() => {
|
|
69
84
|
/* noop */
|
|
@@ -92,3 +107,4 @@ export class HeapSnapshotManager {
|
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
109
|
}
|
|
110
|
+
//# sourceMappingURL=HeapSnapshotManager.js.map
|
package/build/src/McpContext.js
CHANGED
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import fs from 'node:fs/promises';
|
|
7
|
+
import os from 'node:os';
|
|
7
8
|
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
10
|
import { UniverseManager } from './DevtoolsUtils.js';
|
|
9
11
|
import { HeapSnapshotManager } from './HeapSnapshotManager.js';
|
|
10
12
|
import { McpPage } from './McpPage.js';
|
|
11
13
|
import { NetworkCollector, ConsoleCollector, } from './PageCollector.js';
|
|
12
|
-
import { Locator } from './third_party/index.js';
|
|
13
|
-
import { PredefinedNetworkConditions } from './third_party/index.js';
|
|
14
|
+
import { Locator, PredefinedNetworkConditions, } from './third_party/index.js';
|
|
14
15
|
import { listPages } from './tools/pages.js';
|
|
15
16
|
import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
|
|
16
|
-
import { ensureExtension,
|
|
17
|
+
import { ensureExtension, getTempFilePath } from './utils/files.js';
|
|
17
18
|
import { getNetworkMultiplierFromString } from './WaitForHelper.js';
|
|
18
19
|
const DEFAULT_TIMEOUT = 5_000;
|
|
19
20
|
const NAVIGATION_TIMEOUT = 10_000;
|
|
@@ -41,6 +42,7 @@ export class McpContext {
|
|
|
41
42
|
#locatorClass;
|
|
42
43
|
#options;
|
|
43
44
|
#heapSnapshotManager = new HeapSnapshotManager();
|
|
45
|
+
#roots = undefined;
|
|
44
46
|
constructor(browser, logger, options, locatorClass) {
|
|
45
47
|
this.browser = browser;
|
|
46
48
|
this.logger = logger;
|
|
@@ -89,6 +91,39 @@ export class McpContext {
|
|
|
89
91
|
await context.#init();
|
|
90
92
|
return context;
|
|
91
93
|
}
|
|
94
|
+
roots() {
|
|
95
|
+
if (this.#roots === undefined) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return [
|
|
99
|
+
...this.#roots,
|
|
100
|
+
{
|
|
101
|
+
uri: pathToFileURL(os.tmpdir()).href,
|
|
102
|
+
name: 'temp',
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
setRoots(roots) {
|
|
107
|
+
this.#roots = roots;
|
|
108
|
+
}
|
|
109
|
+
validatePath(filePath) {
|
|
110
|
+
if (filePath === undefined) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const roots = this.roots();
|
|
114
|
+
if (roots === undefined) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const absolutePath = path.resolve(filePath);
|
|
118
|
+
for (const root of roots) {
|
|
119
|
+
const rootPath = path.resolve(fileURLToPath(root.uri));
|
|
120
|
+
if (absolutePath === rootPath ||
|
|
121
|
+
absolutePath.startsWith(rootPath + path.sep)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
throw new Error(`Access denied: path ${filePath} is not within any of the workspace roots ${JSON.stringify(roots)}.`);
|
|
126
|
+
}
|
|
92
127
|
resolveCdpRequestId(page, cdpRequestId) {
|
|
93
128
|
if (!cdpRequestId) {
|
|
94
129
|
this.logger('no network request');
|
|
@@ -463,9 +498,18 @@ export class McpContext {
|
|
|
463
498
|
return this.#mcpPages.get(page)?.isolatedContextName;
|
|
464
499
|
}
|
|
465
500
|
async saveTemporaryFile(data, filename) {
|
|
466
|
-
|
|
501
|
+
const filepath = await getTempFilePath(filename);
|
|
502
|
+
this.validatePath(filepath);
|
|
503
|
+
try {
|
|
504
|
+
await fs.writeFile(filepath, data);
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
throw new Error('Could not save a file', { cause: err });
|
|
508
|
+
}
|
|
509
|
+
return { filepath };
|
|
467
510
|
}
|
|
468
511
|
async saveFile(data, clientProvidedFilePath, extension) {
|
|
512
|
+
this.validatePath(clientProvidedFilePath);
|
|
469
513
|
try {
|
|
470
514
|
const filePath = ensureExtension(path.resolve(clientProvidedFilePath), extension);
|
|
471
515
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
@@ -518,6 +562,7 @@ export class McpContext {
|
|
|
518
562
|
await this.#networkCollector.init(pages);
|
|
519
563
|
}
|
|
520
564
|
async installExtension(extensionPath) {
|
|
565
|
+
this.validatePath(extensionPath);
|
|
521
566
|
const id = await this.browser.installExtension(extensionPath);
|
|
522
567
|
return id;
|
|
523
568
|
}
|
|
@@ -541,12 +586,20 @@ export class McpContext {
|
|
|
541
586
|
return pptrExtensions.get(id);
|
|
542
587
|
}
|
|
543
588
|
async getHeapSnapshotAggregates(filePath) {
|
|
589
|
+
this.validatePath(filePath);
|
|
544
590
|
return await this.#heapSnapshotManager.getAggregates(filePath);
|
|
545
591
|
}
|
|
546
592
|
async getHeapSnapshotStats(filePath) {
|
|
593
|
+
this.validatePath(filePath);
|
|
547
594
|
return await this.#heapSnapshotManager.getStats(filePath);
|
|
548
595
|
}
|
|
549
596
|
async getHeapSnapshotStaticData(filePath) {
|
|
597
|
+
this.validatePath(filePath);
|
|
550
598
|
return await this.#heapSnapshotManager.getStaticData(filePath);
|
|
551
599
|
}
|
|
600
|
+
async getHeapSnapshotNodesByUid(filePath, uid) {
|
|
601
|
+
this.validatePath(filePath);
|
|
602
|
+
return await this.#heapSnapshotManager.getNodesByUid(filePath, uid);
|
|
603
|
+
}
|
|
552
604
|
}
|
|
605
|
+
//# sourceMappingURL=McpContext.js.map
|
package/build/src/McpPage.js
CHANGED
|
@@ -48,6 +48,11 @@ export class McpPage {
|
|
|
48
48
|
clearDialog() {
|
|
49
49
|
this.#dialog = undefined;
|
|
50
50
|
}
|
|
51
|
+
throwIfDialogOpen() {
|
|
52
|
+
if (this.#dialog) {
|
|
53
|
+
throw new Error(`A dialog is open (${this.#dialog.type()}: ${this.#dialog.message()}).`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
51
56
|
getInPageTools() {
|
|
52
57
|
return this.inPageTools;
|
|
53
58
|
}
|
|
@@ -307,3 +312,4 @@ export class McpPage {
|
|
|
307
312
|
return {};
|
|
308
313
|
}
|
|
309
314
|
}
|
|
315
|
+
//# sourceMappingURL=McpPage.js.map
|
package/build/src/McpResponse.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { ConsoleFormatter } from './formatters/ConsoleFormatter.js';
|
|
7
7
|
import { HeapSnapshotFormatter } from './formatters/HeapSnapshotFormatter.js';
|
|
8
|
+
import { isNodeLike } from './formatters/HeapSnapshotFormatter.js';
|
|
8
9
|
import { IssueFormatter } from './formatters/IssueFormatter.js';
|
|
9
10
|
import { NetworkFormatter } from './formatters/NetworkFormatter.js';
|
|
10
11
|
import { SnapshotFormatter } from './formatters/SnapshotFormatter.js';
|
|
@@ -131,6 +132,7 @@ export class McpResponse {
|
|
|
131
132
|
#args;
|
|
132
133
|
#page;
|
|
133
134
|
#redactNetworkHeaders = true;
|
|
135
|
+
#error;
|
|
134
136
|
constructor(args) {
|
|
135
137
|
this.#args = args;
|
|
136
138
|
}
|
|
@@ -162,7 +164,7 @@ export class McpResponse {
|
|
|
162
164
|
this.#listExtensions = true;
|
|
163
165
|
}
|
|
164
166
|
setListInPageTools() {
|
|
165
|
-
if (this.#args.
|
|
167
|
+
if (this.#args.categoryExperimentalInPage) {
|
|
166
168
|
this.#listInPageTools = true;
|
|
167
169
|
}
|
|
168
170
|
}
|
|
@@ -204,6 +206,9 @@ export class McpResponse {
|
|
|
204
206
|
includePreservedMessages: options?.includePreservedMessages,
|
|
205
207
|
};
|
|
206
208
|
}
|
|
209
|
+
setError(error) {
|
|
210
|
+
this.#error = error;
|
|
211
|
+
}
|
|
207
212
|
attachNetworkRequest(reqId, options) {
|
|
208
213
|
this.#attachedNetworkRequestId = reqId;
|
|
209
214
|
this.#attachedNetworkRequestOptions = options;
|
|
@@ -254,6 +259,9 @@ export class McpResponse {
|
|
|
254
259
|
get consoleMessagesTypes() {
|
|
255
260
|
return this.#consoleDataOptions?.types;
|
|
256
261
|
}
|
|
262
|
+
get error() {
|
|
263
|
+
return this.#error;
|
|
264
|
+
}
|
|
257
265
|
appendResponseLine(value) {
|
|
258
266
|
this.#textResponseLines.push(value);
|
|
259
267
|
}
|
|
@@ -273,6 +281,14 @@ export class McpResponse {
|
|
|
273
281
|
staticData,
|
|
274
282
|
};
|
|
275
283
|
}
|
|
284
|
+
setHeapSnapshotNodes(nodes, options) {
|
|
285
|
+
this.#heapSnapshotOptions = {
|
|
286
|
+
...this.#heapSnapshotOptions,
|
|
287
|
+
include: true,
|
|
288
|
+
nodes,
|
|
289
|
+
pagination: options,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
276
292
|
attachImage(value) {
|
|
277
293
|
this.#images.push(value);
|
|
278
294
|
}
|
|
@@ -455,6 +471,7 @@ export class McpResponse {
|
|
|
455
471
|
lighthouseResult: this.#attachedLighthouseResult,
|
|
456
472
|
inPageTools,
|
|
457
473
|
webmcpTools,
|
|
474
|
+
errorMessage: this.#error?.message,
|
|
458
475
|
});
|
|
459
476
|
}
|
|
460
477
|
format(toolName, context, data) {
|
|
@@ -645,6 +662,17 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
645
662
|
response.push(formatter.toString());
|
|
646
663
|
structuredContent.heapSnapshotData = formatter.toJSON();
|
|
647
664
|
}
|
|
665
|
+
const nodes = this.#heapSnapshotOptions.nodes;
|
|
666
|
+
if (nodes) {
|
|
667
|
+
const sortedItems = nodes.items
|
|
668
|
+
.filter(isNodeLike)
|
|
669
|
+
.sort((a, b) => b.retainedSize - a.retainedSize);
|
|
670
|
+
const paginationData = this.#dataWithPagination(sortedItems, this.#heapSnapshotOptions.pagination);
|
|
671
|
+
response.push(HeapSnapshotFormatter.formatNodes(paginationData.items));
|
|
672
|
+
structuredContent.pagination = paginationData.pagination;
|
|
673
|
+
response.push(...paginationData.info);
|
|
674
|
+
structuredContent.heapSnapshotNodes = paginationData.items;
|
|
675
|
+
}
|
|
648
676
|
}
|
|
649
677
|
if (data.detailedNetworkRequest) {
|
|
650
678
|
response.push(data.detailedNetworkRequest.toStringDetailed());
|
|
@@ -733,16 +761,21 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
733
761
|
const messages = data.consoleMessages ?? [];
|
|
734
762
|
response.push('## Console messages');
|
|
735
763
|
if (messages.length) {
|
|
736
|
-
const
|
|
764
|
+
const grouped = ConsoleFormatter.groupConsecutive(messages);
|
|
765
|
+
const paginationData = this.#dataWithPagination(grouped, this.#consoleDataOptions.pagination);
|
|
737
766
|
structuredContent.pagination = paginationData.pagination;
|
|
738
767
|
response.push(...paginationData.info);
|
|
739
|
-
response.push(...paginationData.items.map(
|
|
740
|
-
structuredContent.consoleMessages = paginationData.items.map(
|
|
768
|
+
response.push(...paginationData.items.map(item => item.toString()));
|
|
769
|
+
structuredContent.consoleMessages = paginationData.items.map(item => item.toJSON());
|
|
741
770
|
}
|
|
742
771
|
else {
|
|
743
772
|
response.push('<no console messages found>');
|
|
744
773
|
}
|
|
745
774
|
}
|
|
775
|
+
if (data.errorMessage) {
|
|
776
|
+
response.push(`Error: ${data.errorMessage}`);
|
|
777
|
+
structuredContent.errorMessage = data.errorMessage;
|
|
778
|
+
}
|
|
746
779
|
const text = {
|
|
747
780
|
type: 'text',
|
|
748
781
|
text: response.join('\n'),
|
|
@@ -804,3 +837,4 @@ function createStructuredPage(page, context) {
|
|
|
804
837
|
}
|
|
805
838
|
return entry;
|
|
806
839
|
}
|
|
840
|
+
//# sourceMappingURL=McpResponse.js.map
|
package/build/src/Mutex.js
CHANGED
|
@@ -160,6 +160,9 @@ export class TextSnapshot {
|
|
|
160
160
|
};
|
|
161
161
|
const findDescendantNodes = async (backendNodeId) => {
|
|
162
162
|
const descendantIds = new Set();
|
|
163
|
+
if (!backendNodeId) {
|
|
164
|
+
return descendantIds;
|
|
165
|
+
}
|
|
163
166
|
try {
|
|
164
167
|
// @ts-expect-error internal API
|
|
165
168
|
const client = page.pptrPage._client();
|
|
@@ -213,6 +216,7 @@ export class TextSnapshot {
|
|
|
213
216
|
if (extraHandles.length) {
|
|
214
217
|
page.extraHandles = extraHandles;
|
|
215
218
|
}
|
|
219
|
+
const reorgInfo = [];
|
|
216
220
|
for (const handle of page.extraHandles) {
|
|
217
221
|
const extraNode = await createExtraNode(handle);
|
|
218
222
|
if (!extraNode) {
|
|
@@ -220,11 +224,13 @@ export class TextSnapshot {
|
|
|
220
224
|
}
|
|
221
225
|
idToNode.set(extraNode.id, extraNode);
|
|
222
226
|
const attachTarget = (await findAncestorNode(handle)) || rootNodeWithId;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
227
|
+
const descendantIds = await findDescendantNodes(extraNode.backendNodeId);
|
|
228
|
+
reorgInfo.push({ extraNode, attachTarget, descendantIds });
|
|
229
|
+
}
|
|
230
|
+
for (const { extraNode, attachTarget, descendantIds } of reorgInfo) {
|
|
231
|
+
const index = moveChildNodes(attachTarget, extraNode, descendantIds);
|
|
232
|
+
attachTarget.children.splice(index, 0, extraNode);
|
|
228
233
|
}
|
|
229
234
|
}
|
|
230
235
|
}
|
|
236
|
+
//# sourceMappingURL=TextSnapshot.js.map
|
|
@@ -104,8 +104,10 @@ export class WaitForHelper {
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
async waitForEventsAfterAction(action, options) {
|
|
107
|
+
let dialogOpened = false;
|
|
107
108
|
if (options?.handleDialog) {
|
|
108
109
|
const dialogHandler = (dialog) => {
|
|
110
|
+
dialogOpened = true;
|
|
109
111
|
if (options.handleDialog === 'dismiss') {
|
|
110
112
|
void dialog.dismiss();
|
|
111
113
|
}
|
|
@@ -142,6 +144,9 @@ export class WaitForHelper {
|
|
|
142
144
|
}
|
|
143
145
|
try {
|
|
144
146
|
await navigationFinished;
|
|
147
|
+
if (dialogOpened) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
145
150
|
// Wait for stable dom after navigation so we execute in
|
|
146
151
|
// the correct context
|
|
147
152
|
await this.waitForStableDom();
|
|
@@ -168,3 +173,4 @@ export function getNetworkMultiplierFromString(condition) {
|
|
|
168
173
|
}
|
|
169
174
|
return 1;
|
|
170
175
|
}
|
|
176
|
+
//# sourceMappingURL=WaitForHelper.js.map
|