chrome-devtools-mcp 0.17.3 → 0.18.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 +27 -2
- package/build/src/McpContext.js +174 -74
- package/build/src/McpResponse.js +11 -2
- package/build/src/SlimMcpResponse.js +18 -0
- package/build/src/browser.js +20 -0
- package/build/src/cli.js +12 -0
- package/build/src/daemon/daemon.js +167 -0
- package/build/src/daemon/utils.js +67 -0
- package/build/src/formatters/ConsoleFormatter.js +106 -105
- package/build/src/formatters/IssueFormatter.js +50 -48
- package/build/src/main.js +16 -11
- package/build/src/third_party/THIRD_PARTY_NOTICES +1 -1
- package/build/src/third_party/bundled-packages.json +2 -2
- package/build/src/third_party/index.js +107 -28
- package/build/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md +2 -2
- package/build/src/tools/emulation.js +1 -83
- package/build/src/tools/input.js +26 -0
- package/build/src/tools/memory.js +29 -0
- package/build/src/tools/pages.js +7 -1
- package/build/src/tools/screencast.js +79 -0
- package/build/src/tools/slim/tools.js +81 -0
- package/build/src/tools/snapshot.js +5 -2
- package/build/src/tools/tools.js +35 -16
- package/build/src/version.js +9 -0
- package/package.json +6 -6
- package/build/src/third_party/issue-descriptions/corsInsecurePrivateNetwork.md +0 -10
- package/build/src/third_party/issue-descriptions/corsPreflightAllowPrivateNetworkError.md +0 -10
- package/build/src/third_party/issue-descriptions/corsPrivateNetworkPermissionDenied.md +0 -10
package/README.md
CHANGED
|
@@ -73,6 +73,21 @@ Add the following config to your MCP client:
|
|
|
73
73
|
> [!NOTE]
|
|
74
74
|
> Using `chrome-devtools-mcp@latest` ensures that your MCP client will always use the latest version of the Chrome DevTools MCP server.
|
|
75
75
|
|
|
76
|
+
If you are intersted in doing only basic browser tasks, use the `--slim` mode:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"chrome-devtools": {
|
|
82
|
+
"command": "npx",
|
|
83
|
+
"args": ["-y", "chrome-devtools-mcp@latest", "--slim", "--headless"]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
See [Slim tool reference](./docs/slim-tool-reference.md).
|
|
90
|
+
|
|
76
91
|
### MCP Client configuration
|
|
77
92
|
|
|
78
93
|
<details>
|
|
@@ -399,7 +414,7 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
|
|
|
399
414
|
|
|
400
415
|
<!-- BEGIN AUTO GENERATED TOOLS -->
|
|
401
416
|
|
|
402
|
-
- **Input automation** (
|
|
417
|
+
- **Input automation** (9 tools)
|
|
403
418
|
- [`click`](docs/tool-reference.md#click)
|
|
404
419
|
- [`drag`](docs/tool-reference.md#drag)
|
|
405
420
|
- [`fill`](docs/tool-reference.md#fill)
|
|
@@ -407,6 +422,7 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
|
|
|
407
422
|
- [`handle_dialog`](docs/tool-reference.md#handle_dialog)
|
|
408
423
|
- [`hover`](docs/tool-reference.md#hover)
|
|
409
424
|
- [`press_key`](docs/tool-reference.md#press_key)
|
|
425
|
+
- [`type_text`](docs/tool-reference.md#type_text)
|
|
410
426
|
- [`upload_file`](docs/tool-reference.md#upload_file)
|
|
411
427
|
- **Navigation automation** (6 tools)
|
|
412
428
|
- [`close_page`](docs/tool-reference.md#close_page)
|
|
@@ -418,10 +434,11 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
|
|
|
418
434
|
- **Emulation** (2 tools)
|
|
419
435
|
- [`emulate`](docs/tool-reference.md#emulate)
|
|
420
436
|
- [`resize_page`](docs/tool-reference.md#resize_page)
|
|
421
|
-
- **Performance** (
|
|
437
|
+
- **Performance** (4 tools)
|
|
422
438
|
- [`performance_analyze_insight`](docs/tool-reference.md#performance_analyze_insight)
|
|
423
439
|
- [`performance_start_trace`](docs/tool-reference.md#performance_start_trace)
|
|
424
440
|
- [`performance_stop_trace`](docs/tool-reference.md#performance_stop_trace)
|
|
441
|
+
- [`take_memory_snapshot`](docs/tool-reference.md#take_memory_snapshot)
|
|
425
442
|
- **Network** (2 tools)
|
|
426
443
|
- [`get_network_request`](docs/tool-reference.md#get_network_request)
|
|
427
444
|
- [`list_network_requests`](docs/tool-reference.md#list_network_requests)
|
|
@@ -495,6 +512,10 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
495
512
|
If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.
|
|
496
513
|
- **Type:** boolean
|
|
497
514
|
|
|
515
|
+
- **`--experimentalScreencast`/ `--experimental-screencast`**
|
|
516
|
+
Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.
|
|
517
|
+
- **Type:** boolean
|
|
518
|
+
|
|
498
519
|
- **`--chromeArg`/ `--chrome-arg`**
|
|
499
520
|
Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
|
|
500
521
|
- **Type:** array
|
|
@@ -528,6 +549,10 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
528
549
|
- **Type:** boolean
|
|
529
550
|
- **Default:** `true`
|
|
530
551
|
|
|
552
|
+
- **`--slim`**
|
|
553
|
+
Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
|
|
554
|
+
- **Type:** boolean
|
|
555
|
+
|
|
531
556
|
<!-- END AUTO GENERATED OPTIONS -->
|
|
532
557
|
|
|
533
558
|
Pass them via the `args` property in the JSON configuration. For example:
|
package/build/src/McpContext.js
CHANGED
|
@@ -9,6 +9,7 @@ import path from 'node:path';
|
|
|
9
9
|
import { extractUrlLikeFromDevToolsTitle, UniverseManager, urlsEqual, } from './DevtoolsUtils.js';
|
|
10
10
|
import { NetworkCollector, ConsoleCollector } from './PageCollector.js';
|
|
11
11
|
import { Locator } from './third_party/index.js';
|
|
12
|
+
import { PredefinedNetworkConditions } from './third_party/index.js';
|
|
12
13
|
import { listPages } from './tools/pages.js';
|
|
13
14
|
import { takeSnapshot } from './tools/snapshot.js';
|
|
14
15
|
import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
|
|
@@ -44,23 +45,24 @@ function getExtensionFromMimeType(mimeType) {
|
|
|
44
45
|
export class McpContext {
|
|
45
46
|
browser;
|
|
46
47
|
logger;
|
|
47
|
-
//
|
|
48
|
+
// Maps LLM-provided isolatedContext name → Puppeteer BrowserContext.
|
|
49
|
+
#isolatedContexts = new Map();
|
|
50
|
+
// Reverse lookup: Page → isolatedContext name (for snapshot labeling).
|
|
51
|
+
// WeakMap so closed pages are garbage-collected automatically.
|
|
52
|
+
#pageToIsolatedContextName = new WeakMap();
|
|
53
|
+
// Auto-generated name counter for when no name is provided.
|
|
54
|
+
#nextIsolatedContextId = 1;
|
|
48
55
|
#pages = [];
|
|
49
56
|
#pageToDevToolsPage = new Map();
|
|
50
57
|
#selectedPage;
|
|
51
|
-
// The most recent snapshot.
|
|
52
58
|
#textSnapshot = null;
|
|
53
59
|
#networkCollector;
|
|
54
60
|
#consoleCollector;
|
|
55
61
|
#devtoolsUniverseManager;
|
|
56
62
|
#extensionRegistry = new ExtensionRegistry();
|
|
57
63
|
#isRunningTrace = false;
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#geolocationMap = new WeakMap();
|
|
61
|
-
#viewportMap = new WeakMap();
|
|
62
|
-
#userAgentMap = new WeakMap();
|
|
63
|
-
#colorSchemeMap = new WeakMap();
|
|
64
|
+
#screenRecorderData = null;
|
|
65
|
+
#emulationSettingsMap = new WeakMap();
|
|
64
66
|
#dialog;
|
|
65
67
|
#pageIdMap = new WeakMap();
|
|
66
68
|
#nextPageId = 1;
|
|
@@ -100,6 +102,10 @@ export class McpContext {
|
|
|
100
102
|
this.#networkCollector.dispose();
|
|
101
103
|
this.#consoleCollector.dispose();
|
|
102
104
|
this.#devtoolsUniverseManager.dispose();
|
|
105
|
+
// Isolated contexts are intentionally not closed here.
|
|
106
|
+
// Either the entire browser will be closed or we disconnect
|
|
107
|
+
// without destroying browser state.
|
|
108
|
+
this.#isolatedContexts.clear();
|
|
103
109
|
}
|
|
104
110
|
static async from(browser, logger, opts,
|
|
105
111
|
/* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
|
|
@@ -163,8 +169,20 @@ export class McpContext {
|
|
|
163
169
|
getConsoleMessageById(id) {
|
|
164
170
|
return this.#consoleCollector.getById(this.getSelectedPage(), id);
|
|
165
171
|
}
|
|
166
|
-
async newPage(background) {
|
|
167
|
-
|
|
172
|
+
async newPage(background, isolatedContextName) {
|
|
173
|
+
let page;
|
|
174
|
+
if (isolatedContextName !== undefined) {
|
|
175
|
+
let ctx = this.#isolatedContexts.get(isolatedContextName);
|
|
176
|
+
if (!ctx) {
|
|
177
|
+
ctx = await this.browser.createBrowserContext();
|
|
178
|
+
this.#isolatedContexts.set(isolatedContextName, ctx);
|
|
179
|
+
}
|
|
180
|
+
page = await ctx.newPage();
|
|
181
|
+
this.#pageToIsolatedContextName.set(page, isolatedContextName);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
page = await this.browser.newPage({ background });
|
|
185
|
+
}
|
|
168
186
|
await this.createPagesSnapshot();
|
|
169
187
|
this.selectPage(page);
|
|
170
188
|
this.#networkCollector.addPage(page);
|
|
@@ -177,84 +195,133 @@ export class McpContext {
|
|
|
177
195
|
}
|
|
178
196
|
const page = this.getPageById(pageId);
|
|
179
197
|
await page.close({ runBeforeUnload: false });
|
|
198
|
+
this.#pageToIsolatedContextName.delete(page);
|
|
180
199
|
}
|
|
181
200
|
getNetworkRequestById(reqid) {
|
|
182
201
|
return this.#networkCollector.getById(this.getSelectedPage(), reqid);
|
|
183
202
|
}
|
|
184
|
-
|
|
203
|
+
async emulate(options) {
|
|
185
204
|
const page = this.getSelectedPage();
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
const currentSettings = this.#emulationSettingsMap.get(page) ?? {};
|
|
206
|
+
const newSettings = { ...currentSettings };
|
|
207
|
+
let timeoutsNeedUpdate = false;
|
|
208
|
+
if (options.networkConditions !== undefined) {
|
|
209
|
+
timeoutsNeedUpdate = true;
|
|
210
|
+
if (options.networkConditions === null ||
|
|
211
|
+
options.networkConditions === 'No emulation') {
|
|
212
|
+
await page.emulateNetworkConditions(null);
|
|
213
|
+
delete newSettings.networkConditions;
|
|
214
|
+
}
|
|
215
|
+
else if (options.networkConditions === 'Offline') {
|
|
216
|
+
await page.emulateNetworkConditions({
|
|
217
|
+
offline: true,
|
|
218
|
+
download: 0,
|
|
219
|
+
upload: 0,
|
|
220
|
+
latency: 0,
|
|
221
|
+
});
|
|
222
|
+
newSettings.networkConditions = 'Offline';
|
|
223
|
+
}
|
|
224
|
+
else if (options.networkConditions in PredefinedNetworkConditions) {
|
|
225
|
+
const networkCondition = PredefinedNetworkConditions[options.networkConditions];
|
|
226
|
+
await page.emulateNetworkConditions(networkCondition);
|
|
227
|
+
newSettings.networkConditions = options.networkConditions;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (options.cpuThrottlingRate !== undefined) {
|
|
231
|
+
timeoutsNeedUpdate = true;
|
|
232
|
+
if (options.cpuThrottlingRate === null) {
|
|
233
|
+
await page.emulateCPUThrottling(1);
|
|
234
|
+
delete newSettings.cpuThrottlingRate;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
await page.emulateCPUThrottling(options.cpuThrottlingRate);
|
|
238
|
+
newSettings.cpuThrottlingRate = options.cpuThrottlingRate;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (options.geolocation !== undefined) {
|
|
242
|
+
if (options.geolocation === null) {
|
|
243
|
+
await page.setGeolocation({ latitude: 0, longitude: 0 });
|
|
244
|
+
delete newSettings.geolocation;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
await page.setGeolocation(options.geolocation);
|
|
248
|
+
newSettings.geolocation = options.geolocation;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (options.userAgent !== undefined) {
|
|
252
|
+
if (options.userAgent === null) {
|
|
253
|
+
await page.setUserAgent({ userAgent: undefined });
|
|
254
|
+
delete newSettings.userAgent;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
await page.setUserAgent({ userAgent: options.userAgent });
|
|
258
|
+
newSettings.userAgent = options.userAgent;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (options.colorScheme !== undefined) {
|
|
262
|
+
if (options.colorScheme === null || options.colorScheme === 'auto') {
|
|
263
|
+
await page.emulateMediaFeatures([
|
|
264
|
+
{ name: 'prefers-color-scheme', value: '' },
|
|
265
|
+
]);
|
|
266
|
+
delete newSettings.colorScheme;
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
await page.emulateMediaFeatures([
|
|
270
|
+
{ name: 'prefers-color-scheme', value: options.colorScheme },
|
|
271
|
+
]);
|
|
272
|
+
newSettings.colorScheme = options.colorScheme;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (options.viewport !== undefined) {
|
|
276
|
+
if (options.viewport === null) {
|
|
277
|
+
await page.setViewport(null);
|
|
278
|
+
delete newSettings.viewport;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const defaults = {
|
|
282
|
+
deviceScaleFactor: 1,
|
|
283
|
+
isMobile: false,
|
|
284
|
+
hasTouch: false,
|
|
285
|
+
isLandscape: false,
|
|
286
|
+
};
|
|
287
|
+
const viewport = { ...defaults, ...options.viewport };
|
|
288
|
+
await page.setViewport(viewport);
|
|
289
|
+
newSettings.viewport = viewport;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (Object.keys(newSettings).length) {
|
|
293
|
+
this.#emulationSettingsMap.set(page, newSettings);
|
|
188
294
|
}
|
|
189
295
|
else {
|
|
190
|
-
this.#
|
|
296
|
+
this.#emulationSettingsMap.delete(page);
|
|
297
|
+
}
|
|
298
|
+
if (timeoutsNeedUpdate) {
|
|
299
|
+
this.#updateSelectedPageTimeouts();
|
|
191
300
|
}
|
|
192
|
-
this.#updateSelectedPageTimeouts();
|
|
193
301
|
}
|
|
194
302
|
getNetworkConditions() {
|
|
195
303
|
const page = this.getSelectedPage();
|
|
196
|
-
return this.#
|
|
197
|
-
}
|
|
198
|
-
setCpuThrottlingRate(rate) {
|
|
199
|
-
const page = this.getSelectedPage();
|
|
200
|
-
this.#cpuThrottlingRateMap.set(page, rate);
|
|
201
|
-
this.#updateSelectedPageTimeouts();
|
|
304
|
+
return this.#emulationSettingsMap.get(page)?.networkConditions ?? null;
|
|
202
305
|
}
|
|
203
306
|
getCpuThrottlingRate() {
|
|
204
307
|
const page = this.getSelectedPage();
|
|
205
|
-
return this.#
|
|
206
|
-
}
|
|
207
|
-
setGeolocation(geolocation) {
|
|
208
|
-
const page = this.getSelectedPage();
|
|
209
|
-
if (geolocation === null) {
|
|
210
|
-
this.#geolocationMap.delete(page);
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
this.#geolocationMap.set(page, geolocation);
|
|
214
|
-
}
|
|
308
|
+
return this.#emulationSettingsMap.get(page)?.cpuThrottlingRate ?? 1;
|
|
215
309
|
}
|
|
216
310
|
getGeolocation() {
|
|
217
311
|
const page = this.getSelectedPage();
|
|
218
|
-
return this.#
|
|
219
|
-
}
|
|
220
|
-
setViewport(viewport) {
|
|
221
|
-
const page = this.getSelectedPage();
|
|
222
|
-
if (viewport === null) {
|
|
223
|
-
this.#viewportMap.delete(page);
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
this.#viewportMap.set(page, viewport);
|
|
227
|
-
}
|
|
312
|
+
return this.#emulationSettingsMap.get(page)?.geolocation ?? null;
|
|
228
313
|
}
|
|
229
314
|
getViewport() {
|
|
230
315
|
const page = this.getSelectedPage();
|
|
231
|
-
return this.#
|
|
232
|
-
}
|
|
233
|
-
setUserAgent(userAgent) {
|
|
234
|
-
const page = this.getSelectedPage();
|
|
235
|
-
if (userAgent === null) {
|
|
236
|
-
this.#userAgentMap.delete(page);
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
this.#userAgentMap.set(page, userAgent);
|
|
240
|
-
}
|
|
316
|
+
return this.#emulationSettingsMap.get(page)?.viewport ?? null;
|
|
241
317
|
}
|
|
242
318
|
getUserAgent() {
|
|
243
319
|
const page = this.getSelectedPage();
|
|
244
|
-
return this.#
|
|
245
|
-
}
|
|
246
|
-
setColorScheme(scheme) {
|
|
247
|
-
const page = this.getSelectedPage();
|
|
248
|
-
if (scheme === null) {
|
|
249
|
-
this.#colorSchemeMap.delete(page);
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
this.#colorSchemeMap.set(page, scheme);
|
|
253
|
-
}
|
|
320
|
+
return this.#emulationSettingsMap.get(page)?.userAgent ?? null;
|
|
254
321
|
}
|
|
255
322
|
getColorScheme() {
|
|
256
323
|
const page = this.getSelectedPage();
|
|
257
|
-
return this.#
|
|
324
|
+
return this.#emulationSettingsMap.get(page)?.colorScheme ?? null;
|
|
258
325
|
}
|
|
259
326
|
setIsRunningPerformanceTrace(x) {
|
|
260
327
|
this.#isRunningTrace = x;
|
|
@@ -262,6 +329,12 @@ export class McpContext {
|
|
|
262
329
|
isRunningPerformanceTrace() {
|
|
263
330
|
return this.#isRunningTrace;
|
|
264
331
|
}
|
|
332
|
+
getScreenRecorder() {
|
|
333
|
+
return this.#screenRecorderData;
|
|
334
|
+
}
|
|
335
|
+
setScreenRecorder(data) {
|
|
336
|
+
this.#screenRecorderData = data;
|
|
337
|
+
}
|
|
265
338
|
isCruxEnabled() {
|
|
266
339
|
return this.#options.performanceCrux;
|
|
267
340
|
}
|
|
@@ -353,19 +426,14 @@ export class McpContext {
|
|
|
353
426
|
});
|
|
354
427
|
}
|
|
355
428
|
}
|
|
356
|
-
/**
|
|
357
|
-
* Creates a snapshot of the pages.
|
|
358
|
-
*/
|
|
359
429
|
async createPagesSnapshot() {
|
|
360
|
-
const allPages = await this
|
|
430
|
+
const allPages = await this.#getAllPages();
|
|
361
431
|
for (const page of allPages) {
|
|
362
432
|
if (!this.#pageIdMap.has(page)) {
|
|
363
433
|
this.#pageIdMap.set(page, this.#nextPageId++);
|
|
364
434
|
}
|
|
365
435
|
}
|
|
366
436
|
this.#pages = allPages.filter(page => {
|
|
367
|
-
// If we allow debugging DevTools windows, return all pages.
|
|
368
|
-
// If we are in regular mode, the user should only see non-DevTools page.
|
|
369
437
|
return (this.#options.experimentalDevToolsDebugging ||
|
|
370
438
|
!page.url().startsWith('devtools://'));
|
|
371
439
|
});
|
|
@@ -376,9 +444,37 @@ export class McpContext {
|
|
|
376
444
|
await this.detectOpenDevToolsWindows();
|
|
377
445
|
return this.#pages;
|
|
378
446
|
}
|
|
447
|
+
async #getAllPages() {
|
|
448
|
+
const defaultCtx = this.browser.defaultBrowserContext();
|
|
449
|
+
const allPages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
|
|
450
|
+
// Build a reverse lookup from BrowserContext instance → name.
|
|
451
|
+
const contextToName = new Map();
|
|
452
|
+
for (const [name, ctx] of this.#isolatedContexts) {
|
|
453
|
+
contextToName.set(ctx, name);
|
|
454
|
+
}
|
|
455
|
+
// Auto-discover BrowserContexts not in our mapping (e.g., externally
|
|
456
|
+
// created incognito contexts) and assign generated names.
|
|
457
|
+
const knownContexts = new Set(this.#isolatedContexts.values());
|
|
458
|
+
for (const ctx of this.browser.browserContexts()) {
|
|
459
|
+
if (ctx !== defaultCtx && !ctx.closed && !knownContexts.has(ctx)) {
|
|
460
|
+
const name = `isolated-context-${this.#nextIsolatedContextId++}`;
|
|
461
|
+
this.#isolatedContexts.set(name, ctx);
|
|
462
|
+
contextToName.set(ctx, name);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Use page.browserContext() to determine each page's context membership.
|
|
466
|
+
for (const page of allPages) {
|
|
467
|
+
const ctx = page.browserContext();
|
|
468
|
+
const name = contextToName.get(ctx);
|
|
469
|
+
if (name) {
|
|
470
|
+
this.#pageToIsolatedContextName.set(page, name);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return allPages;
|
|
474
|
+
}
|
|
379
475
|
async detectOpenDevToolsWindows() {
|
|
380
476
|
this.logger('Detecting open DevTools windows');
|
|
381
|
-
const pages = await this
|
|
477
|
+
const pages = await this.#getAllPages();
|
|
382
478
|
this.#pageToDevToolsPage = new Map();
|
|
383
479
|
for (const devToolsPage of pages) {
|
|
384
480
|
if (devToolsPage.url().startsWith('devtools://')) {
|
|
@@ -409,6 +505,9 @@ export class McpContext {
|
|
|
409
505
|
getPages() {
|
|
410
506
|
return this.#pages;
|
|
411
507
|
}
|
|
508
|
+
getIsolatedContextName(page) {
|
|
509
|
+
return this.#pageToIsolatedContextName.get(page);
|
|
510
|
+
}
|
|
412
511
|
getDevToolsPage(page) {
|
|
413
512
|
return this.#pageToDevToolsPage.get(page);
|
|
414
513
|
}
|
|
@@ -560,10 +659,10 @@ export class McpContext {
|
|
|
560
659
|
waitForTextOnPage(text, timeout) {
|
|
561
660
|
const page = this.getSelectedPage();
|
|
562
661
|
const frames = page.frames();
|
|
563
|
-
let locator = this.#locatorClass.race(frames.flatMap(frame => [
|
|
564
|
-
frame.locator(`aria/${
|
|
565
|
-
frame.locator(`text/${
|
|
566
|
-
]));
|
|
662
|
+
let locator = this.#locatorClass.race(frames.flatMap(frame => text.flatMap(value => [
|
|
663
|
+
frame.locator(`aria/${value}`),
|
|
664
|
+
frame.locator(`text/${value}`),
|
|
665
|
+
])));
|
|
567
666
|
if (timeout) {
|
|
568
667
|
locator = locator.setTimeout(timeout);
|
|
569
668
|
}
|
|
@@ -583,7 +682,8 @@ export class McpContext {
|
|
|
583
682
|
},
|
|
584
683
|
};
|
|
585
684
|
});
|
|
586
|
-
await this.#
|
|
685
|
+
const pages = await this.#getAllPages();
|
|
686
|
+
await this.#networkCollector.init(pages);
|
|
587
687
|
}
|
|
588
688
|
async installExtension(extensionPath) {
|
|
589
689
|
const id = await this.browser.installExtension(extensionPath);
|
package/build/src/McpResponse.js
CHANGED
|
@@ -325,15 +325,24 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
325
325
|
if (this.#includePages) {
|
|
326
326
|
const parts = [`## Pages`];
|
|
327
327
|
for (const page of context.getPages()) {
|
|
328
|
-
|
|
328
|
+
const isolatedContextName = context.getIsolatedContextName(page);
|
|
329
|
+
const contextLabel = isolatedContextName
|
|
330
|
+
? ` isolatedContext=${isolatedContextName}`
|
|
331
|
+
: '';
|
|
332
|
+
parts.push(`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
|
|
329
333
|
}
|
|
330
334
|
response.push(...parts);
|
|
331
335
|
structuredContent.pages = context.getPages().map(page => {
|
|
332
|
-
|
|
336
|
+
const isolatedContextName = context.getIsolatedContextName(page);
|
|
337
|
+
const entry = {
|
|
333
338
|
id: context.getPageId(page),
|
|
334
339
|
url: page.url(),
|
|
335
340
|
selected: context.isPageSelected(page),
|
|
336
341
|
};
|
|
342
|
+
if (isolatedContextName) {
|
|
343
|
+
entry.isolatedContext = isolatedContextName;
|
|
344
|
+
}
|
|
345
|
+
return entry;
|
|
337
346
|
});
|
|
338
347
|
}
|
|
339
348
|
if (this.#tabId) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { McpResponse } from './McpResponse.js';
|
|
7
|
+
export class SlimMcpResponse extends McpResponse {
|
|
8
|
+
async handle(_toolName, _context) {
|
|
9
|
+
const text = {
|
|
10
|
+
type: 'text',
|
|
11
|
+
text: this.responseLines.join('\n'),
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
content: [text],
|
|
15
|
+
structuredContent: text,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
package/build/src/browser.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
6
7
|
import fs from 'node:fs';
|
|
7
8
|
import os from 'node:os';
|
|
8
9
|
import path from 'node:path';
|
|
@@ -103,6 +104,22 @@ export async function ensureBrowserConnected(options) {
|
|
|
103
104
|
logger('Connected Puppeteer');
|
|
104
105
|
return browser;
|
|
105
106
|
}
|
|
107
|
+
export function detectDisplay() {
|
|
108
|
+
// Only detect display on Linux/UNIX.
|
|
109
|
+
if (os.platform() === 'win32' || os.platform() === 'darwin') {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!process.env['DISPLAY']) {
|
|
113
|
+
try {
|
|
114
|
+
const result = execSync(`ps -u $(id -u) -o pid= | xargs -I{} cat /proc/{}/environ 2>/dev/null | tr '\\0' '\\n' | grep -m1 '^DISPLAY=' | cut -d= -f2`);
|
|
115
|
+
const display = result.toString('utf8').trim();
|
|
116
|
+
process.env['DISPLAY'] = display;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// no-op
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
106
123
|
export async function launch(options) {
|
|
107
124
|
const { channel, executablePath, headless, isolated } = options;
|
|
108
125
|
const profileDirName = channel && channel !== 'stable'
|
|
@@ -133,6 +150,9 @@ export async function launch(options) {
|
|
|
133
150
|
? `chrome-${channel}`
|
|
134
151
|
: 'chrome';
|
|
135
152
|
}
|
|
153
|
+
if (!headless) {
|
|
154
|
+
detectDisplay();
|
|
155
|
+
}
|
|
136
156
|
try {
|
|
137
157
|
const browser = await puppeteer.launch({
|
|
138
158
|
channel: puppeteerChannel,
|
package/build/src/cli.js
CHANGED
|
@@ -159,6 +159,10 @@ export const cliOptions = {
|
|
|
159
159
|
describe: 'Whether to enable interoperability tools',
|
|
160
160
|
hidden: true,
|
|
161
161
|
},
|
|
162
|
+
experimentalScreencast: {
|
|
163
|
+
type: 'boolean',
|
|
164
|
+
describe: 'Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.',
|
|
165
|
+
},
|
|
162
166
|
chromeArg: {
|
|
163
167
|
type: 'array',
|
|
164
168
|
describe: 'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
|
|
@@ -213,6 +217,10 @@ export const cliOptions = {
|
|
|
213
217
|
hidden: true,
|
|
214
218
|
describe: 'Include watchdog PID in Clearcut request headers (for testing).',
|
|
215
219
|
},
|
|
220
|
+
slim: {
|
|
221
|
+
type: 'boolean',
|
|
222
|
+
describe: 'Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.',
|
|
223
|
+
},
|
|
216
224
|
};
|
|
217
225
|
export function parseArguments(version, argv = process.argv) {
|
|
218
226
|
const yargsInstance = yargs(hideBin(argv))
|
|
@@ -286,6 +294,10 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
286
294
|
'$0 --no-performance-crux',
|
|
287
295
|
'Disable CrUX (field data) integration in performance tools.',
|
|
288
296
|
],
|
|
297
|
+
[
|
|
298
|
+
'$0 --slim',
|
|
299
|
+
'Only 3 tools: navigation, JavaScript execution and screenshot',
|
|
300
|
+
],
|
|
289
301
|
]);
|
|
290
302
|
return yargsInstance
|
|
291
303
|
.wrap(Math.min(120, yargsInstance.terminalWidth()))
|