chrome-devtools-mcp 0.17.3 → 0.18.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.
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Google LLC
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+ import fs from 'node:fs/promises';
8
+ import { createServer } from 'node:net';
9
+ import process from 'node:process';
10
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
11
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
12
+ import { logger } from '../logger.js';
13
+ import { PipeTransport } from '../third_party/index.js';
14
+ import { VERSION } from '../version.js';
15
+ import { getSocketPath, handlePidFile, INDEX_SCRIPT_PATH, IS_WINDOWS, } from './utils.js';
16
+ const pidFile = handlePidFile();
17
+ const socketPath = getSocketPath();
18
+ let mcpClient = null;
19
+ let mcpTransport = null;
20
+ let server = null;
21
+ async function setupMCPClient() {
22
+ console.log('Setting up MCP client connection...');
23
+ const args = process.argv.slice(2);
24
+ // Create stdio transport for chrome-devtools-mcp
25
+ mcpTransport = new StdioClientTransport({
26
+ command: process.execPath,
27
+ args: [INDEX_SCRIPT_PATH, ...args],
28
+ env: process.env,
29
+ });
30
+ mcpClient = new Client({
31
+ name: 'chrome-devtools-cli-daemon',
32
+ version: VERSION,
33
+ }, {
34
+ capabilities: {},
35
+ });
36
+ await mcpClient.connect(mcpTransport);
37
+ console.log('MCP client connected');
38
+ }
39
+ async function handleRequest(msg) {
40
+ try {
41
+ if (msg.method === 'invoke_tool') {
42
+ if (!mcpClient) {
43
+ throw new Error('MCP client not initialized');
44
+ }
45
+ const { tool, args } = msg;
46
+ const result = (await mcpClient.callTool({
47
+ name: tool,
48
+ arguments: args || {},
49
+ }));
50
+ return {
51
+ success: true,
52
+ result: JSON.stringify(result),
53
+ };
54
+ }
55
+ else if (msg.method === 'stop') {
56
+ // Trigger cleanup asynchronously
57
+ setImmediate(() => {
58
+ void cleanup();
59
+ });
60
+ return {
61
+ success: true,
62
+ message: 'stopping',
63
+ };
64
+ }
65
+ else {
66
+ return {
67
+ success: false,
68
+ error: `Unknown method: ${JSON.stringify(msg, null, 2)}`,
69
+ };
70
+ }
71
+ }
72
+ catch (error) {
73
+ const errorMessage = error instanceof Error ? error.message : String(error);
74
+ return {
75
+ success: false,
76
+ error: errorMessage,
77
+ };
78
+ }
79
+ }
80
+ async function startSocketServer() {
81
+ // Remove existing socket file if it exists (only on non-Windows)
82
+ if (!IS_WINDOWS) {
83
+ try {
84
+ await fs.unlink(socketPath);
85
+ }
86
+ catch {
87
+ // ignore errors.
88
+ }
89
+ }
90
+ return await new Promise((resolve, reject) => {
91
+ server = createServer(socket => {
92
+ const transport = new PipeTransport(socket, socket);
93
+ transport.onmessage = async (message) => {
94
+ logger('onmessage', message);
95
+ const response = await handleRequest(JSON.parse(message));
96
+ transport.send(JSON.stringify(response));
97
+ socket.end();
98
+ };
99
+ socket.on('error', error => {
100
+ logger('Socket error:', error);
101
+ });
102
+ });
103
+ server.listen({
104
+ path: socketPath,
105
+ readableAll: false,
106
+ writableAll: false,
107
+ }, async () => {
108
+ console.log(`Daemon server listening on ${socketPath}`);
109
+ try {
110
+ // Setup MCP client
111
+ await setupMCPClient();
112
+ resolve();
113
+ }
114
+ catch (err) {
115
+ reject(err);
116
+ }
117
+ });
118
+ server.on('error', error => {
119
+ logger('Server error:', error);
120
+ reject(error);
121
+ });
122
+ });
123
+ }
124
+ async function cleanup() {
125
+ console.log('Cleaning up daemon...');
126
+ try {
127
+ await mcpClient?.close();
128
+ }
129
+ catch (error) {
130
+ logger('Error closing MCP client:', error);
131
+ }
132
+ try {
133
+ await mcpTransport?.close();
134
+ }
135
+ catch (error) {
136
+ logger('Error closing MCP transport:', error);
137
+ }
138
+ server?.close(() => {
139
+ if (!IS_WINDOWS) {
140
+ void fs.unlink(socketPath).catch(() => undefined);
141
+ }
142
+ });
143
+ await fs.unlink(pidFile).catch(() => undefined);
144
+ process.exit(0);
145
+ }
146
+ // Handle shutdown signals
147
+ process.on('SIGTERM', () => {
148
+ void cleanup();
149
+ });
150
+ process.on('SIGINT', () => {
151
+ void cleanup();
152
+ });
153
+ process.on('SIGHUP', () => {
154
+ void cleanup();
155
+ });
156
+ // Handle uncaught errors
157
+ process.on('uncaughtException', error => {
158
+ logger('Uncaught exception:', error);
159
+ });
160
+ process.on('unhandledRejection', error => {
161
+ logger('Unhandled rejection:', error);
162
+ });
163
+ // Start the server
164
+ startSocketServer().catch(error => {
165
+ logger('Failed to start daemon server:', error);
166
+ process.exit(1);
167
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import fs from 'node:fs';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import process from 'node:process';
10
+ export const DAEMON_SCRIPT_PATH = path.join(import.meta.dirname, 'daemon.js');
11
+ export const INDEX_SCRIPT_PATH = path.join(import.meta.dirname, '..', 'index.js');
12
+ const APP_NAME = 'chrome-devtools-mcp';
13
+ // Using these paths due to strict limits on the POSIX socket path length.
14
+ export function getSocketPath() {
15
+ const uid = os.userInfo().uid;
16
+ if (IS_WINDOWS) {
17
+ // Windows uses Named Pipes, not file paths.
18
+ // This format is required for server.listen()
19
+ return path.join('\\\\.\\pipe', APP_NAME, 'server.sock');
20
+ }
21
+ // 1. Try XDG_RUNTIME_DIR (Linux standard, sometimes macOS)
22
+ if (process.env.XDG_RUNTIME_DIR) {
23
+ return path.join(process.env.XDG_RUNTIME_DIR, APP_NAME, 'server.sock');
24
+ }
25
+ // 2. macOS/Unix Fallback: Use /tmp/
26
+ // We use /tmp/ because it is much shorter than ~/Library/Application Support/
27
+ // and keeps us well under the 104-character limit.
28
+ return path.join('/tmp', `${APP_NAME}-${uid}.sock`);
29
+ }
30
+ export function getRuntimeHome() {
31
+ const platform = os.platform();
32
+ const uid = os.userInfo().uid;
33
+ // 1. Check for the modern Unix standard
34
+ if (process.env.XDG_RUNTIME_DIR) {
35
+ return path.join(process.env.XDG_RUNTIME_DIR, APP_NAME);
36
+ }
37
+ // 2. Fallback for macOS and older Linux
38
+ if (platform === 'darwin' || platform === 'linux') {
39
+ // /tmp is cleared on boot, making it perfect for PIDs
40
+ return path.join('/tmp', `${APP_NAME}-${uid}`);
41
+ }
42
+ // 3. Windows Fallback
43
+ return path.join(os.tmpdir(), APP_NAME);
44
+ }
45
+ export const IS_WINDOWS = os.platform() === 'win32';
46
+ export function handlePidFile() {
47
+ const runtimeDir = getRuntimeHome();
48
+ const pidPath = path.join(runtimeDir, 'daemon.pid');
49
+ if (fs.existsSync(pidPath)) {
50
+ const oldPid = parseInt(fs.readFileSync(pidPath, 'utf8'), 10);
51
+ try {
52
+ // Sending signal 0 checks if the process is still alive without killing it
53
+ process.kill(oldPid, 0);
54
+ console.error('Daemon is already running!');
55
+ process.exit(1);
56
+ }
57
+ catch {
58
+ // Process is dead, we can safely overwrite the PID file
59
+ fs.unlinkSync(pidPath);
60
+ }
61
+ }
62
+ fs.mkdirSync(path.dirname(pidPath), {
63
+ recursive: true,
64
+ });
65
+ fs.writeFileSync(pidPath, process.pid.toString());
66
+ return pidPath;
67
+ }
@@ -3,12 +3,10 @@
3
3
  * Copyright 2026 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- var _a;
7
6
  import { createStackTraceForConsoleMessage, SymbolizedError, } from '../DevtoolsUtils.js';
8
7
  import { UncaughtError } from '../PageCollector.js';
9
8
  import * as DevTools from '../third_party/index.js';
10
9
  export class ConsoleFormatter {
11
- static #STACK_TRACE_MAX_LINES = 50;
12
10
  #id;
13
11
  #type;
14
12
  #text;
@@ -16,7 +14,7 @@ export class ConsoleFormatter {
16
14
  #resolvedArgs;
17
15
  #stack;
18
16
  #cause;
19
- #isIgnored;
17
+ isIgnored;
20
18
  constructor(params) {
21
19
  this.#id = params.id;
22
20
  this.#type = params.type;
@@ -25,7 +23,7 @@ export class ConsoleFormatter {
25
23
  this.#resolvedArgs = params.resolvedArgs ?? [];
26
24
  this.#stack = params.stack;
27
25
  this.#cause = params.cause;
28
- this.#isIgnored = params.isIgnored;
26
+ this.isIgnored = params.isIgnored;
29
27
  }
30
28
  static async from(msg, options) {
31
29
  const ignoreListManager = options?.devTools?.universe.context.get(DevTools.DevTools.IgnoreListManager);
@@ -51,7 +49,7 @@ export class ConsoleFormatter {
51
49
  resolvedStackTraceForTesting: options?.resolvedStackTraceForTesting,
52
50
  resolvedCauseForTesting: options?.resolvedCauseForTesting,
53
51
  });
54
- return new _a({
52
+ return new ConsoleFormatter({
55
53
  id: options.id,
56
54
  type: 'error',
57
55
  text: error.message,
@@ -96,7 +94,7 @@ export class ConsoleFormatter {
96
94
  // ignore
97
95
  }
98
96
  }
99
- return new _a({
97
+ return new ConsoleFormatter({
100
98
  id: options.id,
101
99
  type: msg.type(),
102
100
  text: msg.text(),
@@ -108,19 +106,11 @@ export class ConsoleFormatter {
108
106
  }
109
107
  // The short format for a console message.
110
108
  toString() {
111
- return `msgid=${this.#id} [${this.#type}] ${this.#text} (${this.#argCount} args)`;
109
+ return convertConsoleMessageConciseToString(this.toJSON());
112
110
  }
113
111
  // The verbose format for a console message, including all details.
114
112
  toStringDetailed() {
115
- const result = [
116
- `ID: ${this.#id}`,
117
- `Message: ${this.#type}> ${this.#text}`,
118
- this.#formatArgs(),
119
- this.#formatStackTrace(this.#stack, this.#cause, {
120
- includeHeading: true,
121
- }),
122
- ].filter(line => !!line);
123
- return result.join('\n');
113
+ return convertConsoleMessageConciseDetailedToString(this.toJSONDetailed());
124
114
  }
125
115
  #getArgs() {
126
116
  if (this.#resolvedArgs.length > 0) {
@@ -133,92 +123,6 @@ export class ConsoleFormatter {
133
123
  }
134
124
  return [];
135
125
  }
136
- #formatArg(arg) {
137
- if (arg instanceof SymbolizedError) {
138
- return [
139
- arg.message,
140
- this.#formatStackTrace(arg.stackTrace, arg.cause, {
141
- includeHeading: false,
142
- }),
143
- ]
144
- .filter(line => !!line)
145
- .join('\n');
146
- }
147
- return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
148
- }
149
- #formatArgs() {
150
- const args = this.#getArgs();
151
- if (!args.length) {
152
- return '';
153
- }
154
- const result = ['### Arguments'];
155
- for (const [key, arg] of args.entries()) {
156
- result.push(`Arg #${key}: ${this.#formatArg(arg)}`);
157
- }
158
- return result.join('\n');
159
- }
160
- #formatStackTrace(stackTrace, cause, opts) {
161
- if (!stackTrace) {
162
- return '';
163
- }
164
- const lines = this.#formatStackTraceInner(stackTrace, cause);
165
- const includedLines = lines.slice(0, _a.#STACK_TRACE_MAX_LINES);
166
- const reminderCount = lines.length - includedLines.length;
167
- return [
168
- opts.includeHeading ? '### Stack trace' : '',
169
- ...includedLines,
170
- reminderCount > 0 ? `... and ${reminderCount} more frames` : '',
171
- 'Note: line and column numbers use 1-based indexing',
172
- ]
173
- .filter(line => !!line)
174
- .join('\n');
175
- }
176
- #formatStackTraceInner(stackTrace, cause) {
177
- if (!stackTrace) {
178
- return [];
179
- }
180
- return [
181
- ...this.#formatFragment(stackTrace.syncFragment),
182
- ...stackTrace.asyncFragments
183
- .map(this.#formatAsyncFragment.bind(this))
184
- .flat(),
185
- ...this.#formatCause(cause),
186
- ];
187
- }
188
- #formatFragment(fragment) {
189
- const frames = fragment.frames.filter(frame => !this.#isIgnored(frame));
190
- return frames.map(this.#formatFrame.bind(this));
191
- }
192
- #formatAsyncFragment(fragment) {
193
- const formattedFrames = this.#formatFragment(fragment);
194
- if (formattedFrames.length === 0) {
195
- return [];
196
- }
197
- const separatorLineLength = 40;
198
- const prefix = `--- ${fragment.description || 'async'} `;
199
- const separator = prefix + '-'.repeat(separatorLineLength - prefix.length);
200
- return [separator, ...formattedFrames];
201
- }
202
- #formatFrame(frame) {
203
- let result = `at ${frame.name ?? '<anonymous>'}`;
204
- if (frame.uiSourceCode) {
205
- const location = frame.uiSourceCode.uiLocation(frame.line, frame.column);
206
- result += ` (${location.linkText(/* skipTrim */ false, /* showColumnNumber */ true)})`;
207
- }
208
- else if (frame.url) {
209
- result += ` (${frame.url}:${frame.line}:${frame.column})`;
210
- }
211
- return result;
212
- }
213
- #formatCause(cause) {
214
- if (!cause) {
215
- return [];
216
- }
217
- return [
218
- `Caused by: ${cause.message}`,
219
- ...this.#formatStackTraceInner(cause.stackTrace, cause.cause),
220
- ];
221
- }
222
126
  toJSON() {
223
127
  return {
224
128
  type: this.#type,
@@ -232,9 +136,106 @@ export class ConsoleFormatter {
232
136
  id: this.#id,
233
137
  type: this.#type,
234
138
  text: this.#text,
235
- args: this.#getArgs().map(arg => typeof arg === 'object' ? arg : String(arg)),
236
- stackTrace: this.#stack,
139
+ argsCount: this.#argCount,
140
+ args: this.#getArgs().map(arg => formatArg(arg, this)),
141
+ stackTrace: this.#stack
142
+ ? formatStackTrace(this.#stack, this.#cause, this)
143
+ : undefined,
237
144
  };
238
145
  }
239
146
  }
240
- _a = ConsoleFormatter;
147
+ function convertConsoleMessageConciseToString(msg) {
148
+ return `msgid=${msg.id} [${msg.type}] ${msg.text} (${msg.argsCount} args)`;
149
+ }
150
+ function convertConsoleMessageConciseDetailedToString(msg) {
151
+ const result = [
152
+ `ID: ${msg.id}`,
153
+ `Message: ${msg.type}> ${msg.text}`,
154
+ formatArgs(msg),
155
+ ...(msg.stackTrace ? ['### Stack trace', msg.stackTrace] : []),
156
+ ].filter(line => !!line);
157
+ return result.join('\n');
158
+ }
159
+ function formatArgs(msg) {
160
+ const args = msg.args;
161
+ if (!args.length) {
162
+ return '';
163
+ }
164
+ const result = ['### Arguments'];
165
+ for (const [key, arg] of args.entries()) {
166
+ result.push(`Arg #${key}: ${arg}`);
167
+ }
168
+ return result.join('\n');
169
+ }
170
+ function formatArg(arg, formatter) {
171
+ if (arg instanceof SymbolizedError) {
172
+ return [
173
+ arg.message,
174
+ arg.stackTrace
175
+ ? formatStackTrace(arg.stackTrace, arg.cause, formatter)
176
+ : undefined,
177
+ ]
178
+ .filter(line => !!line)
179
+ .join('\n');
180
+ }
181
+ return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
182
+ }
183
+ const STACK_TRACE_MAX_LINES = 50;
184
+ function formatStackTrace(stackTrace, cause, formatter) {
185
+ const lines = formatStackTraceInner(stackTrace, cause, formatter);
186
+ const includedLines = lines.slice(0, STACK_TRACE_MAX_LINES);
187
+ const reminderCount = lines.length - includedLines.length;
188
+ return [
189
+ ...includedLines,
190
+ reminderCount > 0 ? `... and ${reminderCount} more frames` : '',
191
+ 'Note: line and column numbers use 1-based indexing',
192
+ ]
193
+ .filter(line => !!line)
194
+ .join('\n');
195
+ }
196
+ function formatStackTraceInner(stackTrace, cause, formatter) {
197
+ if (!stackTrace) {
198
+ return [];
199
+ }
200
+ return [
201
+ ...formatFragment(stackTrace.syncFragment, formatter),
202
+ ...stackTrace.asyncFragments
203
+ .map(item => formatAsyncFragment(item, formatter))
204
+ .flat(),
205
+ ...formatCause(cause, formatter),
206
+ ];
207
+ }
208
+ function formatFragment(fragment, formatter) {
209
+ const frames = fragment.frames.filter(frame => !formatter.isIgnored(frame));
210
+ return frames.map(formatFrame);
211
+ }
212
+ function formatAsyncFragment(fragment, formatter) {
213
+ const formattedFrames = formatFragment(fragment, formatter);
214
+ if (formattedFrames.length === 0) {
215
+ return [];
216
+ }
217
+ const separatorLineLength = 40;
218
+ const prefix = `--- ${fragment.description || 'async'} `;
219
+ const separator = prefix + '-'.repeat(separatorLineLength - prefix.length);
220
+ return [separator, ...formattedFrames];
221
+ }
222
+ function formatFrame(frame) {
223
+ let result = `at ${frame.name ?? '<anonymous>'}`;
224
+ if (frame.uiSourceCode) {
225
+ const location = frame.uiSourceCode.uiLocation(frame.line, frame.column);
226
+ result += ` (${location.linkText(/* skipTrim */ false, /* showColumnNumber */ true)})`;
227
+ }
228
+ else if (frame.url) {
229
+ result += ` (${frame.url}:${frame.line}:${frame.column})`;
230
+ }
231
+ return result;
232
+ }
233
+ function formatCause(cause, formatter) {
234
+ if (!cause) {
235
+ return [];
236
+ }
237
+ return [
238
+ `Caused by: ${cause.message}`,
239
+ ...formatStackTraceInner(cause.stackTrace, cause.cause, formatter),
240
+ ];
241
+ }
@@ -14,56 +14,10 @@ export class IssueFormatter {
14
14
  this.#options = options;
15
15
  }
16
16
  toString() {
17
- const title = this.#getTitle();
18
- const count = this.#issue.getAggregatedIssuesCount();
19
- const idPart = this.#options.id !== undefined ? `msgid=${this.#options.id} ` : '';
20
- return `${idPart}[issue] ${title} (count: ${count})`;
17
+ return convertIssueConciseToString(this.toJSON());
21
18
  }
22
19
  toStringDetailed() {
23
- const result = [];
24
- if (this.#options.id !== undefined) {
25
- result.push(`ID: ${this.#options.id}`);
26
- }
27
- const bodyParts = [];
28
- const description = this.#getDescription();
29
- let processedMarkdown = description?.trim();
30
- // Remove heading in order not to conflict with the whole console message response markdown
31
- if (processedMarkdown?.startsWith('# ')) {
32
- processedMarkdown = processedMarkdown.substring(2).trimStart();
33
- }
34
- if (processedMarkdown) {
35
- bodyParts.push(processedMarkdown);
36
- }
37
- else {
38
- bodyParts.push(this.#getTitle() ?? 'Unknown Issue');
39
- }
40
- const links = this.#issue.getDescription()?.links;
41
- if (links && links.length > 0) {
42
- bodyParts.push('Learn more:');
43
- for (const link of links) {
44
- bodyParts.push(`[${link.linkTitle}](${link.link})`);
45
- }
46
- }
47
- const affectedResources = this.#getAffectedResources();
48
- if (affectedResources.length) {
49
- bodyParts.push('### Affected resources');
50
- bodyParts.push(...affectedResources.map(item => {
51
- const details = [];
52
- if (item.uid) {
53
- details.push(`uid=${item.uid}`);
54
- }
55
- if (item.request) {
56
- details.push((typeof item.request === 'number' ? `reqid=` : 'url=') +
57
- item.request);
58
- }
59
- if (item.data) {
60
- details.push(`data=${JSON.stringify(item.data)}`);
61
- }
62
- return details.join(' ');
63
- }));
64
- }
65
- result.push(`Message: issue> ${bodyParts.join('\n')}`);
66
- return result.join('\n');
20
+ return convertIssueDetailedToString(this.toJSONDetailed());
67
21
  }
68
22
  toJSON() {
69
23
  return {
@@ -77,6 +31,7 @@ export class IssueFormatter {
77
31
  return {
78
32
  id: this.#options.id,
79
33
  type: 'issue',
34
+ count: this.#issue.getAggregatedIssuesCount(),
80
35
  title: this.#getTitle(),
81
36
  description: this.#getDescription(),
82
37
  links: this.#issue.getDescription()?.links,
@@ -188,3 +143,50 @@ export class IssueFormatter {
188
143
  }
189
144
  }
190
145
  }
146
+ function convertIssueConciseToString(issue) {
147
+ return `msgid=${issue.id} [issue] ${issue.title} (count: ${issue.count})`;
148
+ }
149
+ function convertIssueDetailedToString(issue) {
150
+ const result = [];
151
+ result.push(`ID: ${issue.id}`);
152
+ const bodyParts = [];
153
+ const description = issue.description;
154
+ let processedMarkdown = description?.trim();
155
+ // Remove heading in order not to conflict with the whole console message response markdown
156
+ if (processedMarkdown?.startsWith('# ')) {
157
+ processedMarkdown = processedMarkdown.substring(2).trimStart();
158
+ }
159
+ if (processedMarkdown) {
160
+ bodyParts.push(processedMarkdown);
161
+ }
162
+ else {
163
+ bodyParts.push(issue.title ?? 'Unknown Issue');
164
+ }
165
+ const links = issue.links;
166
+ if (links && links.length > 0) {
167
+ bodyParts.push('Learn more:');
168
+ for (const link of links) {
169
+ bodyParts.push(`[${link.linkTitle}](${link.link})`);
170
+ }
171
+ }
172
+ const affectedResources = issue.affectedResources;
173
+ if (affectedResources.length) {
174
+ bodyParts.push('### Affected resources');
175
+ bodyParts.push(...affectedResources.map(item => {
176
+ const details = [];
177
+ if (item.uid) {
178
+ details.push(`uid=${item.uid}`);
179
+ }
180
+ if (item.request) {
181
+ details.push((typeof item.request === 'number' ? `reqid=` : 'url=') +
182
+ item.request);
183
+ }
184
+ if (item.data) {
185
+ details.push(`data=${JSON.stringify(item.data)}`);
186
+ }
187
+ return details.join(' ');
188
+ }));
189
+ }
190
+ result.push(`Message: issue> ${bodyParts.join('\n')}`);
191
+ return result.join('\n');
192
+ }
package/build/src/main.js CHANGED
@@ -12,16 +12,14 @@ import { logger, saveLogsToFile } from './logger.js';
12
12
  import { McpContext } from './McpContext.js';
13
13
  import { McpResponse } from './McpResponse.js';
14
14
  import { Mutex } from './Mutex.js';
15
+ import { SlimMcpResponse } from './SlimMcpResponse.js';
15
16
  import { ClearcutLogger } from './telemetry/ClearcutLogger.js';
16
17
  import { computeFlagUsage } from './telemetry/flagUtils.js';
17
18
  import { bucketizeLatency } from './telemetry/metricUtils.js';
18
19
  import { McpServer, StdioServerTransport, SetLevelRequestSchema, } from './third_party/index.js';
19
20
  import { ToolCategory } from './tools/categories.js';
20
- import { tools } from './tools/tools.js';
21
- // If moved update release-please config
22
- // x-release-please-start-version
23
- const VERSION = '0.17.3';
24
- // x-release-please-end
21
+ import { createTools } from './tools/tools.js';
22
+ import { VERSION } from './version.js';
25
23
  export const args = parseArguments(VERSION);
26
24
  const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
27
25
  if (process.env['CI'] ||
@@ -39,9 +37,11 @@ if (args.usageStatistics) {
39
37
  clearcutIncludePidHeader: args.clearcutIncludePidHeader,
40
38
  });
41
39
  }
42
- process.on('unhandledRejection', (reason, promise) => {
43
- logger('Unhandled promise rejection', promise, reason);
44
- });
40
+ if (process.env['CHROME_DEVTOOLS_MCP_CRASH_ON_UNCAUGHT'] !== 'true') {
41
+ process.on('unhandledRejection', (reason, promise) => {
42
+ logger('Unhandled promise rejection', promise, reason);
43
+ });
44
+ }
45
45
  logger(`Starting Chrome DevTools MCP Server v${VERSION}`);
46
46
  const server = new McpServer({
47
47
  name: 'chrome_devtools',
@@ -96,10 +96,10 @@ const logDisclaimers = () => {
96
96
  console.error(`chrome-devtools-mcp exposes content of the browser instance to the MCP clients allowing them to inspect,
97
97
  debug, and modify any data in the browser or DevTools.
98
98
  Avoid sharing sensitive or personal information that you do not want to share with MCP clients.`);
99
- if (args.performanceCrux) {
99
+ if (!args.slim && args.performanceCrux) {
100
100
  console.error(`Performance tools may send trace URLs to the Google CrUX API to fetch real-user experience data. To disable, run with --no-performance-crux.`);
101
101
  }
102
- if (args.usageStatistics) {
102
+ if (!args.slim && args.usageStatistics) {
103
103
  console.error(`
104
104
  Google collects usage statistics to improve Chrome DevTools MCP. To opt-out, run with --no-usage-statistics.
105
105
  For more details, visit: https://github.com/ChromeDevTools/chrome-devtools-mcp#usage-statistics`);
@@ -131,6 +131,10 @@ function registerTool(tool) {
131
131
  !args.experimentalInteropTools) {
132
132
  return;
133
133
  }
134
+ if (tool.annotations.conditions?.includes('screencast') &&
135
+ !args.experimentalScreencast) {
136
+ return;
137
+ }
134
138
  server.registerTool(tool.name, {
135
139
  description: tool.description,
136
140
  inputSchema: tool.schema,
@@ -144,7 +148,7 @@ function registerTool(tool) {
144
148
  const context = await getContext();
145
149
  logger(`${tool.name} context: resolved`);
146
150
  await context.detectOpenDevToolsWindows();
147
- const response = new McpResponse();
151
+ const response = args.slim ? new SlimMcpResponse() : new McpResponse();
148
152
  await tool.handler({
149
153
  params,
150
154
  }, response, context);
@@ -184,6 +188,7 @@ function registerTool(tool) {
184
188
  }
185
189
  });
186
190
  }
191
+ const tools = createTools(args);
187
192
  for (const tool of tools) {
188
193
  registerTool(tool);
189
194
  }