chrome-devtools-mcp 0.2.1 → 0.2.3

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
@@ -66,6 +66,17 @@ claude mcp add chrome-devtools npx chrome-devtools-mcp@latest
66
66
  Follow https://docs.cline.bot/mcp/configuring-mcp-servers and use the config provided above.
67
67
  </details>
68
68
 
69
+ <details>
70
+ <summary>Codex</summary>
71
+ Follow the <a href="https://github.com/openai/codex/blob/main/docs/advanced.md#model-context-protocol-mcp">configure MCP guide</a>
72
+ using the standard config from above. You can also install the Chrome DevTools MCP server using the Codex CLI:
73
+
74
+ ```bash
75
+ codex mcp add chrome-devtools -- npx chrome-devtools-mcp@latest
76
+ ```
77
+
78
+ </details>
79
+
69
80
  <details>
70
81
  <summary>Copilot / VS Code</summary>
71
82
  Follow the MCP install <a href="https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server">guide</a>,
@@ -101,6 +112,19 @@ Go to `Cursor Settings` -> `MCP` -> `New MCP Server`. Use the config provided ab
101
112
  using the standard config from above.
102
113
  </details>
103
114
 
115
+ ### Your first prompt
116
+
117
+ Enter the following prompt in your MCP Client to check if everything is working:
118
+
119
+ ```
120
+ Check the performance of https://developers.chrome.com
121
+ ```
122
+
123
+ Your MCP client should open the browser and record a performance trace.
124
+
125
+ > [!NOTE]
126
+ > The MCP server will start the browser automatically once the MCP client uses a tool that requires a running browser instance. Connecting to the Chrome DevTools MCP server on its own will not automatically start the browser.
127
+
104
128
  ## Tools
105
129
 
106
130
  <!-- BEGIN AUTO GENERATED TOOLS -->
@@ -123,7 +123,12 @@ Call browser_handle_dialog to handle it before continuing.`);
123
123
  }
124
124
  if (this.#includeConsoleData && this.#formattedConsoleData) {
125
125
  response.push('## Console messages');
126
- response.push(...this.#formattedConsoleData);
126
+ if (this.#formattedConsoleData.length) {
127
+ response.push(...this.#formattedConsoleData);
128
+ }
129
+ else {
130
+ response.push('<no console messages found>');
131
+ }
127
132
  }
128
133
  const text = {
129
134
  type: 'text',
@@ -99,16 +99,27 @@ export class WaitForHelper {
99
99
  });
100
100
  }
101
101
  async waitForEventsAfterAction(action) {
102
- const navigationStartedPromise = this.waitForNavigationStarted();
103
- await action();
104
- try {
105
- const navigationStated = await navigationStartedPromise;
102
+ const navigationFinished = this.waitForNavigationStarted()
103
+ .then(navigationStated => {
106
104
  if (navigationStated) {
107
- await this.#page.waitForNavigation({
105
+ return this.#page.waitForNavigation({
108
106
  timeout: this.#navigationTimeout,
109
107
  signal: this.#abortController.signal,
110
108
  });
111
109
  }
110
+ return;
111
+ })
112
+ .catch(error => logger(error));
113
+ try {
114
+ await action();
115
+ }
116
+ catch (error) {
117
+ // Clear up pending promises
118
+ this.#abortController.abort();
119
+ throw error;
120
+ }
121
+ try {
122
+ await navigationFinished;
112
123
  // Wait for stable dom after navigation so we execute in
113
124
  // the correct context
114
125
  await this.waitForStableDom();
@@ -71,6 +71,22 @@ export const cliOptions = {
71
71
  hidden: true,
72
72
  },
73
73
  };
74
+ function readPackageJson() {
75
+ const currentDir = import.meta.dirname;
76
+ const packageJsonPath = path.join(currentDir, '..', '..', 'package.json');
77
+ if (!fs.existsSync(packageJsonPath)) {
78
+ return {};
79
+ }
80
+ try {
81
+ const json = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
82
+ assert.strict(json['name'], 'chrome-devtools-mcp');
83
+ return json;
84
+ }
85
+ catch {
86
+ return {};
87
+ }
88
+ }
89
+ const version = readPackageJson().version ?? 'unknown';
74
90
  const yargsInstance = yargs(hideBin(process.argv))
75
91
  .scriptName('npx chrome-devtools-mcp@latest')
76
92
  .options(cliOptions)
@@ -97,24 +113,9 @@ const yargsInstance = yargs(hideBin(process.argv))
97
113
  export const args = yargsInstance
98
114
  .wrap(Math.min(120, yargsInstance.terminalWidth()))
99
115
  .help()
116
+ .version(version)
100
117
  .parseSync();
101
118
  const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
102
- function readPackageJson() {
103
- const currentDir = import.meta.dirname;
104
- const packageJsonPath = path.join(currentDir, '..', '..', 'package.json');
105
- if (!fs.existsSync(packageJsonPath)) {
106
- return {};
107
- }
108
- try {
109
- const json = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
110
- assert.strict(json['name'], 'chrome-devtools-mcp');
111
- return json;
112
- }
113
- catch {
114
- return {};
115
- }
116
- }
117
- const version = readPackageJson().version ?? 'unknown';
118
119
  logger(`Starting Chrome DevTools MCP Server v${version}`);
119
120
  const server = new McpServer({
120
121
  name: 'chrome_devtools',
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import z from 'zod';
7
7
  import { defineTool } from './ToolDefinition.js';
8
- import { getInsightOutput, getTraceSummary, parseRawTraceBuffer, } from '../trace-processing/parse.js';
8
+ import { getInsightOutput, getTraceSummary, parseRawTraceBuffer, traceResultIsSuccess, } from '../trace-processing/parse.js';
9
9
  import { logger } from '../logger.js';
10
10
  import { ToolCategories } from './categories.js';
11
11
  export const startTrace = defineTool({
@@ -122,20 +122,22 @@ async function stopTracingAndAppendOutput(page, response, context) {
122
122
  const traceEventsBuffer = await page.tracing.stop();
123
123
  const result = await parseRawTraceBuffer(traceEventsBuffer);
124
124
  response.appendResponseLine('The performance trace has been stopped.');
125
- if (result) {
125
+ if (traceResultIsSuccess(result)) {
126
126
  context.storeTraceRecording(result);
127
- const insightText = getTraceSummary(result);
128
- if (insightText) {
129
- response.appendResponseLine('Insights with performance opportunities:');
130
- response.appendResponseLine(insightText);
131
- }
132
- else {
133
- response.appendResponseLine('No insights have been found. The performance looks good!');
134
- }
127
+ response.appendResponseLine('Here is a high level summary of the trace and the Insights that were found:');
128
+ const traceSummaryText = getTraceSummary(result);
129
+ response.appendResponseLine(traceSummaryText);
130
+ }
131
+ else {
132
+ response.appendResponseLine('There was an unexpected error parsing the trace:');
133
+ response.appendResponseLine(result.error);
135
134
  }
136
135
  }
137
136
  catch (e) {
138
- logger(`Error stopping performance trace: ${e instanceof Error ? e.message : JSON.stringify(e)}`);
137
+ const errorText = e instanceof Error ? e.message : JSON.stringify(e);
138
+ logger(`Error stopping performance trace: ${errorText}`);
139
+ response.appendResponseLine('An error occured generating the response for this trace:');
140
+ response.appendResponseLine(errorText);
139
141
  }
140
142
  finally {
141
143
  context.setIsRunningPerformanceTrace(false);
@@ -9,14 +9,21 @@ import * as TraceEngine from '../../node_modules/chrome-devtools-frontend/front_
9
9
  import { logger } from '../logger.js';
10
10
  import { AgentFocus } from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js';
11
11
  const engine = TraceEngine.TraceModel.Model.createWithAllHandlers();
12
+ export function traceResultIsSuccess(x) {
13
+ return 'parsedTrace' in x;
14
+ }
12
15
  export async function parseRawTraceBuffer(buffer) {
13
16
  engine.resetProcessor();
14
17
  if (!buffer) {
15
- return null;
18
+ return {
19
+ error: 'No buffer was provided.',
20
+ };
16
21
  }
17
22
  const asString = new TextDecoder().decode(buffer);
18
23
  if (!asString) {
19
- return null;
24
+ return {
25
+ error: 'Decoding the trace buffer returned an empty string.',
26
+ };
20
27
  }
21
28
  try {
22
29
  const data = JSON.parse(asString);
@@ -24,25 +31,22 @@ export async function parseRawTraceBuffer(buffer) {
24
31
  await engine.parse(events);
25
32
  const parsedTrace = engine.parsedTrace();
26
33
  if (!parsedTrace) {
27
- return null;
28
- }
29
- const insights = parsedTrace?.insights;
30
- if (!insights) {
31
- return null;
34
+ return {
35
+ error: 'No parsed trace was returned from the trace engine.',
36
+ };
32
37
  }
38
+ const insights = parsedTrace?.insights ?? null;
33
39
  return {
34
40
  parsedTrace,
35
41
  insights,
36
42
  };
37
43
  }
38
44
  catch (e) {
39
- if (e instanceof Error) {
40
- logger(`Error parsing trace: ${e.message}`);
41
- }
42
- else {
43
- logger(`Error parsing trace: ${JSON.stringify(e)}`);
44
- }
45
- return null;
45
+ const errorText = e instanceof Error ? e.message : JSON.stringify(e);
46
+ logger(`Unexpeced error parsing trace: ${errorText}`);
47
+ return {
48
+ error: errorText,
49
+ };
46
50
  }
47
51
  }
48
52
  export function getTraceSummary(result) {
@@ -53,6 +57,11 @@ export function getTraceSummary(result) {
53
57
  return output;
54
58
  }
55
59
  export function getInsightOutput(result, insightName) {
60
+ if (!result.insights) {
61
+ return {
62
+ error: 'No Performance insights are available for this trace.',
63
+ };
64
+ }
56
65
  // Currently, we do not support inspecting traces with multiple navigations. We either:
57
66
  // 1. Find Insights from the first navigation (common case: user records a trace with a page reload to test load performance)
58
67
  // 2. Fall back to finding Insights not associated with a navigation (common case: user tests an interaction without a page load).
package/package.json CHANGED
@@ -1,11 +1,9 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "MCP server for Chrome DevTools",
5
5
  "type": "module",
6
- "bin": {
7
- "chrome-devtools-mcp": "./build/src/index.js"
8
- },
6
+ "bin": "./build/src/index.js",
9
7
  "main": "index.js",
10
8
  "scripts": {
11
9
  "build": "tsc && node --experimental-strip-types --no-warnings=ExperimentalWarning scripts/post-build.ts",
@@ -38,7 +36,7 @@
38
36
  "dependencies": {
39
37
  "@modelcontextprotocol/sdk": "1.18.1",
40
38
  "debug": "4.4.3",
41
- "puppeteer-core": "24.22.0",
39
+ "puppeteer-core": "24.22.2",
42
40
  "yargs": "18.0.0"
43
41
  },
44
42
  "devDependencies": {
@@ -54,7 +52,7 @@
54
52
  "eslint": "^9.35.0",
55
53
  "globals": "^16.4.0",
56
54
  "prettier": "^3.6.2",
57
- "puppeteer": "24.22.0",
55
+ "puppeteer": "24.22.2",
58
56
  "sinon": "^21.0.0",
59
57
  "typescript": "^5.9.2",
60
58
  "typescript-eslint": "^8.43.0"