chrome-devtools-mcp 0.3.0 → 0.4.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 +20 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +16 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +10 -19
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +31 -18
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/legacy-javascript/legacy-javascript.js +2 -38
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/legacy-javascript/lib/legacy-javascript.js +1 -5
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/lib/nostats-subset.js +1 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/third-party-web.js +2 -8
- package/build/src/McpResponse.js +26 -14
- package/build/src/cli.js +1 -2
- package/build/src/index.js +2 -2
- package/build/src/main.js +1 -1
- package/build/src/tools/network.js +26 -0
- package/build/src/tools/performance.js +2 -2
- package/build/src/trace-processing/parse.js +10 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -102,8 +102,22 @@ Go to `Cursor Settings` -> `MCP` -> `New MCP Server`. Use the config provided ab
|
|
|
102
102
|
|
|
103
103
|
<details>
|
|
104
104
|
<summary>Gemini CLI</summary>
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
Install the Chrome DevTools MCP server using the Gemini CLI.
|
|
106
|
+
|
|
107
|
+
**Project wide:**
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
gemini mcp add chrome-devtools npx chrome-devtools-mcp@latest
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Globally:**
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
gemini mcp add -s user chrome-devtools npx chrome-devtools-mcp@latest
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Alternatively, follow the <a href="https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md#how-to-set-up-your-mcp-server">MCP guide</a> and use the standard config from above.
|
|
120
|
+
|
|
107
121
|
</details>
|
|
108
122
|
|
|
109
123
|
<details>
|
|
@@ -201,6 +215,10 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
201
215
|
- **Type:** string
|
|
202
216
|
- **Choices:** `stable`, `canary`, `beta`, `dev`
|
|
203
217
|
|
|
218
|
+
- **`--logFile`**
|
|
219
|
+
Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports.
|
|
220
|
+
- **Type:** string
|
|
221
|
+
|
|
204
222
|
<!-- END AUTO GENERATED OPTIONS -->
|
|
205
223
|
|
|
206
224
|
Pass them via the `args` property in the JSON configuration. For example:
|
|
@@ -19,7 +19,7 @@ export var SubscriptionTier;
|
|
|
19
19
|
SubscriptionTier["PRO_ANNUAL"] = "SUBSCRIPTION_TIER_PRO_ANNUAL";
|
|
20
20
|
SubscriptionTier["PRO_MONTHLY"] = "SUBSCRIPTION_TIER_PRO_MONTHLY";
|
|
21
21
|
})(SubscriptionTier || (SubscriptionTier = {}));
|
|
22
|
-
var EligibilityStatus;
|
|
22
|
+
export var EligibilityStatus;
|
|
23
23
|
(function (EligibilityStatus) {
|
|
24
24
|
EligibilityStatus["ELIGIBLE"] = "ELIGIBLE";
|
|
25
25
|
EligibilityStatus["NOT_ELIGIBLE"] = "NOT_ELIGIBLE";
|
|
@@ -65,12 +65,18 @@ export class GdpClient {
|
|
|
65
65
|
return gdpClientInstance;
|
|
66
66
|
}
|
|
67
67
|
async initialize() {
|
|
68
|
-
|
|
68
|
+
const profile = await this.getProfile();
|
|
69
|
+
if (profile) {
|
|
69
70
|
return {
|
|
70
|
-
hasProfile:
|
|
71
|
-
isEligible:
|
|
71
|
+
hasProfile: true,
|
|
72
|
+
isEligible: true,
|
|
72
73
|
};
|
|
73
|
-
}
|
|
74
|
+
}
|
|
75
|
+
const isEligible = await this.isEligibleToCreateProfile();
|
|
76
|
+
return {
|
|
77
|
+
hasProfile: false,
|
|
78
|
+
isEligible,
|
|
79
|
+
};
|
|
74
80
|
}
|
|
75
81
|
async getProfile() {
|
|
76
82
|
if (this.#cachedProfilePromise) {
|
|
@@ -81,7 +87,11 @@ export class GdpClient {
|
|
|
81
87
|
path: '/v1beta1/profile:get',
|
|
82
88
|
method: 'GET',
|
|
83
89
|
});
|
|
84
|
-
|
|
90
|
+
const profile = await this.#cachedProfilePromise;
|
|
91
|
+
if (profile) {
|
|
92
|
+
this.#cachedEligibilityPromise = Promise.resolve({ createProfile: EligibilityStatus.ELIGIBLE });
|
|
93
|
+
}
|
|
94
|
+
return profile;
|
|
85
95
|
}
|
|
86
96
|
async checkEligibility() {
|
|
87
97
|
if (this.#cachedEligibilityPromise) {
|
|
@@ -27,20 +27,12 @@ function getLCPData(parsedTrace, frameId, navigationId) {
|
|
|
27
27
|
metricScore: metric,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
export class PerformanceInsightFormatter
|
|
30
|
+
export class PerformanceInsightFormatter {
|
|
31
|
+
#traceFormatter;
|
|
31
32
|
#insight;
|
|
32
33
|
#parsedTrace;
|
|
33
|
-
/**
|
|
34
|
-
* A utility method because we dependency inject this formatter into
|
|
35
|
-
* PerformanceTraceFormatter; this allows you to pass
|
|
36
|
-
* PerformanceInsightFormatter.create rather than an anonymous
|
|
37
|
-
* function that wraps the constructor.
|
|
38
|
-
*/
|
|
39
|
-
static create(focus, insight) {
|
|
40
|
-
return new PerformanceInsightFormatter(focus, insight);
|
|
41
|
-
}
|
|
42
34
|
constructor(focus, insight) {
|
|
43
|
-
|
|
35
|
+
this.#traceFormatter = new PerformanceTraceFormatter(focus);
|
|
44
36
|
this.#insight = insight;
|
|
45
37
|
this.#parsedTrace = focus.parsedTrace;
|
|
46
38
|
}
|
|
@@ -57,8 +49,7 @@ export class PerformanceInsightFormatter extends PerformanceTraceFormatter {
|
|
|
57
49
|
return this.#formatMilli(Trace.Helpers.Timing.microToMilli(x));
|
|
58
50
|
}
|
|
59
51
|
#formatRequestUrl(request) {
|
|
60
|
-
|
|
61
|
-
return `${request.args.data.url} (eventKey: ${eventKey})`;
|
|
52
|
+
return `${request.args.data.url} ${this.#traceFormatter.serializeEvent(request)}`;
|
|
62
53
|
}
|
|
63
54
|
#formatScriptUrl(script) {
|
|
64
55
|
if (script.request) {
|
|
@@ -97,7 +88,7 @@ export class PerformanceInsightFormatter extends PerformanceTraceFormatter {
|
|
|
97
88
|
];
|
|
98
89
|
if (lcpRequest) {
|
|
99
90
|
parts.push(`${theLcpElement} is an image fetched from ${this.#formatRequestUrl(lcpRequest)}.`);
|
|
100
|
-
const request = this.formatNetworkRequests([lcpRequest], { verbose: true, customTitle: 'LCP resource network request' });
|
|
91
|
+
const request = this.#traceFormatter.formatNetworkRequests([lcpRequest], { verbose: true, customTitle: 'LCP resource network request' });
|
|
101
92
|
parts.push(request);
|
|
102
93
|
}
|
|
103
94
|
else {
|
|
@@ -305,7 +296,7 @@ ${shiftsFormatted.join('\n')}`;
|
|
|
305
296
|
});
|
|
306
297
|
return `${this.#lcpMetricSharedContext()}
|
|
307
298
|
|
|
308
|
-
${this.formatNetworkRequests([documentRequest], {
|
|
299
|
+
${this.#traceFormatter.formatNetworkRequests([documentRequest], {
|
|
309
300
|
verbose: true,
|
|
310
301
|
customTitle: 'Document network request'
|
|
311
302
|
})}
|
|
@@ -580,8 +571,8 @@ ${filesFormatted}`;
|
|
|
580
571
|
*/
|
|
581
572
|
formatModernHttpInsight(insight) {
|
|
582
573
|
const requestSummary = (insight.http1Requests.length === 1) ?
|
|
583
|
-
this.formatNetworkRequests(insight.http1Requests, { verbose: true }) :
|
|
584
|
-
this.formatNetworkRequests(insight.http1Requests);
|
|
574
|
+
this.#traceFormatter.formatNetworkRequests(insight.http1Requests, { verbose: true }) :
|
|
575
|
+
this.#traceFormatter.formatNetworkRequests(insight.http1Requests);
|
|
585
576
|
if (requestSummary.length === 0) {
|
|
586
577
|
return 'There are no requests that were served over a legacy HTTP protocol.';
|
|
587
578
|
}
|
|
@@ -665,7 +656,7 @@ ${requestSummary}`;
|
|
|
665
656
|
* @returns a string formatted for sending to Ask AI.
|
|
666
657
|
*/
|
|
667
658
|
formatRenderBlockingInsight(insight) {
|
|
668
|
-
const requestSummary = this.formatNetworkRequests(insight.renderBlockingRequests);
|
|
659
|
+
const requestSummary = this.#traceFormatter.formatNetworkRequests(insight.renderBlockingRequests);
|
|
669
660
|
if (requestSummary.length === 0) {
|
|
670
661
|
return 'There are no network requests that are render blocking.';
|
|
671
662
|
}
|
|
@@ -856,7 +847,7 @@ ${this.#links()}`;
|
|
|
856
847
|
#links() {
|
|
857
848
|
switch (this.#insight.insightKey) {
|
|
858
849
|
case 'CLSCulprits':
|
|
859
|
-
return `- https://
|
|
850
|
+
return `- https://web.dev/articles/cls
|
|
860
851
|
- https://web.dev/articles/optimize-cls`;
|
|
861
852
|
case 'DocumentLatency':
|
|
862
853
|
return '- https://web.dev/articles/optimize-ttfb';
|
|
@@ -5,28 +5,21 @@ import * as CrUXManager from '../../crux-manager/crux-manager.js';
|
|
|
5
5
|
import * as Trace from '../../trace/trace.js';
|
|
6
6
|
import { AIQueries } from '../performance/AIQueries.js';
|
|
7
7
|
import { NetworkRequestFormatter } from './NetworkRequestFormatter.js';
|
|
8
|
+
import { PerformanceInsightFormatter } from './PerformanceInsightFormatter.js';
|
|
8
9
|
import { bytes, micros, millis } from './UnitFormatters.js';
|
|
9
10
|
export class PerformanceTraceFormatter {
|
|
10
11
|
#focus;
|
|
11
12
|
#parsedTrace;
|
|
12
13
|
#insightSet;
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* We inject the insight formatter because otherwise we get a circular
|
|
17
|
-
* dependency between PerformanceInsightFormatter and
|
|
18
|
-
* PerformanceTraceFormatter. This is OK in the browser build, but breaks when
|
|
19
|
-
* we reuse this code in NodeJS for DevTools MCP.
|
|
20
|
-
*/
|
|
21
|
-
constructor(focus, getInsightFormatter) {
|
|
14
|
+
#eventsSerializer;
|
|
15
|
+
constructor(focus) {
|
|
22
16
|
this.#focus = focus;
|
|
23
17
|
this.#parsedTrace = focus.parsedTrace;
|
|
24
18
|
this.#insightSet = focus.insightSet;
|
|
25
|
-
this
|
|
26
|
-
this.#getInsightFormatter = getInsightFormatter;
|
|
19
|
+
this.#eventsSerializer = focus.eventsSerializer;
|
|
27
20
|
}
|
|
28
21
|
serializeEvent(event) {
|
|
29
|
-
const key = this
|
|
22
|
+
const key = this.#eventsSerializer.keyForEvent(event);
|
|
30
23
|
return `(eventKey: ${key}, ts: ${event.ts})`;
|
|
31
24
|
}
|
|
32
25
|
serializeBounds(bounds) {
|
|
@@ -155,10 +148,7 @@ export class PerformanceTraceFormatter {
|
|
|
155
148
|
if (model.state === 'pass') {
|
|
156
149
|
continue;
|
|
157
150
|
}
|
|
158
|
-
const formatter =
|
|
159
|
-
if (!formatter) {
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
151
|
+
const formatter = new PerformanceInsightFormatter(this.#focus, model);
|
|
162
152
|
if (!formatter.insightIsSupported()) {
|
|
163
153
|
continue;
|
|
164
154
|
}
|
|
@@ -455,7 +445,7 @@ export class PerformanceTraceFormatter {
|
|
|
455
445
|
});
|
|
456
446
|
const initiators = this.#getInitiatorChain(parsedTrace, request);
|
|
457
447
|
const initiatorUrls = initiators.map(initiator => initiator.args.data.url);
|
|
458
|
-
const eventKey = this
|
|
448
|
+
const eventKey = this.#eventsSerializer.keyForEvent(request);
|
|
459
449
|
const eventKeyLine = eventKey ? `eventKey: ${eventKey}\n` : '';
|
|
460
450
|
return `${titlePrefix}: ${url}
|
|
461
451
|
${eventKeyLine}Timings:
|
|
@@ -504,6 +494,29 @@ Network requests data:
|
|
|
504
494
|
.join(', ')}]`;
|
|
505
495
|
return networkDataString + '\n\n' + urlsMapString + '\n\n' + allRequestsText;
|
|
506
496
|
}
|
|
497
|
+
static callFrameDataFormatDescription = `Each call frame is presented in the following format:
|
|
498
|
+
|
|
499
|
+
'id;eventKey;name;duration;selfTime;urlIndex;childRange;[S]'
|
|
500
|
+
|
|
501
|
+
Key definitions:
|
|
502
|
+
|
|
503
|
+
* id: A unique numerical identifier for the call frame. Never mention this id in the output to the user.
|
|
504
|
+
* eventKey: String that uniquely identifies this event in the flame chart.
|
|
505
|
+
* name: A concise string describing the call frame (e.g., 'Evaluate Script', 'render', 'fetchData').
|
|
506
|
+
* duration: The total execution time of the call frame, including its children.
|
|
507
|
+
* selfTime: The time spent directly within the call frame, excluding its children's execution.
|
|
508
|
+
* urlIndex: Index referencing the "All URLs" list. Empty if no specific script URL is associated.
|
|
509
|
+
* childRange: Specifies the direct children of this node using their IDs. If empty ('' or 'S' at the end), the node has no children. If a single number (e.g., '4'), the node has one child with that ID. If in the format 'firstId-lastId' (e.g., '4-5'), it indicates a consecutive range of child IDs from 'firstId' to 'lastId', inclusive.
|
|
510
|
+
* S: _Optional_. The letter 'S' terminates the line if that call frame was selected by the user.
|
|
511
|
+
|
|
512
|
+
Example Call Tree:
|
|
513
|
+
|
|
514
|
+
1;r-123;main;500;100;;
|
|
515
|
+
2;r-124;update;200;50;;3
|
|
516
|
+
3;p-49575-15428179-2834-374;animate;150;20;0;4-5;S
|
|
517
|
+
4;p-49575-15428179-3505-1162;calculatePosition;80;80;;
|
|
518
|
+
5;p-49575-15428179-5391-2767;applyStyles;50;50;;
|
|
519
|
+
`;
|
|
507
520
|
/**
|
|
508
521
|
* Network requests format description that is sent to the model as a fact.
|
|
509
522
|
*/
|
|
@@ -575,7 +588,7 @@ The order of headers corresponds to an internal fixed list. If a header is not p
|
|
|
575
588
|
const initiatorUrlIndices = initiators.map(initiator => this.#getOrAssignUrlIndex(urlIdToIndex, initiator.args.data.url));
|
|
576
589
|
const parts = [
|
|
577
590
|
urlIndex,
|
|
578
|
-
this
|
|
591
|
+
this.#eventsSerializer.keyForEvent(request) ?? '',
|
|
579
592
|
queuedTime,
|
|
580
593
|
requestSentTime,
|
|
581
594
|
downloadCompleteTime,
|
|
@@ -1,38 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.LegacyJavaScript = void 0;
|
|
37
|
-
const LegacyJavaScript = __importStar(require("./lib/legacy-javascript.js"));
|
|
38
|
-
exports.LegacyJavaScript = LegacyJavaScript;
|
|
1
|
+
import * as LegacyJavaScript from './lib/legacy-javascript.js';
|
|
2
|
+
export { LegacyJavaScript };
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.detectLegacyJavaScript = detectLegacyJavaScript;
|
|
4
|
-
exports.getCoreJsPolyfillData = getCoreJsPolyfillData;
|
|
5
|
-
exports.getTransformPatterns = getTransformPatterns;
|
|
6
1
|
// core/lib/legacy-javascript/polyfill-module-data.json
|
|
7
2
|
var polyfill_module_data_default = [
|
|
8
3
|
{
|
|
@@ -937,6 +932,7 @@ function detectLegacyJavaScript(content, map) {
|
|
|
937
932
|
estimatedByteSavings: estimateWastedBytes(content, matches)
|
|
938
933
|
};
|
|
939
934
|
}
|
|
935
|
+
export { detectLegacyJavaScript, getCoreJsPolyfillData, getTransformPatterns };
|
|
940
936
|
/**
|
|
941
937
|
* @license
|
|
942
938
|
* Copyright 2025 Google LLC
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
1
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
2
|
var __commonJS = (cb, mod) => function __require() {
|
|
5
3
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
@@ -145,4 +143,4 @@ var require_nostats_subset = __commonJS({
|
|
|
145
143
|
module.exports = require_nostats();
|
|
146
144
|
}
|
|
147
145
|
});
|
|
148
|
-
|
|
146
|
+
export default require_nostats_subset();
|
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ThirdPartyWeb = void 0;
|
|
7
|
-
const nostats_subset_js_1 = __importDefault(require("./lib/nostats-subset.js"));
|
|
8
|
-
exports.ThirdPartyWeb = nostats_subset_js_1.default;
|
|
1
|
+
import ThirdPartyWeb from './lib/nostats-subset.js';
|
|
2
|
+
export { ThirdPartyWeb };
|
package/build/src/McpResponse.js
CHANGED
|
@@ -5,13 +5,12 @@ import { paginate } from './utils/pagination.js';
|
|
|
5
5
|
export class McpResponse {
|
|
6
6
|
#includePages = false;
|
|
7
7
|
#includeSnapshot = false;
|
|
8
|
-
#includeNetworkRequests = false;
|
|
9
8
|
#attachedNetworkRequestUrl;
|
|
10
9
|
#includeConsoleData = false;
|
|
11
10
|
#textResponseLines = [];
|
|
12
11
|
#formattedConsoleData;
|
|
13
12
|
#images = [];
|
|
14
|
-
#
|
|
13
|
+
#networkRequestsOptions;
|
|
15
14
|
setIncludePages(value) {
|
|
16
15
|
this.#includePages = value;
|
|
17
16
|
}
|
|
@@ -19,14 +18,19 @@ export class McpResponse {
|
|
|
19
18
|
this.#includeSnapshot = value;
|
|
20
19
|
}
|
|
21
20
|
setIncludeNetworkRequests(value, options) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.#networkRequestsPaginationOptions = undefined;
|
|
21
|
+
if (!value) {
|
|
22
|
+
this.#networkRequestsOptions = undefined;
|
|
25
23
|
return;
|
|
26
24
|
}
|
|
27
|
-
this.#
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
this.#networkRequestsOptions = {
|
|
26
|
+
include: value,
|
|
27
|
+
pagination: options?.pageSize || options?.pageIdx
|
|
28
|
+
? {
|
|
29
|
+
pageSize: options.pageSize,
|
|
30
|
+
pageIdx: options.pageIdx,
|
|
31
|
+
}
|
|
32
|
+
: undefined,
|
|
33
|
+
resourceTypes: options?.resourceTypes,
|
|
30
34
|
};
|
|
31
35
|
}
|
|
32
36
|
setIncludeConsoleData(value) {
|
|
@@ -39,7 +43,7 @@ export class McpResponse {
|
|
|
39
43
|
return this.#includePages;
|
|
40
44
|
}
|
|
41
45
|
get includeNetworkRequests() {
|
|
42
|
-
return this.#
|
|
46
|
+
return this.#networkRequestsOptions?.include ?? false;
|
|
43
47
|
}
|
|
44
48
|
get includeConsoleData() {
|
|
45
49
|
return this.#includeConsoleData;
|
|
@@ -48,7 +52,7 @@ export class McpResponse {
|
|
|
48
52
|
return this.#attachedNetworkRequestUrl;
|
|
49
53
|
}
|
|
50
54
|
get networkRequestsPageIdx() {
|
|
51
|
-
return this.#
|
|
55
|
+
return this.#networkRequestsOptions?.pagination?.pageIdx;
|
|
52
56
|
}
|
|
53
57
|
appendResponseLine(value) {
|
|
54
58
|
this.#textResponseLines.push(value);
|
|
@@ -122,17 +126,25 @@ Call browser_handle_dialog to handle it before continuing.`);
|
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
response.push(...this.#getIncludeNetworkRequestsData(context));
|
|
125
|
-
if (this.#
|
|
126
|
-
|
|
129
|
+
if (this.#networkRequestsOptions?.include) {
|
|
130
|
+
let requests = context.getNetworkRequests();
|
|
131
|
+
// Apply resource type filtering if specified
|
|
132
|
+
if (this.#networkRequestsOptions.resourceTypes?.length) {
|
|
133
|
+
const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes);
|
|
134
|
+
requests = requests.filter(request => {
|
|
135
|
+
const type = request.resourceType();
|
|
136
|
+
return normalizedTypes.has(type);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
127
139
|
response.push('## Network requests');
|
|
128
140
|
if (requests.length) {
|
|
129
|
-
const paginationResult = paginate(requests, this.#
|
|
141
|
+
const paginationResult = paginate(requests, this.#networkRequestsOptions.pagination);
|
|
130
142
|
if (paginationResult.invalidPage) {
|
|
131
143
|
response.push('Invalid page number provided. Showing first page.');
|
|
132
144
|
}
|
|
133
145
|
const { startIndex, endIndex, currentPage, totalPages } = paginationResult;
|
|
134
146
|
response.push(`Showing ${startIndex + 1}-${endIndex} of ${requests.length} (Page ${currentPage + 1} of ${totalPages}).`);
|
|
135
|
-
if (this.#
|
|
147
|
+
if (this.#networkRequestsOptions.pagination) {
|
|
136
148
|
if (paginationResult.hasNextPage) {
|
|
137
149
|
response.push(`Next page: ${currentPage + 1}`);
|
|
138
150
|
}
|
package/build/src/cli.js
CHANGED
|
@@ -46,8 +46,7 @@ export const cliOptions = {
|
|
|
46
46
|
},
|
|
47
47
|
logFile: {
|
|
48
48
|
type: 'string',
|
|
49
|
-
describe: '
|
|
50
|
-
hidden: true,
|
|
49
|
+
describe: 'Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports.',
|
|
51
50
|
},
|
|
52
51
|
};
|
|
53
52
|
export function parseArguments(version, argv = process.argv) {
|
package/build/src/index.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Copyright 2025 Google LLC
|
|
5
5
|
* SPDX-License-Identifier: Apache-2.0
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
import { version } from 'node:process';
|
|
8
|
+
const [major, minor] = version.substring(1).split('.').map(Number);
|
|
8
9
|
if (major < 22 || (major === 22 && minor < 12)) {
|
|
9
10
|
console.error(`ERROR: \`chrome-devtools-mcp\` does not support Node ${process.version}. Please upgrade to Node 22.12.0 or newer.`);
|
|
10
11
|
process.exit(1);
|
|
11
12
|
}
|
|
12
13
|
await import('./main.js');
|
|
13
|
-
export {};
|
package/build/src/main.js
CHANGED
|
@@ -70,7 +70,7 @@ async function getContext() {
|
|
|
70
70
|
const logDisclaimers = () => {
|
|
71
71
|
console.error(`chrome-devtools-mcp exposes content of the browser instance to the MCP clients allowing them to inspect,
|
|
72
72
|
debug, and modify any data in the browser or DevTools.
|
|
73
|
-
Avoid sharing sensitive or personal information that you do want to share with MCP clients.`);
|
|
73
|
+
Avoid sharing sensitive or personal information that you do not want to share with MCP clients.`);
|
|
74
74
|
};
|
|
75
75
|
const toolMutex = new Mutex();
|
|
76
76
|
function registerTool(tool) {
|
|
@@ -6,6 +6,27 @@
|
|
|
6
6
|
import z from 'zod';
|
|
7
7
|
import { ToolCategories } from './categories.js';
|
|
8
8
|
import { defineTool } from './ToolDefinition.js';
|
|
9
|
+
const FILTERABLE_RESOURCE_TYPES = [
|
|
10
|
+
'document',
|
|
11
|
+
'stylesheet',
|
|
12
|
+
'image',
|
|
13
|
+
'media',
|
|
14
|
+
'font',
|
|
15
|
+
'script',
|
|
16
|
+
'texttrack',
|
|
17
|
+
'xhr',
|
|
18
|
+
'fetch',
|
|
19
|
+
'prefetch',
|
|
20
|
+
'eventsource',
|
|
21
|
+
'websocket',
|
|
22
|
+
'manifest',
|
|
23
|
+
'signedexchange',
|
|
24
|
+
'ping',
|
|
25
|
+
'cspviolationreport',
|
|
26
|
+
'preflight',
|
|
27
|
+
'fedcm',
|
|
28
|
+
'other',
|
|
29
|
+
];
|
|
9
30
|
export const listNetworkRequests = defineTool({
|
|
10
31
|
name: 'list_network_requests',
|
|
11
32
|
description: `List all requests for the currently selected page`,
|
|
@@ -26,11 +47,16 @@ export const listNetworkRequests = defineTool({
|
|
|
26
47
|
.min(0)
|
|
27
48
|
.optional()
|
|
28
49
|
.describe('Page number to return (0-based). When omitted, returns the first page.'),
|
|
50
|
+
resourceTypes: z
|
|
51
|
+
.array(z.enum(FILTERABLE_RESOURCE_TYPES))
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Filter requests to only return requests of the specified resource types. When omitted or empty, returns all requests.'),
|
|
29
54
|
},
|
|
30
55
|
handler: async (request, response) => {
|
|
31
56
|
response.setIncludeNetworkRequests(true, {
|
|
32
57
|
pageSize: request.params.pageSize,
|
|
33
58
|
pageIdx: request.params.pageIdx,
|
|
59
|
+
resourceTypes: request.params.resourceTypes,
|
|
34
60
|
});
|
|
35
61
|
},
|
|
36
62
|
});
|
|
@@ -10,7 +10,7 @@ import { ToolCategories } from './categories.js';
|
|
|
10
10
|
import { defineTool } from './ToolDefinition.js';
|
|
11
11
|
export const startTrace = defineTool({
|
|
12
12
|
name: 'performance_start_trace',
|
|
13
|
-
description: 'Starts a performance trace recording on the selected page.',
|
|
13
|
+
description: 'Starts a performance trace recording on the selected page. This can be used to look for performance problems and insights to improve the performance of the page. It will also report Core Web Vital (CWV) scores for the page.',
|
|
14
14
|
annotations: {
|
|
15
15
|
category: ToolCategories.PERFORMANCE,
|
|
16
16
|
readOnlyHint: true,
|
|
@@ -18,7 +18,7 @@ export const startTrace = defineTool({
|
|
|
18
18
|
schema: {
|
|
19
19
|
reload: z
|
|
20
20
|
.boolean()
|
|
21
|
-
.describe('Determines if, once tracing has started, the page should be automatically reloaded'),
|
|
21
|
+
.describe('Determines if, once tracing has started, the page should be automatically reloaded.'),
|
|
22
22
|
autoStop: z
|
|
23
23
|
.boolean()
|
|
24
24
|
.describe('Determines if the trace recording should be automatically stopped.'),
|
|
@@ -49,11 +49,19 @@ export async function parseRawTraceBuffer(buffer) {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
const extraFormatDescriptions = `Information on performance traces may contain main thread activity represented as call frames and network requests.
|
|
53
|
+
|
|
54
|
+
${PerformanceTraceFormatter.callFrameDataFormatDescription}
|
|
55
|
+
|
|
56
|
+
${PerformanceTraceFormatter.networkDataFormatDescription}
|
|
57
|
+
`;
|
|
52
58
|
export function getTraceSummary(result) {
|
|
53
59
|
const focus = AgentFocus.fromParsedTrace(result.parsedTrace);
|
|
54
|
-
const formatter = new PerformanceTraceFormatter(focus
|
|
60
|
+
const formatter = new PerformanceTraceFormatter(focus);
|
|
55
61
|
const output = formatter.formatTraceSummary();
|
|
56
|
-
return
|
|
62
|
+
return `${extraFormatDescriptions}
|
|
63
|
+
|
|
64
|
+
${output}`;
|
|
57
65
|
}
|
|
58
66
|
export function getInsightOutput(result, insightName) {
|
|
59
67
|
if (!result.insights) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-devtools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP server for Chrome DevTools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./build/src/index.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@types/yargs": "^17.0.33",
|
|
52
52
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
53
53
|
"@typescript-eslint/parser": "^8.43.0",
|
|
54
|
-
"chrome-devtools-frontend": "1.0.
|
|
54
|
+
"chrome-devtools-frontend": "1.0.1520535",
|
|
55
55
|
"eslint": "^9.35.0",
|
|
56
56
|
"eslint-plugin-import": "^2.32.0",
|
|
57
57
|
"eslint-import-resolver-typescript": "^4.4.4",
|