chrome-devtools-mcp 0.20.2 → 0.21.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 +16 -1
- package/build/src/DevtoolsUtils.js +0 -46
- package/build/src/McpContext.js +26 -31
- package/build/src/McpResponse.js +77 -3
- package/build/src/PageCollector.js +8 -7
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +10 -5
- package/build/src/bin/chrome-devtools-mcp.js +1 -0
- package/build/src/bin/chrome-devtools.js +6 -3
- package/build/src/bin/cliDefinitions.js +1 -1
- package/build/src/daemon/daemon.js +2 -2
- package/build/src/daemon/utils.js +1 -0
- package/build/src/index.js +11 -1
- package/build/src/telemetry/ClearcutLogger.js +118 -1
- package/build/src/telemetry/types.js +5 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +1453 -536
- package/build/src/third_party/bundled-packages.json +4 -4
- package/build/src/third_party/devtools-formatter-worker.js +0 -3
- package/build/src/third_party/index.js +9556 -2434
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +7494 -2395
- package/build/src/tools/categories.js +2 -0
- package/build/src/tools/inPage.js +84 -0
- package/build/src/tools/input.js +4 -1
- package/build/src/tools/lighthouse.js +1 -1
- package/build/src/tools/memory.js +2 -2
- package/build/src/tools/pages.js +6 -1
- package/build/src/tools/tools.js +2 -0
- package/build/src/version.js +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -27,6 +27,10 @@ allowing them to inspect, debug, and modify any data in the browser or DevTools.
|
|
|
27
27
|
Avoid sharing sensitive or personal information that you don't want to share with
|
|
28
28
|
MCP clients.
|
|
29
29
|
|
|
30
|
+
`chrome-devtools-mcp` officially supports Google Chrome and [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) only.
|
|
31
|
+
Other Chromium-based browser may work, but this is not guaranteed, and you may encounter unexpected behavior. Use at your own discretion.
|
|
32
|
+
We are committed to providing fixes and support for the latest version of [Extended Stable Chrome](https://chromiumdash.appspot.com/schedule).
|
|
33
|
+
|
|
30
34
|
Performance tools may send trace URLs to the Google CrUX API to fetch real-user
|
|
31
35
|
experience data. This helps provide a holistic performance picture by
|
|
32
36
|
presenting field data alongside lab data. This data is collected by the [Chrome
|
|
@@ -53,7 +57,7 @@ Collection is disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env vari
|
|
|
53
57
|
|
|
54
58
|
- [Node.js](https://nodejs.org/) v20.19 or a newer [latest maintenance LTS](https://github.com/nodejs/Release#release-schedule) version.
|
|
55
59
|
- [Chrome](https://www.google.com/chrome/) current stable version or newer.
|
|
56
|
-
- [npm](https://www.npmjs.com/)
|
|
60
|
+
- [npm](https://www.npmjs.com/)
|
|
57
61
|
|
|
58
62
|
## Getting started
|
|
59
63
|
|
|
@@ -194,6 +198,17 @@ startup_timeout_ms = 20_000
|
|
|
194
198
|
|
|
195
199
|
</details>
|
|
196
200
|
|
|
201
|
+
<details>
|
|
202
|
+
<summary>Command Code</summary>
|
|
203
|
+
|
|
204
|
+
Use the Command Code CLI to add the Chrome DevTools MCP server (<a href="https://commandcode.ai/docs/mcp">MCP guide</a>):
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
cmd mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
</details>
|
|
211
|
+
|
|
197
212
|
<details>
|
|
198
213
|
<summary>Copilot CLI</summary>
|
|
199
214
|
|
|
@@ -6,52 +6,6 @@
|
|
|
6
6
|
import { PuppeteerDevToolsConnection } from './DevToolsConnectionAdapter.js';
|
|
7
7
|
import { Mutex } from './Mutex.js';
|
|
8
8
|
import { DevTools } from './third_party/index.js';
|
|
9
|
-
export function extractUrlLikeFromDevToolsTitle(title) {
|
|
10
|
-
const match = title.match(new RegExp(`DevTools - (.*)`));
|
|
11
|
-
return match?.[1] ?? undefined;
|
|
12
|
-
}
|
|
13
|
-
export function urlsEqual(url1, url2) {
|
|
14
|
-
const normalizedUrl1 = normalizeUrl(url1);
|
|
15
|
-
const normalizedUrl2 = normalizeUrl(url2);
|
|
16
|
-
return normalizedUrl1 === normalizedUrl2;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* For the sake of the MCP server, when we determine if two URLs are equal we
|
|
20
|
-
* remove some parts:
|
|
21
|
-
*
|
|
22
|
-
* 1. We do not care about the protocol.
|
|
23
|
-
* 2. We do not care about trailing slashes.
|
|
24
|
-
* 3. We do not care about "www".
|
|
25
|
-
* 4. We ignore the hash parts.
|
|
26
|
-
*
|
|
27
|
-
* For example, if the user types "record a trace on foo.com", we would want to
|
|
28
|
-
* match a tab in the connected Chrome instance that is showing "www.foo.com/"
|
|
29
|
-
*/
|
|
30
|
-
function normalizeUrl(url) {
|
|
31
|
-
let result = url.trim();
|
|
32
|
-
// Remove protocols
|
|
33
|
-
if (result.startsWith('https://')) {
|
|
34
|
-
result = result.slice(8);
|
|
35
|
-
}
|
|
36
|
-
else if (result.startsWith('http://')) {
|
|
37
|
-
result = result.slice(7);
|
|
38
|
-
}
|
|
39
|
-
// Remove 'www.'. This ensures that we find the right URL regardless of if the user adds `www` or not.
|
|
40
|
-
if (result.startsWith('www.')) {
|
|
41
|
-
result = result.slice(4);
|
|
42
|
-
}
|
|
43
|
-
// We use target URLs to locate DevTools but those often do
|
|
44
|
-
// no include hash.
|
|
45
|
-
const hashIdx = result.lastIndexOf('#');
|
|
46
|
-
if (hashIdx !== -1) {
|
|
47
|
-
result = result.slice(0, hashIdx);
|
|
48
|
-
}
|
|
49
|
-
// Remove trailing slash
|
|
50
|
-
if (result.endsWith('/')) {
|
|
51
|
-
result = result.slice(0, -1);
|
|
52
|
-
}
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
9
|
/**
|
|
56
10
|
* A mock implementation of an issues manager that only implements the methods
|
|
57
11
|
* that are actually used by the IssuesAggregator
|
package/build/src/McpContext.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import fs from 'node:fs/promises';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import {
|
|
8
|
+
import { UniverseManager } from './DevtoolsUtils.js';
|
|
9
9
|
import { McpPage } from './McpPage.js';
|
|
10
|
-
import { NetworkCollector, ConsoleCollector } from './PageCollector.js';
|
|
10
|
+
import { NetworkCollector, ConsoleCollector, } from './PageCollector.js';
|
|
11
11
|
import { Locator } from './third_party/index.js';
|
|
12
12
|
import { PredefinedNetworkConditions } from './third_party/index.js';
|
|
13
13
|
import { listPages } from './tools/pages.js';
|
|
@@ -48,6 +48,7 @@ export class McpContext {
|
|
|
48
48
|
#extensionRegistry = new ExtensionRegistry();
|
|
49
49
|
#isRunningTrace = false;
|
|
50
50
|
#screenRecorderData = null;
|
|
51
|
+
#inPageTools;
|
|
51
52
|
#nextPageId = 1;
|
|
52
53
|
#extensionPages = new WeakMap();
|
|
53
54
|
#extensionServiceWorkerMap = new WeakMap();
|
|
@@ -328,6 +329,12 @@ export class McpContext {
|
|
|
328
329
|
this.#selectedPage = newPage;
|
|
329
330
|
this.#updateSelectedPageTimeouts();
|
|
330
331
|
}
|
|
332
|
+
setInPageTools(toolGroup) {
|
|
333
|
+
this.#inPageTools = toolGroup;
|
|
334
|
+
}
|
|
335
|
+
getInPageTools() {
|
|
336
|
+
return this.#inPageTools;
|
|
337
|
+
}
|
|
331
338
|
#updateSelectedPageTimeouts() {
|
|
332
339
|
const page = this.#getSelectedMcpPage();
|
|
333
340
|
// For waiters 5sec timeout should be sufficient.
|
|
@@ -467,38 +474,26 @@ export class McpContext {
|
|
|
467
474
|
async detectOpenDevToolsWindows() {
|
|
468
475
|
this.logger('Detecting open DevTools windows');
|
|
469
476
|
const { pages } = await this.#getAllPages();
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
mcpPage
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
.send('Target.getTargetInfo');
|
|
482
|
-
const devtoolsPageTitle = data.targetInfo.title;
|
|
483
|
-
const urlLike = extractUrlLikeFromDevToolsTitle(devtoolsPageTitle);
|
|
484
|
-
if (!urlLike) {
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
// TODO: lookup without a loop.
|
|
488
|
-
for (const page of this.#pages) {
|
|
489
|
-
if (urlsEqual(page.url(), urlLike)) {
|
|
490
|
-
const mcpPage = this.#mcpPages.get(page);
|
|
491
|
-
if (mcpPage) {
|
|
492
|
-
mcpPage.devToolsPage = devToolsPage;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
477
|
+
await Promise.all(pages.map(async (page) => {
|
|
478
|
+
const mcpPage = this.#mcpPages.get(page);
|
|
479
|
+
if (!mcpPage) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
// Prior to Chrome 144.0.7559.59, the command fails,
|
|
483
|
+
// Some Electron apps still use older version
|
|
484
|
+
// Fall back to not exposing DevTools at all.
|
|
485
|
+
try {
|
|
486
|
+
if (await page.hasDevTools()) {
|
|
487
|
+
mcpPage.devToolsPage = await page.openDevTools();
|
|
496
488
|
}
|
|
497
|
-
|
|
498
|
-
|
|
489
|
+
else {
|
|
490
|
+
mcpPage.devToolsPage = undefined;
|
|
499
491
|
}
|
|
500
492
|
}
|
|
501
|
-
|
|
493
|
+
catch {
|
|
494
|
+
mcpPage.devToolsPage = undefined;
|
|
495
|
+
}
|
|
496
|
+
}));
|
|
502
497
|
}
|
|
503
498
|
getExtensionServiceWorkers() {
|
|
504
499
|
return this.#extensionServiceWorkers;
|
package/build/src/McpResponse.js
CHANGED
|
@@ -12,6 +12,50 @@ import { DevTools } from './third_party/index.js';
|
|
|
12
12
|
import { handleDialog } from './tools/pages.js';
|
|
13
13
|
import { getInsightOutput, getTraceSummary } from './trace-processing/parse.js';
|
|
14
14
|
import { paginate } from './utils/pagination.js';
|
|
15
|
+
async function getToolGroup(page) {
|
|
16
|
+
// Check if there is a `devtoolstooldiscovery` event listener
|
|
17
|
+
const windowHandle = await page.pptrPage.evaluateHandle(() => window);
|
|
18
|
+
// @ts-expect-error internal API
|
|
19
|
+
const client = page.pptrPage._client();
|
|
20
|
+
const { listeners } = await client.send('DOMDebugger.getEventListeners', {
|
|
21
|
+
objectId: windowHandle.remoteObject().objectId,
|
|
22
|
+
});
|
|
23
|
+
if (listeners.find(l => l.type === 'devtoolstooldiscovery') === undefined) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const toolGroup = await page.pptrPage.evaluate(() => {
|
|
27
|
+
return new Promise(resolve => {
|
|
28
|
+
const event = new CustomEvent('devtoolstooldiscovery');
|
|
29
|
+
// @ts-expect-error Adding custom property
|
|
30
|
+
event.respondWith = (toolGroup) => {
|
|
31
|
+
if (!window.__dtmcp) {
|
|
32
|
+
window.__dtmcp = {};
|
|
33
|
+
}
|
|
34
|
+
window.__dtmcp.toolGroup = toolGroup;
|
|
35
|
+
// When receiving a toolGroup for the first time, expose a simple execution helper
|
|
36
|
+
if (!window.__dtmcp.executeTool) {
|
|
37
|
+
window.__dtmcp.executeTool = async (toolName, args) => {
|
|
38
|
+
if (!window.__dtmcp?.toolGroup) {
|
|
39
|
+
throw new Error('No tools found on the page');
|
|
40
|
+
}
|
|
41
|
+
const tool = window.__dtmcp.toolGroup.tools.find(t => t.name === toolName);
|
|
42
|
+
if (!tool) {
|
|
43
|
+
throw new Error(`Tool ${toolName} not found`);
|
|
44
|
+
}
|
|
45
|
+
return await tool.execute(args);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
resolve(toolGroup);
|
|
49
|
+
};
|
|
50
|
+
window.dispatchEvent(event);
|
|
51
|
+
// If the page does not synchronously call `event.respondWith`, return instead of timing out
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
resolve(undefined);
|
|
54
|
+
}, 0);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
return toolGroup;
|
|
58
|
+
}
|
|
15
59
|
export class McpResponse {
|
|
16
60
|
#includePages = false;
|
|
17
61
|
#includeExtensionServiceWorkers = false;
|
|
@@ -28,6 +72,7 @@ export class McpResponse {
|
|
|
28
72
|
#networkRequestsOptions;
|
|
29
73
|
#consoleDataOptions;
|
|
30
74
|
#listExtensions;
|
|
75
|
+
#listInPageTools;
|
|
31
76
|
#devToolsData;
|
|
32
77
|
#tabId;
|
|
33
78
|
#args;
|
|
@@ -59,6 +104,11 @@ export class McpResponse {
|
|
|
59
104
|
setListExtensions() {
|
|
60
105
|
this.#listExtensions = true;
|
|
61
106
|
}
|
|
107
|
+
setListInPageTools() {
|
|
108
|
+
if (this.#args.categoryInPageTools) {
|
|
109
|
+
this.#listInPageTools = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
62
112
|
setIncludeNetworkRequests(value, options) {
|
|
63
113
|
if (!value) {
|
|
64
114
|
this.#networkRequestsOptions = undefined;
|
|
@@ -94,8 +144,8 @@ export class McpResponse {
|
|
|
94
144
|
includePreservedMessages: options?.includePreservedMessages,
|
|
95
145
|
};
|
|
96
146
|
}
|
|
97
|
-
attachNetworkRequest(
|
|
98
|
-
this.#attachedNetworkRequestId =
|
|
147
|
+
attachNetworkRequest(reqId, options) {
|
|
148
|
+
this.#attachedNetworkRequestId = reqId;
|
|
99
149
|
this.#attachedNetworkRequestOptions = options;
|
|
100
150
|
}
|
|
101
151
|
attachConsoleMessage(msgid) {
|
|
@@ -223,7 +273,7 @@ export class McpResponse {
|
|
|
223
273
|
elementIdResolver: context.resolveCdpElementId.bind(context, this.#page),
|
|
224
274
|
});
|
|
225
275
|
if (!formatter.isValid()) {
|
|
226
|
-
throw new Error("Can't provide
|
|
276
|
+
throw new Error("Can't provide details for the msgid " + consoleMessageStableId);
|
|
227
277
|
}
|
|
228
278
|
detailedConsoleMessage = formatter;
|
|
229
279
|
}
|
|
@@ -232,6 +282,11 @@ export class McpResponse {
|
|
|
232
282
|
if (this.#listExtensions) {
|
|
233
283
|
extensions = context.listExtensions();
|
|
234
284
|
}
|
|
285
|
+
let inPageTools;
|
|
286
|
+
if (this.#listInPageTools) {
|
|
287
|
+
inPageTools = await getToolGroup(context.getSelectedMcpPage());
|
|
288
|
+
context.setInPageTools(inPageTools);
|
|
289
|
+
}
|
|
235
290
|
let consoleMessages;
|
|
236
291
|
if (this.#consoleDataOptions?.include) {
|
|
237
292
|
if (!this.#page) {
|
|
@@ -308,6 +363,7 @@ export class McpResponse {
|
|
|
308
363
|
traceSummary: this.#attachedTraceSummary,
|
|
309
364
|
extensions,
|
|
310
365
|
lighthouseResult: this.#attachedLighthouseResult,
|
|
366
|
+
inPageTools,
|
|
311
367
|
});
|
|
312
368
|
}
|
|
313
369
|
format(toolName, context, data) {
|
|
@@ -498,6 +554,24 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
498
554
|
response.push(extensionsMessage);
|
|
499
555
|
}
|
|
500
556
|
}
|
|
557
|
+
if (this.#listInPageTools) {
|
|
558
|
+
structuredContent.inPageTools = data.inPageTools ?? undefined;
|
|
559
|
+
response.push('## In-page tools');
|
|
560
|
+
if (!data.inPageTools || !data.inPageTools.tools) {
|
|
561
|
+
response.push('No in-page tools available.');
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
const toolGroup = data.inPageTools;
|
|
565
|
+
response.push(`${toolGroup.name}: ${toolGroup.description}`);
|
|
566
|
+
response.push('Available tools:');
|
|
567
|
+
const toolDefinitionsMessage = toolGroup.tools
|
|
568
|
+
.map(tool => {
|
|
569
|
+
return `name="${tool.name}", description="${tool.description}", inputSchema=${JSON.stringify(tool.inputSchema)}`;
|
|
570
|
+
})
|
|
571
|
+
.join('\n');
|
|
572
|
+
response.push(toolDefinitionsMessage);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
501
575
|
if (this.#networkRequestsOptions?.include && data.networkRequests) {
|
|
502
576
|
const requests = data.networkRequests;
|
|
503
577
|
response.push('## Network requests');
|
|
@@ -28,7 +28,7 @@ export class PageCollector {
|
|
|
28
28
|
#browser;
|
|
29
29
|
#listenersInitializer;
|
|
30
30
|
#listeners = new WeakMap();
|
|
31
|
-
|
|
31
|
+
maxNavigationSaved = 3;
|
|
32
32
|
/**
|
|
33
33
|
* This maps a Page to a list of navigations with a sub-list
|
|
34
34
|
* of all collected resources.
|
|
@@ -109,7 +109,7 @@ export class PageCollector {
|
|
|
109
109
|
}
|
|
110
110
|
// Add the latest navigation first
|
|
111
111
|
navigations.unshift([]);
|
|
112
|
-
navigations.splice(this
|
|
112
|
+
navigations.splice(this.maxNavigationSaved);
|
|
113
113
|
}
|
|
114
114
|
cleanupPageDestroyed(page) {
|
|
115
115
|
const listeners = this.#listeners.get(page);
|
|
@@ -129,7 +129,7 @@ export class PageCollector {
|
|
|
129
129
|
return navigations[0];
|
|
130
130
|
}
|
|
131
131
|
const data = [];
|
|
132
|
-
for (let index = this
|
|
132
|
+
for (let index = this.maxNavigationSaved; index >= 0; index--) {
|
|
133
133
|
if (navigations[index]) {
|
|
134
134
|
data.push(...navigations[index]);
|
|
135
135
|
}
|
|
@@ -198,10 +198,10 @@ class PageEventSubscriber {
|
|
|
198
198
|
#resetIssueAggregator() {
|
|
199
199
|
this.#issueManager = new FakeIssuesManager();
|
|
200
200
|
if (this.#issueAggregator) {
|
|
201
|
-
this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#
|
|
201
|
+
this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
|
|
202
202
|
}
|
|
203
203
|
this.#issueAggregator = new DevTools.IssueAggregator(this.#issueManager);
|
|
204
|
-
this.#issueAggregator.addEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#
|
|
204
|
+
this.#issueAggregator.addEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
|
|
205
205
|
}
|
|
206
206
|
async subscribe() {
|
|
207
207
|
this.#resetIssueAggregator();
|
|
@@ -222,13 +222,13 @@ class PageEventSubscriber {
|
|
|
222
222
|
this.#session.off('Audits.issueAdded', this.#onIssueAdded);
|
|
223
223
|
this.#session.off('Runtime.exceptionThrown', this.#onExceptionThrown);
|
|
224
224
|
if (this.#issueAggregator) {
|
|
225
|
-
this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#
|
|
225
|
+
this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
|
|
226
226
|
}
|
|
227
227
|
void this.#session.send('Audits.disable').catch(() => {
|
|
228
228
|
// might fail.
|
|
229
229
|
});
|
|
230
230
|
}
|
|
231
|
-
#
|
|
231
|
+
#onAggregatedIssue = (event) => {
|
|
232
232
|
if (this.#seenIssues.has(event.data)) {
|
|
233
233
|
return;
|
|
234
234
|
}
|
|
@@ -305,5 +305,6 @@ export class NetworkCollector extends PageCollector {
|
|
|
305
305
|
else {
|
|
306
306
|
navigations.unshift([]);
|
|
307
307
|
}
|
|
308
|
+
navigations.splice(this.maxNavigationSaved);
|
|
308
309
|
}
|
|
309
310
|
}
|
|
@@ -8,7 +8,7 @@ export const cliOptions = {
|
|
|
8
8
|
autoConnect: {
|
|
9
9
|
type: 'boolean',
|
|
10
10
|
description: 'If specified, automatically connects to a browser (Chrome 144+) running locally from the user data directory identified by the channel param (default channel is stable). Requires the remoted debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.',
|
|
11
|
-
conflicts: ['isolated', 'executablePath'],
|
|
11
|
+
conflicts: ['isolated', 'executablePath', 'categoryExtensions'],
|
|
12
12
|
default: false,
|
|
13
13
|
coerce: (value) => {
|
|
14
14
|
if (!value) {
|
|
@@ -21,7 +21,7 @@ export const cliOptions = {
|
|
|
21
21
|
type: 'string',
|
|
22
22
|
description: 'Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp#connecting-to-a-running-chrome-instance.',
|
|
23
23
|
alias: 'u',
|
|
24
|
-
conflicts: 'wsEndpoint',
|
|
24
|
+
conflicts: ['wsEndpoint', 'categoryExtensions'],
|
|
25
25
|
coerce: (url) => {
|
|
26
26
|
if (!url) {
|
|
27
27
|
return;
|
|
@@ -39,7 +39,7 @@ export const cliOptions = {
|
|
|
39
39
|
type: 'string',
|
|
40
40
|
description: 'WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.',
|
|
41
41
|
alias: 'w',
|
|
42
|
-
conflicts: 'browserUrl',
|
|
42
|
+
conflicts: ['browserUrl', 'categoryExtensions'],
|
|
43
43
|
coerce: (url) => {
|
|
44
44
|
if (!url) {
|
|
45
45
|
return;
|
|
@@ -193,9 +193,14 @@ export const cliOptions = {
|
|
|
193
193
|
},
|
|
194
194
|
categoryExtensions: {
|
|
195
195
|
type: 'boolean',
|
|
196
|
-
default: false,
|
|
197
196
|
hidden: true,
|
|
198
|
-
|
|
197
|
+
conflicts: ['browserUrl', 'autoConnect', 'wsEndpoint'],
|
|
198
|
+
describe: 'Set to true to include tools related to extensions. Note: This feature is only supported with a pipe connection. autoConnect is not supported.',
|
|
199
|
+
},
|
|
200
|
+
categoryInPageTools: {
|
|
201
|
+
type: 'boolean',
|
|
202
|
+
hidden: true,
|
|
203
|
+
describe: 'Set to true to enable tools exposed by the inspected page itself',
|
|
199
204
|
},
|
|
200
205
|
performanceCrux: {
|
|
201
206
|
type: 'boolean',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Copyright 2026 Google LLC
|
|
5
5
|
* SPDX-License-Identifier: Apache-2.0
|
|
6
6
|
*/
|
|
7
|
+
process.title = 'chrome-devtools';
|
|
7
8
|
import process from 'node:process';
|
|
8
9
|
import { startDaemon, stopDaemon, sendCommand, handleResponse, } from '../daemon/client.js';
|
|
9
10
|
import { isDaemonRunning, serializeArgs } from '../daemon/utils.js';
|
|
@@ -26,7 +27,7 @@ delete startCliOptions.autoConnect;
|
|
|
26
27
|
// Missing CLI serialization.
|
|
27
28
|
delete startCliOptions.viewport;
|
|
28
29
|
// CLI is generated based on the default tool definitions. To enable conditional
|
|
29
|
-
// tools, they
|
|
30
|
+
// tools, they need to be enabled during CLI generation.
|
|
30
31
|
delete startCliOptions.experimentalPageIdRouting;
|
|
31
32
|
delete startCliOptions.experimentalVision;
|
|
32
33
|
delete startCliOptions.experimentalInteropTools;
|
|
@@ -42,9 +43,11 @@ if (!('default' in cliOptions.headless)) {
|
|
|
42
43
|
throw new Error('headless cli option unexpectedly does not have a default');
|
|
43
44
|
}
|
|
44
45
|
if ('default' in cliOptions.isolated) {
|
|
45
|
-
throw new Error('
|
|
46
|
+
throw new Error('isolated cli option unexpectedly has a default');
|
|
46
47
|
}
|
|
47
48
|
startCliOptions.headless.default = true;
|
|
49
|
+
startCliOptions.isolated.description =
|
|
50
|
+
'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to true unless userDataDir is provided.';
|
|
48
51
|
const y = yargs(hideBin(process.argv))
|
|
49
52
|
.scriptName('chrome-devtools')
|
|
50
53
|
.showHelpOnFail(true)
|
|
@@ -63,7 +66,7 @@ y.command('start', 'Start or restart chrome-devtools-mcp', y => y
|
|
|
63
66
|
await stopDaemon();
|
|
64
67
|
}
|
|
65
68
|
// Defaults but we do not want to affect the yargs conflict resolution.
|
|
66
|
-
if (argv.isolated === undefined) {
|
|
69
|
+
if (argv.isolated === undefined && argv.userDataDir === undefined) {
|
|
67
70
|
argv.isolated = true;
|
|
68
71
|
}
|
|
69
72
|
if (argv.headless === undefined) {
|
|
@@ -503,7 +503,7 @@ export const commands = {
|
|
|
503
503
|
},
|
|
504
504
|
},
|
|
505
505
|
take_memory_snapshot: {
|
|
506
|
-
description: 'Capture a
|
|
506
|
+
description: 'Capture a heap snapshot of the currently selected page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.',
|
|
507
507
|
category: 'Performance',
|
|
508
508
|
args: {
|
|
509
509
|
filePath: {
|
|
@@ -11,7 +11,7 @@ import process from 'node:process';
|
|
|
11
11
|
import { logger } from '../logger.js';
|
|
12
12
|
import { Client, PipeTransport, StdioClientTransport, } from '../third_party/index.js';
|
|
13
13
|
import { VERSION } from '../version.js';
|
|
14
|
-
import { getDaemonPid, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
|
|
14
|
+
import { DAEMON_CLIENT_NAME, getDaemonPid, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
|
|
15
15
|
const pid = getDaemonPid();
|
|
16
16
|
if (isDaemonRunning(pid)) {
|
|
17
17
|
logger('Another daemon process is running.');
|
|
@@ -42,7 +42,7 @@ async function setupMCPClient() {
|
|
|
42
42
|
env: process.env,
|
|
43
43
|
});
|
|
44
44
|
mcpClient = new Client({
|
|
45
|
-
name:
|
|
45
|
+
name: DAEMON_CLIENT_NAME,
|
|
46
46
|
version: VERSION,
|
|
47
47
|
}, {
|
|
48
48
|
capabilities: {},
|
|
@@ -11,6 +11,7 @@ import { logger } from '../logger.js';
|
|
|
11
11
|
export const DAEMON_SCRIPT_PATH = path.join(import.meta.dirname, 'daemon.js');
|
|
12
12
|
export const INDEX_SCRIPT_PATH = path.join(import.meta.dirname, '..', 'bin', 'chrome-devtools-mcp.js');
|
|
13
13
|
const APP_NAME = 'chrome-devtools-mcp';
|
|
14
|
+
export const DAEMON_CLIENT_NAME = 'chrome-devtools-cli-daemon';
|
|
14
15
|
// Using these paths due to strict limits on the POSIX socket path length.
|
|
15
16
|
export function getSocketPath() {
|
|
16
17
|
const uid = os.userInfo().uid;
|
package/build/src/index.js
CHANGED
|
@@ -36,6 +36,12 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
36
36
|
server.server.setRequestHandler(SetLevelRequestSchema, () => {
|
|
37
37
|
return {};
|
|
38
38
|
});
|
|
39
|
+
server.server.oninitialized = () => {
|
|
40
|
+
const clientName = server.server.getClientVersion()?.name;
|
|
41
|
+
if (clientName) {
|
|
42
|
+
clearcutLogger?.setClientName(clientName);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
39
45
|
let context;
|
|
40
46
|
async function getContext() {
|
|
41
47
|
const chromeArgs = (serverArgs.chromeArg ?? []).map(String);
|
|
@@ -95,7 +101,11 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
95
101
|
return;
|
|
96
102
|
}
|
|
97
103
|
if (tool.annotations.category === ToolCategory.EXTENSIONS &&
|
|
98
|
-
serverArgs.categoryExtensions
|
|
104
|
+
!serverArgs.categoryExtensions) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (tool.annotations.category === ToolCategory.IN_PAGE &&
|
|
108
|
+
!serverArgs.categoryInPageTools) {
|
|
99
109
|
return;
|
|
100
110
|
}
|
|
101
111
|
if (tool.annotations.conditions?.includes('computerVision') &&
|