chrome-devtools-mcp 0.8.1 → 0.9.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 +58 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Gzip.js +8 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Worker.js +10 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/ConnectionTransport.js +12 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +15 -27
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/protocol_client.js +2 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +42 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSRule.js +34 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ChildTargetManager.js +3 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Connections.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +3 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +336 -40
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PreloadingModel.js +56 -13
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +32 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/{models/source_map_scopes → core/sdk}/ScopeTreeCache.js +9 -5
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +48 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +8 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +131 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TargetManager.js +0 -21
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TraceObject.js +9 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1301 -174
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/Deprecation.js +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +8 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +16 -19
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +50 -34
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +2 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +45 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +14 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +5 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/source_map_scopes.js +1 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/Trie.js +8 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +6 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +10 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +4 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserTimingsHandler.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/CLSCulprits.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Cache.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DOMSize.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DocumentLatency.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DuplicatedJavaScript.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/FontDisplay.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ForcedReflow.js +3 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ImageDelivery.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LegacyJavaScript.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ModernHTTP.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/SlowCSSSelector.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ThirdParties.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Viewport.js +2 -1
- package/build/src/DevToolsConnectionAdapter.js +32 -0
- package/build/src/McpContext.js +70 -31
- package/build/src/McpResponse.js +145 -44
- package/build/src/PageCollector.js +110 -26
- package/build/src/WaitForHelper.js +5 -0
- package/build/src/browser.js +16 -4
- package/build/src/cli.js +82 -6
- package/build/src/formatters/consoleFormatter.js +29 -62
- package/build/src/formatters/networkFormatter.js +5 -6
- package/build/src/formatters/snapshotFormatter.js +10 -5
- package/build/src/logger.js +1 -1
- package/build/src/main.js +18 -5
- package/build/src/polyfill.js +2 -2
- package/build/src/third_party/THIRD_PARTY_NOTICES +1393 -0
- package/build/src/third_party/index.js +76159 -0
- package/build/src/tools/ToolDefinition.js +2 -2
- package/build/src/tools/categories.js +17 -9
- package/build/src/tools/console.js +71 -6
- package/build/src/tools/emulation.js +6 -7
- package/build/src/tools/input.js +21 -21
- package/build/src/tools/network.js +18 -10
- package/build/src/tools/pages.js +19 -19
- package/build/src/tools/performance.js +8 -8
- package/build/src/tools/screenshot.js +8 -8
- package/build/src/tools/script.js +29 -15
- package/build/src/tools/snapshot.js +15 -20
- package/build/src/utils/types.js +6 -0
- package/package.json +16 -13
package/build/src/McpResponse.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatConsoleEventShort, formatConsoleEventVerbose, } from './formatters/consoleFormatter.js';
|
|
2
2
|
import { getFormattedHeaderValue, getFormattedResponseBody, getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
|
|
3
3
|
import { formatA11ySnapshot } from './formatters/snapshotFormatter.js';
|
|
4
4
|
import { handleDialog } from './tools/pages.js';
|
|
@@ -6,17 +6,19 @@ import { paginate } from './utils/pagination.js';
|
|
|
6
6
|
export class McpResponse {
|
|
7
7
|
#includePages = false;
|
|
8
8
|
#includeSnapshot = false;
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
#includeVerboseSnapshot = false;
|
|
10
|
+
#attachedNetworkRequestId;
|
|
11
|
+
#attachedConsoleMessageId;
|
|
11
12
|
#textResponseLines = [];
|
|
12
|
-
#formattedConsoleData;
|
|
13
13
|
#images = [];
|
|
14
14
|
#networkRequestsOptions;
|
|
15
|
+
#consoleDataOptions;
|
|
15
16
|
setIncludePages(value) {
|
|
16
17
|
this.#includePages = value;
|
|
17
18
|
}
|
|
18
|
-
setIncludeSnapshot(value) {
|
|
19
|
+
setIncludeSnapshot(value, verbose = false) {
|
|
19
20
|
this.#includeSnapshot = value;
|
|
21
|
+
this.#includeVerboseSnapshot = verbose;
|
|
20
22
|
}
|
|
21
23
|
setIncludeNetworkRequests(value, options) {
|
|
22
24
|
if (!value) {
|
|
@@ -32,16 +34,32 @@ export class McpResponse {
|
|
|
32
34
|
}
|
|
33
35
|
: undefined,
|
|
34
36
|
resourceTypes: options?.resourceTypes,
|
|
37
|
+
includePreservedRequests: options?.includePreservedRequests,
|
|
35
38
|
};
|
|
36
39
|
}
|
|
37
|
-
setIncludeConsoleData(value) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
setIncludeConsoleData(value, options) {
|
|
41
|
+
if (!value) {
|
|
42
|
+
this.#consoleDataOptions = undefined;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.#consoleDataOptions = {
|
|
46
|
+
include: value,
|
|
47
|
+
pagination: options?.pageSize || options?.pageIdx
|
|
48
|
+
? {
|
|
49
|
+
pageSize: options.pageSize,
|
|
50
|
+
pageIdx: options.pageIdx,
|
|
51
|
+
}
|
|
52
|
+
: undefined,
|
|
53
|
+
types: options?.types,
|
|
54
|
+
includePreservedMessages: options?.includePreservedMessages,
|
|
43
55
|
};
|
|
44
56
|
}
|
|
57
|
+
attachNetworkRequest(reqid) {
|
|
58
|
+
this.#attachedNetworkRequestId = reqid;
|
|
59
|
+
}
|
|
60
|
+
attachConsoleMessage(msgid) {
|
|
61
|
+
this.#attachedConsoleMessageId = msgid;
|
|
62
|
+
}
|
|
45
63
|
get includePages() {
|
|
46
64
|
return this.#includePages;
|
|
47
65
|
}
|
|
@@ -49,14 +67,20 @@ export class McpResponse {
|
|
|
49
67
|
return this.#networkRequestsOptions?.include ?? false;
|
|
50
68
|
}
|
|
51
69
|
get includeConsoleData() {
|
|
52
|
-
return this.#
|
|
70
|
+
return this.#consoleDataOptions?.include ?? false;
|
|
53
71
|
}
|
|
54
|
-
get
|
|
55
|
-
return this.#
|
|
72
|
+
get attachedNetworkRequestId() {
|
|
73
|
+
return this.#attachedNetworkRequestId;
|
|
56
74
|
}
|
|
57
75
|
get networkRequestsPageIdx() {
|
|
58
76
|
return this.#networkRequestsOptions?.pagination?.pageIdx;
|
|
59
77
|
}
|
|
78
|
+
get consoleMessagesPageIdx() {
|
|
79
|
+
return this.#consoleDataOptions?.pagination?.pageIdx;
|
|
80
|
+
}
|
|
81
|
+
get consoleMessagesTypes() {
|
|
82
|
+
return this.#consoleDataOptions?.types;
|
|
83
|
+
}
|
|
60
84
|
appendResponseLine(value) {
|
|
61
85
|
this.#textResponseLines.push(value);
|
|
62
86
|
}
|
|
@@ -72,34 +96,99 @@ export class McpResponse {
|
|
|
72
96
|
get includeSnapshot() {
|
|
73
97
|
return this.#includeSnapshot;
|
|
74
98
|
}
|
|
99
|
+
get includeVersboseSnapshot() {
|
|
100
|
+
return this.#includeVerboseSnapshot;
|
|
101
|
+
}
|
|
75
102
|
async handle(toolName, context) {
|
|
76
103
|
if (this.#includePages) {
|
|
77
104
|
await context.createPagesSnapshot();
|
|
78
105
|
}
|
|
79
106
|
if (this.#includeSnapshot) {
|
|
80
|
-
await context.createTextSnapshot();
|
|
107
|
+
await context.createTextSnapshot(this.#includeVerboseSnapshot);
|
|
81
108
|
}
|
|
82
|
-
|
|
83
|
-
if (this.#
|
|
84
|
-
const request = context.
|
|
85
|
-
|
|
86
|
-
await getFormattedRequestBody(request);
|
|
109
|
+
const bodies = {};
|
|
110
|
+
if (this.#attachedNetworkRequestId) {
|
|
111
|
+
const request = context.getNetworkRequestById(this.#attachedNetworkRequestId);
|
|
112
|
+
bodies.requestBody = await getFormattedRequestBody(request);
|
|
87
113
|
const response = request.response();
|
|
88
114
|
if (response) {
|
|
89
|
-
|
|
90
|
-
await getFormattedResponseBody(response);
|
|
115
|
+
bodies.responseBody = await getFormattedResponseBody(response);
|
|
91
116
|
}
|
|
92
117
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
let consoleData;
|
|
119
|
+
if (this.#attachedConsoleMessageId) {
|
|
120
|
+
const message = context.getConsoleMessageById(this.#attachedConsoleMessageId);
|
|
121
|
+
const consoleMessageStableId = this.#attachedConsoleMessageId;
|
|
122
|
+
if ('args' in message) {
|
|
123
|
+
const consoleMessage = message;
|
|
124
|
+
consoleData = {
|
|
125
|
+
consoleMessageStableId,
|
|
126
|
+
type: consoleMessage.type(),
|
|
127
|
+
message: consoleMessage.text(),
|
|
128
|
+
args: await Promise.all(consoleMessage.args().map(async (arg) => {
|
|
129
|
+
const stringArg = await arg.jsonValue().catch(() => {
|
|
130
|
+
// Ignore errors.
|
|
131
|
+
});
|
|
132
|
+
return typeof stringArg === 'object'
|
|
133
|
+
? JSON.stringify(stringArg)
|
|
134
|
+
: String(stringArg);
|
|
135
|
+
})),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
consoleData = {
|
|
140
|
+
consoleMessageStableId,
|
|
141
|
+
type: 'error',
|
|
142
|
+
message: message.message,
|
|
143
|
+
args: [],
|
|
144
|
+
};
|
|
98
145
|
}
|
|
99
146
|
}
|
|
100
|
-
|
|
147
|
+
let consoleListData;
|
|
148
|
+
if (this.#consoleDataOptions?.include) {
|
|
149
|
+
let messages = context.getConsoleData(this.#consoleDataOptions.includePreservedMessages);
|
|
150
|
+
if (this.#consoleDataOptions.types?.length) {
|
|
151
|
+
const normalizedTypes = new Set(this.#consoleDataOptions.types);
|
|
152
|
+
messages = messages.filter(message => {
|
|
153
|
+
if ('type' in message) {
|
|
154
|
+
return normalizedTypes.has(message.type());
|
|
155
|
+
}
|
|
156
|
+
return normalizedTypes.has('error');
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
consoleListData = await Promise.all(messages.map(async (item) => {
|
|
160
|
+
const consoleMessageStableId = context.getConsoleMessageStableId(item);
|
|
161
|
+
if ('args' in item) {
|
|
162
|
+
const consoleMessage = item;
|
|
163
|
+
return {
|
|
164
|
+
consoleMessageStableId,
|
|
165
|
+
type: consoleMessage.type(),
|
|
166
|
+
message: consoleMessage.text(),
|
|
167
|
+
args: await Promise.all(consoleMessage.args().map(async (arg) => {
|
|
168
|
+
const stringArg = await arg.jsonValue().catch(() => {
|
|
169
|
+
// Ignore errors.
|
|
170
|
+
});
|
|
171
|
+
return typeof stringArg === 'object'
|
|
172
|
+
? JSON.stringify(stringArg)
|
|
173
|
+
: String(stringArg);
|
|
174
|
+
})),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
consoleMessageStableId,
|
|
179
|
+
type: 'error',
|
|
180
|
+
message: item.message,
|
|
181
|
+
args: [],
|
|
182
|
+
};
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
return this.format(toolName, context, {
|
|
186
|
+
bodies,
|
|
187
|
+
consoleData,
|
|
188
|
+
consoleListData,
|
|
189
|
+
});
|
|
101
190
|
}
|
|
102
|
-
format(toolName, context) {
|
|
191
|
+
format(toolName, context, data) {
|
|
103
192
|
const response = [`# ${toolName} response`];
|
|
104
193
|
for (const line of this.#textResponseLines) {
|
|
105
194
|
response.push(line);
|
|
@@ -141,9 +230,10 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
141
230
|
response.push(formattedSnapshot);
|
|
142
231
|
}
|
|
143
232
|
}
|
|
144
|
-
response.push(...this.#
|
|
233
|
+
response.push(...this.#formatNetworkRequestData(context, data.bodies));
|
|
234
|
+
response.push(...this.#formatConsoleData(data.consoleData));
|
|
145
235
|
if (this.#networkRequestsOptions?.include) {
|
|
146
|
-
let requests = context.getNetworkRequests();
|
|
236
|
+
let requests = context.getNetworkRequests(this.#networkRequestsOptions?.includePreservedRequests);
|
|
147
237
|
// Apply resource type filtering if specified
|
|
148
238
|
if (this.#networkRequestsOptions.resourceTypes?.length) {
|
|
149
239
|
const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes);
|
|
@@ -157,17 +247,20 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
157
247
|
const data = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination);
|
|
158
248
|
response.push(...data.info);
|
|
159
249
|
for (const request of data.items) {
|
|
160
|
-
response.push(getShortDescriptionForRequest(request));
|
|
250
|
+
response.push(getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request)));
|
|
161
251
|
}
|
|
162
252
|
}
|
|
163
253
|
else {
|
|
164
254
|
response.push('No requests found.');
|
|
165
255
|
}
|
|
166
256
|
}
|
|
167
|
-
if (this.#
|
|
257
|
+
if (this.#consoleDataOptions?.include) {
|
|
258
|
+
const messages = data.consoleListData ?? [];
|
|
168
259
|
response.push('## Console messages');
|
|
169
|
-
if (
|
|
170
|
-
|
|
260
|
+
if (messages.length) {
|
|
261
|
+
const data = this.#dataWithPagination(messages, this.#consoleDataOptions.pagination);
|
|
262
|
+
response.push(...data.info);
|
|
263
|
+
response.push(...data.items.map(message => formatConsoleEventShort(message)));
|
|
171
264
|
}
|
|
172
265
|
else {
|
|
173
266
|
response.push('<no console messages found>');
|
|
@@ -206,22 +299,30 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
206
299
|
items: paginationResult.items,
|
|
207
300
|
};
|
|
208
301
|
}
|
|
209
|
-
#
|
|
302
|
+
#formatConsoleData(data) {
|
|
303
|
+
const response = [];
|
|
304
|
+
if (!data) {
|
|
305
|
+
return response;
|
|
306
|
+
}
|
|
307
|
+
response.push(formatConsoleEventVerbose(data));
|
|
308
|
+
return response;
|
|
309
|
+
}
|
|
310
|
+
#formatNetworkRequestData(context, data) {
|
|
210
311
|
const response = [];
|
|
211
|
-
const
|
|
212
|
-
if (!
|
|
312
|
+
const id = this.#attachedNetworkRequestId;
|
|
313
|
+
if (!id) {
|
|
213
314
|
return response;
|
|
214
315
|
}
|
|
215
|
-
const httpRequest = context.
|
|
316
|
+
const httpRequest = context.getNetworkRequestById(id);
|
|
216
317
|
response.push(`## Request ${httpRequest.url()}`);
|
|
217
318
|
response.push(`Status: ${getStatusFromRequest(httpRequest)}`);
|
|
218
319
|
response.push(`### Request Headers`);
|
|
219
320
|
for (const line of getFormattedHeaderValue(httpRequest.headers())) {
|
|
220
321
|
response.push(line);
|
|
221
322
|
}
|
|
222
|
-
if (
|
|
323
|
+
if (data.requestBody) {
|
|
223
324
|
response.push(`### Request Body`);
|
|
224
|
-
response.push(
|
|
325
|
+
response.push(data.requestBody);
|
|
225
326
|
}
|
|
226
327
|
const httpResponse = httpRequest.response();
|
|
227
328
|
if (httpResponse) {
|
|
@@ -230,9 +331,9 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
230
331
|
response.push(line);
|
|
231
332
|
}
|
|
232
333
|
}
|
|
233
|
-
if (
|
|
334
|
+
if (data.responseBody) {
|
|
234
335
|
response.push(`### Response Body`);
|
|
235
|
-
response.push(
|
|
336
|
+
response.push(data.responseBody);
|
|
236
337
|
}
|
|
237
338
|
const httpFailure = httpRequest.failure();
|
|
238
339
|
if (httpFailure) {
|
|
@@ -244,7 +345,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
244
345
|
response.push(`### Redirect chain`);
|
|
245
346
|
let indent = 0;
|
|
246
347
|
for (const request of redirectChain.reverse()) {
|
|
247
|
-
response.push(`${' '.repeat(indent)}${getShortDescriptionForRequest(request)}`);
|
|
348
|
+
response.push(`${' '.repeat(indent)}${getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request))}`);
|
|
248
349
|
indent++;
|
|
249
350
|
}
|
|
250
351
|
}
|
|
@@ -3,18 +3,30 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
function createIdGenerator() {
|
|
7
|
+
let i = 1;
|
|
8
|
+
return () => {
|
|
9
|
+
if (i === Number.MAX_SAFE_INTEGER) {
|
|
10
|
+
i = 0;
|
|
11
|
+
}
|
|
12
|
+
return i++;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export const stableIdSymbol = Symbol('stableIdSymbol');
|
|
6
16
|
export class PageCollector {
|
|
7
17
|
#browser;
|
|
8
|
-
#
|
|
18
|
+
#listenersInitializer;
|
|
19
|
+
#listeners = new WeakMap();
|
|
20
|
+
#maxNavigationSaved = 3;
|
|
9
21
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
22
|
+
* This maps a Page to a list of navigations with a sub-list
|
|
23
|
+
* of all collected resources.
|
|
24
|
+
* The newer navigations come first.
|
|
13
25
|
*/
|
|
14
26
|
storage = new WeakMap();
|
|
15
|
-
constructor(browser,
|
|
27
|
+
constructor(browser, listeners) {
|
|
16
28
|
this.#browser = browser;
|
|
17
|
-
this.#
|
|
29
|
+
this.#listenersInitializer = listeners;
|
|
18
30
|
}
|
|
19
31
|
async init() {
|
|
20
32
|
const pages = await this.#browser.pages();
|
|
@@ -28,6 +40,13 @@ export class PageCollector {
|
|
|
28
40
|
}
|
|
29
41
|
this.#initializePage(page);
|
|
30
42
|
});
|
|
43
|
+
this.#browser.on('targetdestroyed', async (target) => {
|
|
44
|
+
const page = await target.page();
|
|
45
|
+
if (!page) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.#cleanupPageDestroyed(page);
|
|
49
|
+
});
|
|
31
50
|
}
|
|
32
51
|
addPage(page) {
|
|
33
52
|
this.#initializePage(page);
|
|
@@ -36,36 +55,95 @@ export class PageCollector {
|
|
|
36
55
|
if (this.storage.has(page)) {
|
|
37
56
|
return;
|
|
38
57
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
const idGenerator = createIdGenerator();
|
|
59
|
+
const storedLists = [[]];
|
|
60
|
+
this.storage.set(page, storedLists);
|
|
61
|
+
const listeners = this.#listenersInitializer(value => {
|
|
62
|
+
const withId = value;
|
|
63
|
+
withId[stableIdSymbol] = idGenerator();
|
|
64
|
+
const navigations = this.storage.get(page) ?? [[]];
|
|
65
|
+
navigations[0].push(withId);
|
|
66
|
+
});
|
|
67
|
+
listeners['framenavigated'] = (frame) => {
|
|
68
|
+
// Only split the storage on main frame navigation
|
|
43
69
|
if (frame !== page.mainFrame()) {
|
|
44
70
|
return;
|
|
45
71
|
}
|
|
46
|
-
this.
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
72
|
+
this.splitAfterNavigation(page);
|
|
73
|
+
};
|
|
74
|
+
for (const [name, listener] of Object.entries(listeners)) {
|
|
75
|
+
page.on(name, listener);
|
|
76
|
+
}
|
|
77
|
+
this.#listeners.set(page, listeners);
|
|
51
78
|
}
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
collection.length = 0;
|
|
79
|
+
splitAfterNavigation(page) {
|
|
80
|
+
const navigations = this.storage.get(page);
|
|
81
|
+
if (!navigations) {
|
|
82
|
+
return;
|
|
57
83
|
}
|
|
84
|
+
// Add the latest navigation first
|
|
85
|
+
navigations.unshift([]);
|
|
86
|
+
navigations.splice(this.#maxNavigationSaved);
|
|
58
87
|
}
|
|
59
|
-
|
|
60
|
-
|
|
88
|
+
#cleanupPageDestroyed(page) {
|
|
89
|
+
const listeners = this.#listeners.get(page);
|
|
90
|
+
if (listeners) {
|
|
91
|
+
for (const [name, listener] of Object.entries(listeners)) {
|
|
92
|
+
page.off(name, listener);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.storage.delete(page);
|
|
96
|
+
}
|
|
97
|
+
getData(page, includePreservedData) {
|
|
98
|
+
const navigations = this.storage.get(page);
|
|
99
|
+
if (!navigations) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
if (!includePreservedData) {
|
|
103
|
+
return navigations[0];
|
|
104
|
+
}
|
|
105
|
+
const data = [];
|
|
106
|
+
for (let index = this.#maxNavigationSaved; index >= 0; index--) {
|
|
107
|
+
if (navigations[index]) {
|
|
108
|
+
data.push(...navigations[index]);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return data;
|
|
112
|
+
}
|
|
113
|
+
getIdForResource(resource) {
|
|
114
|
+
return resource[stableIdSymbol] ?? -1;
|
|
115
|
+
}
|
|
116
|
+
getById(page, stableId) {
|
|
117
|
+
const navigations = this.storage.get(page);
|
|
118
|
+
if (!navigations) {
|
|
119
|
+
throw new Error('No requests found for selected page');
|
|
120
|
+
}
|
|
121
|
+
for (const navigation of navigations) {
|
|
122
|
+
for (const collected of navigation) {
|
|
123
|
+
if (collected[stableIdSymbol] === stableId) {
|
|
124
|
+
return collected;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
throw new Error('Request not found for selected page');
|
|
61
129
|
}
|
|
62
130
|
}
|
|
63
131
|
export class NetworkCollector extends PageCollector {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
132
|
+
constructor(browser, listeners = collect => {
|
|
133
|
+
return {
|
|
134
|
+
request: req => {
|
|
135
|
+
collect(req);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}) {
|
|
139
|
+
super(browser, listeners);
|
|
140
|
+
}
|
|
141
|
+
splitAfterNavigation(page) {
|
|
142
|
+
const navigations = this.storage.get(page) ?? [];
|
|
143
|
+
if (!navigations) {
|
|
67
144
|
return;
|
|
68
145
|
}
|
|
146
|
+
const requests = navigations[0];
|
|
69
147
|
const lastRequestIdx = requests.findLastIndex(request => {
|
|
70
148
|
return request.frame() === page.mainFrame()
|
|
71
149
|
? request.isNavigationRequest()
|
|
@@ -74,6 +152,12 @@ export class NetworkCollector extends PageCollector {
|
|
|
74
152
|
// Keep all requests since the last navigation request including that
|
|
75
153
|
// navigation request itself.
|
|
76
154
|
// Keep the reference
|
|
77
|
-
|
|
155
|
+
if (lastRequestIdx !== -1) {
|
|
156
|
+
const fromCurrentNavigation = requests.splice(lastRequestIdx);
|
|
157
|
+
navigations.unshift(fromCurrentNavigation);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
navigations.unshift([]);
|
|
161
|
+
}
|
|
78
162
|
}
|
|
79
163
|
}
|
package/build/src/browser.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
-
import puppeteer from '
|
|
9
|
+
import { puppeteer } from './third_party/index.js';
|
|
10
10
|
let browser;
|
|
11
11
|
function makeTargetFilter(devtools) {
|
|
12
12
|
const ignoredPrefixes = new Set([
|
|
@@ -33,12 +33,24 @@ export async function ensureBrowserConnected(options) {
|
|
|
33
33
|
if (browser?.connected) {
|
|
34
34
|
return browser;
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
const connectOptions = {
|
|
37
37
|
targetFilter: makeTargetFilter(options.devtools),
|
|
38
|
-
browserURL: options.browserURL,
|
|
39
38
|
defaultViewport: null,
|
|
40
39
|
handleDevToolsAsPage: options.devtools,
|
|
41
|
-
}
|
|
40
|
+
};
|
|
41
|
+
if (options.wsEndpoint) {
|
|
42
|
+
connectOptions.browserWSEndpoint = options.wsEndpoint;
|
|
43
|
+
if (options.wsHeaders) {
|
|
44
|
+
connectOptions.headers = options.wsHeaders;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (options.browserURL) {
|
|
48
|
+
connectOptions.browserURL = options.browserURL;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
throw new Error('Either browserURL or wsEndpoint must be provided');
|
|
52
|
+
}
|
|
53
|
+
browser = await puppeteer.connect(connectOptions);
|
|
42
54
|
return browser;
|
|
43
55
|
}
|
|
44
56
|
export async function launch(options) {
|
package/build/src/cli.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import yargs from '
|
|
7
|
-
import { hideBin } from 'yargs/helpers';
|
|
6
|
+
import { yargs, hideBin } from './third_party/index.js';
|
|
8
7
|
export const cliOptions = {
|
|
9
8
|
browserUrl: {
|
|
10
9
|
type: 'string',
|
|
11
10
|
description: 'Connect to a running Chrome instance using port forwarding. For more details see: https://developer.chrome.com/docs/devtools/remote-debugging/local-server.',
|
|
12
11
|
alias: 'u',
|
|
12
|
+
conflicts: 'wsEndpoint',
|
|
13
13
|
coerce: (url) => {
|
|
14
14
|
if (!url) {
|
|
15
15
|
return;
|
|
@@ -23,6 +23,50 @@ export const cliOptions = {
|
|
|
23
23
|
return url;
|
|
24
24
|
},
|
|
25
25
|
},
|
|
26
|
+
wsEndpoint: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.',
|
|
29
|
+
alias: 'w',
|
|
30
|
+
conflicts: 'browserUrl',
|
|
31
|
+
coerce: (url) => {
|
|
32
|
+
if (!url) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const parsed = new URL(url);
|
|
37
|
+
if (parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') {
|
|
38
|
+
throw new Error(`Provided wsEndpoint ${url} must use ws:// or wss:// protocol.`);
|
|
39
|
+
}
|
|
40
|
+
return url;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.message.includes('ws://')) {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Provided wsEndpoint ${url} is not valid URL.`);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
wsHeaders: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Custom headers for WebSocket connection in JSON format (e.g., \'{"Authorization":"Bearer token"}\'). Only works with --wsEndpoint.',
|
|
53
|
+
implies: 'wsEndpoint',
|
|
54
|
+
coerce: (val) => {
|
|
55
|
+
if (!val) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(val);
|
|
60
|
+
if (typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
61
|
+
throw new Error('Headers must be a JSON object');
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw new Error(`Invalid JSON for wsHeaders: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
26
70
|
headless: {
|
|
27
71
|
type: 'boolean',
|
|
28
72
|
description: 'Whether to run in headless (no UI) mode.',
|
|
@@ -31,7 +75,7 @@ export const cliOptions = {
|
|
|
31
75
|
executablePath: {
|
|
32
76
|
type: 'string',
|
|
33
77
|
description: 'Path to custom Chrome executable.',
|
|
34
|
-
conflicts: 'browserUrl',
|
|
78
|
+
conflicts: ['browserUrl', 'wsEndpoint'],
|
|
35
79
|
alias: 'e',
|
|
36
80
|
},
|
|
37
81
|
isolated: {
|
|
@@ -43,7 +87,7 @@ export const cliOptions = {
|
|
|
43
87
|
type: 'string',
|
|
44
88
|
description: 'Specify a different Chrome channel that should be used. The default is the stable channel version.',
|
|
45
89
|
choices: ['stable', 'canary', 'beta', 'dev'],
|
|
46
|
-
conflicts: ['browserUrl', 'executablePath'],
|
|
90
|
+
conflicts: ['browserUrl', 'wsEndpoint', 'executablePath'],
|
|
47
91
|
},
|
|
48
92
|
logFile: {
|
|
49
93
|
type: 'string',
|
|
@@ -83,6 +127,21 @@ export const cliOptions = {
|
|
|
83
127
|
type: 'array',
|
|
84
128
|
describe: 'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
|
|
85
129
|
},
|
|
130
|
+
categoryEmulation: {
|
|
131
|
+
type: 'boolean',
|
|
132
|
+
default: true,
|
|
133
|
+
describe: 'Set to false to exlcude tools related to emulation.',
|
|
134
|
+
},
|
|
135
|
+
categoryPerformance: {
|
|
136
|
+
type: 'boolean',
|
|
137
|
+
default: true,
|
|
138
|
+
describe: 'Set to false to exlcude tools related to performance.',
|
|
139
|
+
},
|
|
140
|
+
categoryNetwork: {
|
|
141
|
+
type: 'boolean',
|
|
142
|
+
default: true,
|
|
143
|
+
describe: 'Set to false to exlcude tools related to network.',
|
|
144
|
+
},
|
|
86
145
|
};
|
|
87
146
|
export function parseArguments(version, argv = process.argv) {
|
|
88
147
|
const yargsInstance = yargs(hideBin(argv))
|
|
@@ -91,7 +150,10 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
91
150
|
.check(args => {
|
|
92
151
|
// We can't set default in the options else
|
|
93
152
|
// Yargs will complain
|
|
94
|
-
if (!args.channel &&
|
|
153
|
+
if (!args.channel &&
|
|
154
|
+
!args.browserUrl &&
|
|
155
|
+
!args.wsEndpoint &&
|
|
156
|
+
!args.executablePath) {
|
|
95
157
|
args.channel = 'stable';
|
|
96
158
|
}
|
|
97
159
|
return true;
|
|
@@ -99,7 +161,15 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
99
161
|
.example([
|
|
100
162
|
[
|
|
101
163
|
'$0 --browserUrl http://127.0.0.1:9222',
|
|
102
|
-
'Connect to an existing browser instance',
|
|
164
|
+
'Connect to an existing browser instance via HTTP',
|
|
165
|
+
],
|
|
166
|
+
[
|
|
167
|
+
'$0 --wsEndpoint ws://127.0.0.1:9222/devtools/browser/abc123',
|
|
168
|
+
'Connect to an existing browser instance via WebSocket',
|
|
169
|
+
],
|
|
170
|
+
[
|
|
171
|
+
`$0 --wsEndpoint ws://127.0.0.1:9222/devtools/browser/abc123 --wsHeaders '{"Authorization":"Bearer token"}'`,
|
|
172
|
+
'Connect via WebSocket with custom headers',
|
|
103
173
|
],
|
|
104
174
|
['$0 --channel beta', 'Use Chrome Beta installed on this system'],
|
|
105
175
|
['$0 --channel canary', 'Use Chrome Canary installed on this system'],
|
|
@@ -115,6 +185,12 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
115
185
|
`$0 --chrome-arg='--no-sandbox' --chrome-arg='--disable-setuid-sandbox'`,
|
|
116
186
|
'Launch Chrome without sandboxes. Use with caution.',
|
|
117
187
|
],
|
|
188
|
+
['$0 --no-category-emulation', 'Disable tools in the emulation category'],
|
|
189
|
+
[
|
|
190
|
+
'$0 --no-category-performance',
|
|
191
|
+
'Disable tools in the performance category',
|
|
192
|
+
],
|
|
193
|
+
['$0 --no-category-network', 'Disable tools in the network category'],
|
|
118
194
|
]);
|
|
119
195
|
return yargsInstance
|
|
120
196
|
.wrap(Math.min(120, yargsInstance.terminalWidth()))
|