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 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
- 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>
106
- using the standard config from above.
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
- return await Promise.all([this.getProfile(), this.checkEligibility()]).then(([profile, eligibilityResponse]) => {
68
+ const profile = await this.getProfile();
69
+ if (profile) {
69
70
  return {
70
- hasProfile: Boolean(profile),
71
- isEligible: eligibilityResponse?.createProfile === EligibilityStatus.ELIGIBLE
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
- return await this.#cachedProfilePromise;
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 extends PerformanceTraceFormatter {
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
- super(focus, null);
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
- const eventKey = this.eventsSerializer.keyForEvent(request);
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://wdeb.dev/articles/cls
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
- #getInsightFormatter = null;
14
- eventsSerializer;
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.eventsSerializer = focus.eventsSerializer;
26
- this.#getInsightFormatter = getInsightFormatter;
19
+ this.#eventsSerializer = focus.eventsSerializer;
27
20
  }
28
21
  serializeEvent(event) {
29
- const key = this.eventsSerializer.keyForEvent(event);
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 = this.#getInsightFormatter?.(this.#focus, model);
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.eventsSerializer.keyForEvent(request);
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.eventsSerializer.keyForEvent(request) ?? '',
591
+ this.#eventsSerializer.keyForEvent(request) ?? '',
579
592
  queuedTime,
580
593
  requestSentTime,
581
594
  downloadCompleteTime,
@@ -1,38 +1,2 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
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
- exports.default = require_nostats_subset();
146
+ export default require_nostats_subset();
@@ -1,8 +1,2 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
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 };
@@ -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
- #networkRequestsPaginationOptions;
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
- this.#includeNetworkRequests = value;
23
- if (!value || !options) {
24
- this.#networkRequestsPaginationOptions = undefined;
21
+ if (!value) {
22
+ this.#networkRequestsOptions = undefined;
25
23
  return;
26
24
  }
27
- this.#networkRequestsPaginationOptions = {
28
- pageSize: options.pageSize,
29
- pageIdx: options.pageIdx,
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.#includeNetworkRequests;
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.#networkRequestsPaginationOptions?.pageIdx;
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.#includeNetworkRequests) {
126
- const requests = context.getNetworkRequests();
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.#networkRequestsPaginationOptions);
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.#networkRequestsPaginationOptions) {
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: 'Save the logs to file.',
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) {
@@ -4,10 +4,10 @@
4
4
  * Copyright 2025 Google LLC
5
5
  * SPDX-License-Identifier: Apache-2.0
6
6
  */
7
- const [major, minor] = process.version.substring(1).split('.').map(Number);
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, PerformanceInsightFormatter.create);
60
+ const formatter = new PerformanceTraceFormatter(focus);
55
61
  const output = formatter.formatTraceSummary();
56
- return output;
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.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.1520139",
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",