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 +24 -0
- package/build/src/McpResponse.js +6 -1
- package/build/src/WaitForHelper.js +16 -5
- package/build/src/index.js +17 -16
- package/build/src/tools/performance.js +13 -11
- package/build/src/trace-processing/parse.js +23 -14
- package/package.json +4 -6
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 -->
|
package/build/src/McpResponse.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const navigationStated = await navigationStartedPromise;
|
|
102
|
+
const navigationFinished = this.waitForNavigationStarted()
|
|
103
|
+
.then(navigationStated => {
|
|
106
104
|
if (navigationStated) {
|
|
107
|
-
|
|
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();
|
package/build/src/index.js
CHANGED
|
@@ -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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
55
|
+
"puppeteer": "24.22.2",
|
|
58
56
|
"sinon": "^21.0.0",
|
|
59
57
|
"typescript": "^5.9.2",
|
|
60
58
|
"typescript-eslint": "^8.43.0"
|