chrome-devtools-mcp 0.9.0 → 0.10.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 +14 -9
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Console.js +1 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +10 -20
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/SegmentedRange.js +1 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +3 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/StringOutputStream.js +1 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/AidaClient.js +19 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/DispatchHttpRequestClient.js +54 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +6 -51
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHost.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostAPI.js +32 -29
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +14 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/host.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/CDPConnection.js +17 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +68 -188
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/protocol_client.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +1 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +3 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +3 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +14 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSStyleDeclaration.js +4 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ChildTargetManager.js +5 -33
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Connections.js +9 -46
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +1 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +1 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EnhancedTracesParser.js +17 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +59 -37
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +5 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +102 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +2 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk-meta.js +8 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +1 -39
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +58 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +46 -45
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js +10 -25
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/cpu_profile/ProfileTreeModel.js +6 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +12 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +3 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/mcp/mcp.js +14 -0
- package/build/src/DevToolsConnectionAdapter.js +1 -0
- package/build/src/DevtoolsUtils.js +44 -0
- package/build/src/McpContext.js +117 -7
- package/build/src/McpResponse.js +32 -21
- package/build/src/PageCollector.js +21 -9
- package/build/src/browser.js +8 -8
- package/build/src/cli.js +8 -3
- package/build/src/formatters/networkFormatter.js +2 -2
- package/build/src/formatters/snapshotFormatter.js +18 -6
- package/build/src/main.js +7 -2
- package/build/src/third_party/THIRD_PARTY_NOTICES +72 -52
- package/build/src/third_party/index.js +12684 -6052
- package/build/src/tools/emulation.js +37 -44
- package/build/src/tools/input.js +36 -6
- package/build/src/tools/network.js +27 -5
- package/build/src/tools/pages.js +59 -33
- package/build/src/tools/performance.js +5 -2
- package/build/src/tools/screenshot.js +2 -1
- package/build/src/tools/snapshot.js +13 -4
- package/build/src/trace-processing/parse.js +6 -16
- package/build/src/utils/keyboard.js +291 -0
- package/package.json +7 -6
package/build/src/McpContext.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import fs from 'node:fs/promises';
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
+
import { extractUrlLikeFromDevToolsTitle, urlsEqual } from './DevtoolsUtils.js';
|
|
9
10
|
import { NetworkCollector, PageCollector } from './PageCollector.js';
|
|
10
11
|
import { Locator } from './third_party/index.js';
|
|
11
12
|
import { listPages } from './tools/pages.js';
|
|
@@ -44,6 +45,7 @@ export class McpContext {
|
|
|
44
45
|
logger;
|
|
45
46
|
// The most recent page state.
|
|
46
47
|
#pages = [];
|
|
48
|
+
#pageToDevToolsPage = new Map();
|
|
47
49
|
#selectedPageIdx = 0;
|
|
48
50
|
// The most recent snapshot.
|
|
49
51
|
#textSnapshot = null;
|
|
@@ -56,11 +58,13 @@ export class McpContext {
|
|
|
56
58
|
#nextSnapshotId = 1;
|
|
57
59
|
#traceResults = [];
|
|
58
60
|
#locatorClass;
|
|
59
|
-
|
|
61
|
+
#options;
|
|
62
|
+
constructor(browser, logger, options, locatorClass) {
|
|
60
63
|
this.browser = browser;
|
|
61
64
|
this.logger = logger;
|
|
62
65
|
this.#locatorClass = locatorClass;
|
|
63
|
-
this.#
|
|
66
|
+
this.#options = options;
|
|
67
|
+
this.#networkCollector = new NetworkCollector(this.browser, undefined, this.#options.experimentalIncludeAllPages);
|
|
64
68
|
this.#consoleCollector = new PageCollector(this.browser, collect => {
|
|
65
69
|
return {
|
|
66
70
|
console: event => {
|
|
@@ -77,7 +81,7 @@ export class McpContext {
|
|
|
77
81
|
}
|
|
78
82
|
},
|
|
79
83
|
};
|
|
80
|
-
});
|
|
84
|
+
}, this.#options.experimentalIncludeAllPages);
|
|
81
85
|
}
|
|
82
86
|
async #init() {
|
|
83
87
|
await this.createPagesSnapshot();
|
|
@@ -85,13 +89,47 @@ export class McpContext {
|
|
|
85
89
|
await this.#networkCollector.init();
|
|
86
90
|
await this.#consoleCollector.init();
|
|
87
91
|
}
|
|
88
|
-
static async from(browser, logger,
|
|
92
|
+
static async from(browser, logger, opts,
|
|
89
93
|
/* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
|
|
90
94
|
locatorClass = Locator) {
|
|
91
|
-
const context = new McpContext(browser, logger, locatorClass);
|
|
95
|
+
const context = new McpContext(browser, logger, opts, locatorClass);
|
|
92
96
|
await context.#init();
|
|
93
97
|
return context;
|
|
94
98
|
}
|
|
99
|
+
resolveCdpRequestId(cdpRequestId) {
|
|
100
|
+
const selectedPage = this.getSelectedPage();
|
|
101
|
+
if (!cdpRequestId) {
|
|
102
|
+
this.logger('no network request');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const request = this.#networkCollector.find(selectedPage, request => {
|
|
106
|
+
// @ts-expect-error id is internal.
|
|
107
|
+
return request.id === cdpRequestId;
|
|
108
|
+
});
|
|
109
|
+
if (!request) {
|
|
110
|
+
this.logger('no network request for ' + cdpRequestId);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
return this.#networkCollector.getIdForResource(request);
|
|
114
|
+
}
|
|
115
|
+
resolveCdpElementId(cdpBackendNodeId) {
|
|
116
|
+
if (!cdpBackendNodeId) {
|
|
117
|
+
this.logger('no cdpBackendNodeId');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// TODO: index by backendNodeId instead.
|
|
121
|
+
const queue = [this.#textSnapshot?.root];
|
|
122
|
+
while (queue.length) {
|
|
123
|
+
const current = queue.pop();
|
|
124
|
+
if (current.backendNodeId === cdpBackendNodeId) {
|
|
125
|
+
return current.id;
|
|
126
|
+
}
|
|
127
|
+
for (const child of current.children) {
|
|
128
|
+
queue.push(child);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
95
133
|
getNetworkRequests(includePreservedRequests) {
|
|
96
134
|
const page = this.getSelectedPage();
|
|
97
135
|
return this.#networkCollector.getData(page, includePreservedRequests);
|
|
@@ -233,16 +271,84 @@ export class McpContext {
|
|
|
233
271
|
* Creates a snapshot of the pages.
|
|
234
272
|
*/
|
|
235
273
|
async createPagesSnapshot() {
|
|
236
|
-
|
|
274
|
+
const allPages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
|
|
275
|
+
this.#pages = allPages.filter(page => {
|
|
276
|
+
// If we allow debugging DevTools windows, return all pages.
|
|
277
|
+
// If we are in regular mode, the user should only see non-DevTools page.
|
|
278
|
+
return (this.#options.experimentalDevToolsDebugging ||
|
|
279
|
+
!page.url().startsWith('devtools://'));
|
|
280
|
+
});
|
|
281
|
+
await this.detectOpenDevToolsWindows();
|
|
237
282
|
return this.#pages;
|
|
238
283
|
}
|
|
284
|
+
async detectOpenDevToolsWindows() {
|
|
285
|
+
this.logger('Detecting open DevTools windows');
|
|
286
|
+
const pages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
|
|
287
|
+
this.#pageToDevToolsPage = new Map();
|
|
288
|
+
for (const devToolsPage of pages) {
|
|
289
|
+
if (devToolsPage.url().startsWith('devtools://')) {
|
|
290
|
+
try {
|
|
291
|
+
this.logger('Calling getTargetInfo for ' + devToolsPage.url());
|
|
292
|
+
const data = await devToolsPage
|
|
293
|
+
// @ts-expect-error no types for _client().
|
|
294
|
+
._client()
|
|
295
|
+
.send('Target.getTargetInfo');
|
|
296
|
+
const devtoolsPageTitle = data.targetInfo.title;
|
|
297
|
+
const urlLike = extractUrlLikeFromDevToolsTitle(devtoolsPageTitle);
|
|
298
|
+
if (!urlLike) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
// TODO: lookup without a loop.
|
|
302
|
+
for (const page of this.#pages) {
|
|
303
|
+
if (urlsEqual(page.url(), urlLike)) {
|
|
304
|
+
this.#pageToDevToolsPage.set(page, devToolsPage);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.logger('Issue occurred while trying to find DevTools', error);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
239
314
|
getPages() {
|
|
240
315
|
return this.#pages;
|
|
241
316
|
}
|
|
317
|
+
getDevToolsPage(page) {
|
|
318
|
+
return this.#pageToDevToolsPage.get(page);
|
|
319
|
+
}
|
|
320
|
+
async getDevToolsData() {
|
|
321
|
+
try {
|
|
322
|
+
this.logger('Getting DevTools UI data');
|
|
323
|
+
const selectedPage = this.getSelectedPage();
|
|
324
|
+
const devtoolsPage = this.getDevToolsPage(selectedPage);
|
|
325
|
+
if (!devtoolsPage) {
|
|
326
|
+
this.logger('No DevTools page detected');
|
|
327
|
+
return {};
|
|
328
|
+
}
|
|
329
|
+
const { cdpRequestId, cdpBackendNodeId } = await devtoolsPage.evaluate(async () => {
|
|
330
|
+
// @ts-expect-error no types
|
|
331
|
+
const UI = await import('/bundled/ui/legacy/legacy.js');
|
|
332
|
+
// @ts-expect-error no types
|
|
333
|
+
const SDK = await import('/bundled/core/sdk/sdk.js');
|
|
334
|
+
const request = UI.Context.Context.instance().flavor(SDK.NetworkRequest.NetworkRequest);
|
|
335
|
+
const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
|
|
336
|
+
return {
|
|
337
|
+
cdpRequestId: request?.requestId(),
|
|
338
|
+
cdpBackendNodeId: node?.backendNodeId(),
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
return { cdpBackendNodeId, cdpRequestId };
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
this.logger('error getting devtools data', err);
|
|
345
|
+
}
|
|
346
|
+
return {};
|
|
347
|
+
}
|
|
242
348
|
/**
|
|
243
349
|
* Creates a text snapshot of a page.
|
|
244
350
|
*/
|
|
245
|
-
async createTextSnapshot(verbose = false) {
|
|
351
|
+
async createTextSnapshot(verbose = false, devtoolsData = undefined) {
|
|
246
352
|
const page = this.getSelectedPage();
|
|
247
353
|
const rootNode = await page.accessibility.snapshot({
|
|
248
354
|
includeIframes: true,
|
|
@@ -281,6 +387,10 @@ export class McpContext {
|
|
|
281
387
|
snapshotId: String(snapshotId),
|
|
282
388
|
idToNode,
|
|
283
389
|
};
|
|
390
|
+
const data = devtoolsData ?? (await this.getDevToolsData());
|
|
391
|
+
if (data?.cdpBackendNodeId) {
|
|
392
|
+
this.#textSnapshot.selectedElementUid = this.resolveCdpElementId(data?.cdpBackendNodeId);
|
|
393
|
+
}
|
|
284
394
|
}
|
|
285
395
|
getTextSnapshot() {
|
|
286
396
|
return this.#textSnapshot;
|
package/build/src/McpResponse.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
import { formatConsoleEventShort, formatConsoleEventVerbose, } from './formatters/consoleFormatter.js';
|
|
2
2
|
import { getFormattedHeaderValue, getFormattedResponseBody, getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
|
|
3
|
-
import {
|
|
3
|
+
import { formatSnapshotNode } from './formatters/snapshotFormatter.js';
|
|
4
4
|
import { handleDialog } from './tools/pages.js';
|
|
5
5
|
import { paginate } from './utils/pagination.js';
|
|
6
6
|
export class McpResponse {
|
|
7
7
|
#includePages = false;
|
|
8
|
-
#
|
|
9
|
-
#includeVerboseSnapshot = false;
|
|
8
|
+
#snapshotParams;
|
|
10
9
|
#attachedNetworkRequestId;
|
|
11
10
|
#attachedConsoleMessageId;
|
|
12
11
|
#textResponseLines = [];
|
|
13
12
|
#images = [];
|
|
14
13
|
#networkRequestsOptions;
|
|
15
14
|
#consoleDataOptions;
|
|
15
|
+
#devToolsData;
|
|
16
|
+
attachDevToolsData(data) {
|
|
17
|
+
this.#devToolsData = data;
|
|
18
|
+
}
|
|
16
19
|
setIncludePages(value) {
|
|
17
20
|
this.#includePages = value;
|
|
18
21
|
}
|
|
19
|
-
|
|
20
|
-
this.#
|
|
21
|
-
|
|
22
|
+
includeSnapshot(params) {
|
|
23
|
+
this.#snapshotParams = params ?? {
|
|
24
|
+
verbose: false,
|
|
25
|
+
};
|
|
22
26
|
}
|
|
23
27
|
setIncludeNetworkRequests(value, options) {
|
|
24
28
|
if (!value) {
|
|
@@ -35,6 +39,7 @@ export class McpResponse {
|
|
|
35
39
|
: undefined,
|
|
36
40
|
resourceTypes: options?.resourceTypes,
|
|
37
41
|
includePreservedRequests: options?.includePreservedRequests,
|
|
42
|
+
networkRequestIdInDevToolsUI: options?.networkRequestIdInDevToolsUI,
|
|
38
43
|
};
|
|
39
44
|
}
|
|
40
45
|
setIncludeConsoleData(value, options) {
|
|
@@ -93,18 +98,26 @@ export class McpResponse {
|
|
|
93
98
|
get images() {
|
|
94
99
|
return this.#images;
|
|
95
100
|
}
|
|
96
|
-
get
|
|
97
|
-
return this.#
|
|
98
|
-
}
|
|
99
|
-
get includeVersboseSnapshot() {
|
|
100
|
-
return this.#includeVerboseSnapshot;
|
|
101
|
+
get snapshotParams() {
|
|
102
|
+
return this.#snapshotParams;
|
|
101
103
|
}
|
|
102
104
|
async handle(toolName, context) {
|
|
103
105
|
if (this.#includePages) {
|
|
104
106
|
await context.createPagesSnapshot();
|
|
105
107
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
let formattedSnapshot;
|
|
109
|
+
if (this.#snapshotParams) {
|
|
110
|
+
await context.createTextSnapshot(this.#snapshotParams.verbose, this.#devToolsData);
|
|
111
|
+
const snapshot = context.getTextSnapshot();
|
|
112
|
+
if (snapshot) {
|
|
113
|
+
if (this.#snapshotParams.filePath) {
|
|
114
|
+
await context.saveFile(new TextEncoder().encode(formatSnapshotNode(snapshot.root, snapshot)), this.#snapshotParams.filePath);
|
|
115
|
+
formattedSnapshot = `Saved snapshot to ${this.#snapshotParams.filePath}.`;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
formattedSnapshot = formatSnapshotNode(snapshot.root, snapshot);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
108
121
|
}
|
|
109
122
|
const bodies = {};
|
|
110
123
|
if (this.#attachedNetworkRequestId) {
|
|
@@ -186,6 +199,7 @@ export class McpResponse {
|
|
|
186
199
|
bodies,
|
|
187
200
|
consoleData,
|
|
188
201
|
consoleListData,
|
|
202
|
+
formattedSnapshot,
|
|
189
203
|
});
|
|
190
204
|
}
|
|
191
205
|
format(toolName, context, data) {
|
|
@@ -222,13 +236,9 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
222
236
|
}
|
|
223
237
|
response.push(...parts);
|
|
224
238
|
}
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const formattedSnapshot = formatA11ySnapshot(snapshot.root);
|
|
229
|
-
response.push('## Page content');
|
|
230
|
-
response.push(formattedSnapshot);
|
|
231
|
-
}
|
|
239
|
+
if (data.formattedSnapshot) {
|
|
240
|
+
response.push('## Page content');
|
|
241
|
+
response.push(data.formattedSnapshot);
|
|
232
242
|
}
|
|
233
243
|
response.push(...this.#formatNetworkRequestData(context, data.bodies));
|
|
234
244
|
response.push(...this.#formatConsoleData(data.consoleData));
|
|
@@ -247,7 +257,8 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
247
257
|
const data = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination);
|
|
248
258
|
response.push(...data.info);
|
|
249
259
|
for (const request of data.items) {
|
|
250
|
-
response.push(getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request))
|
|
260
|
+
response.push(getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request), context.getNetworkRequestStableId(request) ===
|
|
261
|
+
this.#networkRequestsOptions?.networkRequestIdInDevToolsUI));
|
|
251
262
|
}
|
|
252
263
|
}
|
|
253
264
|
else {
|
|
@@ -18,18 +18,20 @@ export class PageCollector {
|
|
|
18
18
|
#listenersInitializer;
|
|
19
19
|
#listeners = new WeakMap();
|
|
20
20
|
#maxNavigationSaved = 3;
|
|
21
|
+
#includeAllPages;
|
|
21
22
|
/**
|
|
22
23
|
* This maps a Page to a list of navigations with a sub-list
|
|
23
24
|
* of all collected resources.
|
|
24
25
|
* The newer navigations come first.
|
|
25
26
|
*/
|
|
26
27
|
storage = new WeakMap();
|
|
27
|
-
constructor(browser, listeners) {
|
|
28
|
+
constructor(browser, listeners, includeAllPages) {
|
|
28
29
|
this.#browser = browser;
|
|
29
30
|
this.#listenersInitializer = listeners;
|
|
31
|
+
this.#includeAllPages = includeAllPages;
|
|
30
32
|
}
|
|
31
33
|
async init() {
|
|
32
|
-
const pages = await this.#browser.pages();
|
|
34
|
+
const pages = await this.#browser.pages(this.#includeAllPages);
|
|
33
35
|
for (const page of pages) {
|
|
34
36
|
this.#initializePage(page);
|
|
35
37
|
}
|
|
@@ -118,14 +120,24 @@ export class PageCollector {
|
|
|
118
120
|
if (!navigations) {
|
|
119
121
|
throw new Error('No requests found for selected page');
|
|
120
122
|
}
|
|
123
|
+
const item = this.find(page, item => item[stableIdSymbol] === stableId);
|
|
124
|
+
if (item) {
|
|
125
|
+
return item;
|
|
126
|
+
}
|
|
127
|
+
throw new Error('Request not found for selected page');
|
|
128
|
+
}
|
|
129
|
+
find(page, filter) {
|
|
130
|
+
const navigations = this.storage.get(page);
|
|
131
|
+
if (!navigations) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
121
134
|
for (const navigation of navigations) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
135
|
+
const item = navigation.find(filter);
|
|
136
|
+
if (item) {
|
|
137
|
+
return item;
|
|
126
138
|
}
|
|
127
139
|
}
|
|
128
|
-
|
|
140
|
+
return;
|
|
129
141
|
}
|
|
130
142
|
}
|
|
131
143
|
export class NetworkCollector extends PageCollector {
|
|
@@ -135,8 +147,8 @@ export class NetworkCollector extends PageCollector {
|
|
|
135
147
|
collect(req);
|
|
136
148
|
},
|
|
137
149
|
};
|
|
138
|
-
}) {
|
|
139
|
-
super(browser, listeners);
|
|
150
|
+
}, includeAllPages) {
|
|
151
|
+
super(browser, listeners, includeAllPages);
|
|
140
152
|
}
|
|
141
153
|
splitAfterNavigation(page) {
|
|
142
154
|
const navigations = this.storage.get(page) ?? [];
|
package/build/src/browser.js
CHANGED
|
@@ -6,17 +6,15 @@
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
+
import { logger } from './logger.js';
|
|
9
10
|
import { puppeteer } from './third_party/index.js';
|
|
10
11
|
let browser;
|
|
11
|
-
function makeTargetFilter(
|
|
12
|
+
function makeTargetFilter() {
|
|
12
13
|
const ignoredPrefixes = new Set([
|
|
13
14
|
'chrome://',
|
|
14
15
|
'chrome-extension://',
|
|
15
16
|
'chrome-untrusted://',
|
|
16
17
|
]);
|
|
17
|
-
if (!devtools) {
|
|
18
|
-
ignoredPrefixes.add('devtools://');
|
|
19
|
-
}
|
|
20
18
|
return function targetFilter(target) {
|
|
21
19
|
if (target.url() === 'chrome://newtab/') {
|
|
22
20
|
return true;
|
|
@@ -34,9 +32,9 @@ export async function ensureBrowserConnected(options) {
|
|
|
34
32
|
return browser;
|
|
35
33
|
}
|
|
36
34
|
const connectOptions = {
|
|
37
|
-
targetFilter: makeTargetFilter(
|
|
35
|
+
targetFilter: makeTargetFilter(),
|
|
38
36
|
defaultViewport: null,
|
|
39
|
-
handleDevToolsAsPage:
|
|
37
|
+
handleDevToolsAsPage: true,
|
|
40
38
|
};
|
|
41
39
|
if (options.wsEndpoint) {
|
|
42
40
|
connectOptions.browserWSEndpoint = options.wsEndpoint;
|
|
@@ -50,7 +48,9 @@ export async function ensureBrowserConnected(options) {
|
|
|
50
48
|
else {
|
|
51
49
|
throw new Error('Either browserURL or wsEndpoint must be provided');
|
|
52
50
|
}
|
|
51
|
+
logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
|
|
53
52
|
browser = await puppeteer.connect(connectOptions);
|
|
53
|
+
logger('Connected Puppeteer');
|
|
54
54
|
return browser;
|
|
55
55
|
}
|
|
56
56
|
export async function launch(options) {
|
|
@@ -85,7 +85,7 @@ export async function launch(options) {
|
|
|
85
85
|
try {
|
|
86
86
|
const browser = await puppeteer.launch({
|
|
87
87
|
channel: puppeteerChannel,
|
|
88
|
-
targetFilter: makeTargetFilter(
|
|
88
|
+
targetFilter: makeTargetFilter(),
|
|
89
89
|
executablePath,
|
|
90
90
|
defaultViewport: null,
|
|
91
91
|
userDataDir,
|
|
@@ -93,7 +93,7 @@ export async function launch(options) {
|
|
|
93
93
|
headless,
|
|
94
94
|
args,
|
|
95
95
|
acceptInsecureCerts: options.acceptInsecureCerts,
|
|
96
|
-
handleDevToolsAsPage:
|
|
96
|
+
handleDevToolsAsPage: true,
|
|
97
97
|
});
|
|
98
98
|
if (options.logFile) {
|
|
99
99
|
// FIXME: we are probably subscribing too late to catch startup logs. We
|
package/build/src/cli.js
CHANGED
|
@@ -123,6 +123,11 @@ export const cliOptions = {
|
|
|
123
123
|
describe: 'Whether to enable automation over DevTools targets',
|
|
124
124
|
hidden: true,
|
|
125
125
|
},
|
|
126
|
+
experimentalIncludeAllPages: {
|
|
127
|
+
type: 'boolean',
|
|
128
|
+
describe: 'Whether to include all kinds of pages such as webviews or background pages as pages.',
|
|
129
|
+
hidden: true,
|
|
130
|
+
},
|
|
126
131
|
chromeArg: {
|
|
127
132
|
type: 'array',
|
|
128
133
|
describe: 'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
|
|
@@ -130,17 +135,17 @@ export const cliOptions = {
|
|
|
130
135
|
categoryEmulation: {
|
|
131
136
|
type: 'boolean',
|
|
132
137
|
default: true,
|
|
133
|
-
describe: 'Set to false to
|
|
138
|
+
describe: 'Set to false to exclude tools related to emulation.',
|
|
134
139
|
},
|
|
135
140
|
categoryPerformance: {
|
|
136
141
|
type: 'boolean',
|
|
137
142
|
default: true,
|
|
138
|
-
describe: 'Set to false to
|
|
143
|
+
describe: 'Set to false to exclude tools related to performance.',
|
|
139
144
|
},
|
|
140
145
|
categoryNetwork: {
|
|
141
146
|
type: 'boolean',
|
|
142
147
|
default: true,
|
|
143
|
-
describe: 'Set to false to
|
|
148
|
+
describe: 'Set to false to exclude tools related to network.',
|
|
144
149
|
},
|
|
145
150
|
};
|
|
146
151
|
export function parseArguments(version, argv = process.argv) {
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { isUtf8 } from 'node:buffer';
|
|
7
7
|
const BODY_CONTEXT_SIZE_LIMIT = 10000;
|
|
8
|
-
export function getShortDescriptionForRequest(request, id) {
|
|
8
|
+
export function getShortDescriptionForRequest(request, id, selectedInDevToolsUI = false) {
|
|
9
9
|
// TODO truncate the URL
|
|
10
|
-
return `reqid=${id} ${request.method()} ${request.url()} ${getStatusFromRequest(request)}`;
|
|
10
|
+
return `reqid=${id} ${request.method()} ${request.url()} ${getStatusFromRequest(request)}${selectedInDevToolsUI ? ` [selected in the DevTools Network panel]` : ''}`;
|
|
11
11
|
}
|
|
12
12
|
export function getStatusFromRequest(request) {
|
|
13
13
|
const httpResponse = request.response();
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function formatSnapshotNode(root, snapshot, depth = 0) {
|
|
2
2
|
let result = '';
|
|
3
|
-
const attributes = getAttributes(
|
|
4
|
-
const line = ' '.repeat(depth * 2) +
|
|
3
|
+
const attributes = getAttributes(root);
|
|
4
|
+
const line = ' '.repeat(depth * 2) +
|
|
5
|
+
attributes.join(' ') +
|
|
6
|
+
(root.id === snapshot?.selectedElementUid
|
|
7
|
+
? ' [selected in the DevTools Elements panel]'
|
|
8
|
+
: '') +
|
|
9
|
+
'\n';
|
|
5
10
|
result += line;
|
|
6
|
-
for (const child of
|
|
7
|
-
result +=
|
|
11
|
+
for (const child of root.children) {
|
|
12
|
+
result += formatSnapshotNode(child, snapshot, depth + 1);
|
|
8
13
|
}
|
|
9
14
|
return result;
|
|
10
15
|
}
|
|
@@ -19,7 +24,14 @@ function getAttributes(serializedAXNodeRoot) {
|
|
|
19
24
|
if (serializedAXNodeRoot.name) {
|
|
20
25
|
attributes.push(`"${serializedAXNodeRoot.name}"`);
|
|
21
26
|
}
|
|
22
|
-
const excluded = new Set([
|
|
27
|
+
const excluded = new Set([
|
|
28
|
+
'id',
|
|
29
|
+
'role',
|
|
30
|
+
'name',
|
|
31
|
+
'elementHandle',
|
|
32
|
+
'children',
|
|
33
|
+
'backendNodeId',
|
|
34
|
+
]);
|
|
23
35
|
const booleanPropertyMap = {
|
|
24
36
|
disabled: 'disableable',
|
|
25
37
|
expanded: 'expandable',
|
package/build/src/main.js
CHANGED
|
@@ -23,7 +23,7 @@ import * as scriptTools from './tools/script.js';
|
|
|
23
23
|
import * as snapshotTools from './tools/snapshot.js';
|
|
24
24
|
// If moved update release-please config
|
|
25
25
|
// x-release-please-start-version
|
|
26
|
-
const VERSION = '0.
|
|
26
|
+
const VERSION = '0.10.0';
|
|
27
27
|
// x-release-please-end
|
|
28
28
|
export const args = parseArguments(VERSION);
|
|
29
29
|
const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
|
|
@@ -62,7 +62,10 @@ async function getContext() {
|
|
|
62
62
|
devtools,
|
|
63
63
|
});
|
|
64
64
|
if (context?.browser !== browser) {
|
|
65
|
-
context = await McpContext.from(browser, logger
|
|
65
|
+
context = await McpContext.from(browser, logger, {
|
|
66
|
+
experimentalDevToolsDebugging: devtools,
|
|
67
|
+
experimentalIncludeAllPages: args.experimentalIncludeAllPages,
|
|
68
|
+
});
|
|
66
69
|
}
|
|
67
70
|
return context;
|
|
68
71
|
}
|
|
@@ -94,6 +97,8 @@ function registerTool(tool) {
|
|
|
94
97
|
try {
|
|
95
98
|
logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
|
|
96
99
|
const context = await getContext();
|
|
100
|
+
logger(`${tool.name} context: resolved`);
|
|
101
|
+
await context.detectOpenDevToolsWindows();
|
|
97
102
|
const response = new McpResponse();
|
|
98
103
|
await tool.handler({
|
|
99
104
|
params,
|