Skip to content

Commit adfcecf

Browse files
authored
fix: validate and report incompatible Node versions (#113)
Ref #92
1 parent c9fdd2c commit adfcecf

File tree

9 files changed

+263
-235
lines changed

9 files changed

+263
-235
lines changed

.github/workflows/run-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
- windows-latest
2121
- macos-latest
2222
node:
23+
- 22.12.0
2324
- 22
2425
- 23
2526
- 24

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ MCP clients.
2727

2828
## Requirements
2929

30-
- [Node.js 22](https://nodejs.org/) or newer.
30+
- [Node.js 22.12.0](https://nodejs.org/) or newer.
3131
- [Chrome](https://www.google.com/chrome/) current stable version or newer.
3232
- [npm](https://www.npmjs.com/).
3333

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,8 @@
5858
"sinon": "^21.0.0",
5959
"typescript": "^5.9.2",
6060
"typescript-eslint": "^8.43.0"
61+
},
62+
"engines": {
63+
"node": ">=22.12.0"
6164
}
6265
}

scripts/generate-docs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {Client} from '@modelcontextprotocol/sdk/client/index.js';
88
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
99
import type {Tool} from '@modelcontextprotocol/sdk/types.js';
1010
import {ToolCategories} from '../build/src/tools/categories.js';
11-
import {cliOptions} from '../build/src/index.js';
11+
import {cliOptions} from '../build/src/main.js';
1212
import fs from 'fs';
1313

1414
const MCP_SERVER_PATH = 'build/src/index.js';

src/index.ts

Lines changed: 6 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -1,241 +1,18 @@
11
#!/usr/bin/env node
2+
23
/**
34
* @license
45
* Copyright 2025 Google LLC
56
* SPDX-License-Identifier: Apache-2.0
67
*/
78

8-
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
9-
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
10-
import {
11-
CallToolResult,
12-
SetLevelRequestSchema,
13-
} from '@modelcontextprotocol/sdk/types.js';
14-
import yargs from 'yargs';
15-
import {hideBin} from 'yargs/helpers';
16-
17-
import {McpResponse} from './McpResponse.js';
18-
import {McpContext} from './McpContext.js';
19-
20-
import {ToolDefinition} from './tools/ToolDefinition.js';
21-
import {logger, saveLogsToFile} from './logger.js';
22-
import {Channel, resolveBrowser} from './browser.js';
23-
import * as emulationTools from './tools/emulation.js';
24-
import * as consoleTools from './tools/console.js';
25-
import * as inputTools from './tools/input.js';
26-
import * as networkTools from './tools/network.js';
27-
import * as pagesTools from './tools/pages.js';
28-
import * as performanceTools from './tools/performance.js';
29-
import * as screenshotTools from './tools/screenshot.js';
30-
import * as scriptTools from './tools/script.js';
31-
import * as snapshotTools from './tools/snapshot.js';
32-
33-
import path from 'node:path';
34-
import fs from 'node:fs';
35-
import assert from 'node:assert';
36-
import {Mutex} from './Mutex.js';
37-
38-
export const cliOptions = {
39-
browserUrl: {
40-
type: 'string' as const,
41-
description:
42-
'Connect to a running Chrome instance using port forwarding. For more details see: https://developer.chrome.com/docs/devtools/remote-debugging/local-server.',
43-
alias: 'u',
44-
coerce: (url: string) => {
45-
new URL(url);
46-
return url;
47-
},
48-
},
49-
headless: {
50-
type: 'boolean' as const,
51-
description: 'Whether to run in headless (no UI) mode.',
52-
default: false,
53-
},
54-
executablePath: {
55-
type: 'string' as const,
56-
description: 'Path to custom Chrome executable.',
57-
conflicts: 'browserUrl',
58-
alias: 'e',
59-
},
60-
isolated: {
61-
type: 'boolean' as const,
62-
description:
63-
'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed.',
64-
default: false,
65-
},
66-
customDevtools: {
67-
type: 'string' as const,
68-
description: 'Path to custom DevTools.',
69-
hidden: true,
70-
conflicts: 'browserUrl',
71-
alias: 'd',
72-
},
73-
channel: {
74-
type: 'string' as const,
75-
description:
76-
'Specify a different Chrome channel that should be used. The default is the stable channel version.',
77-
choices: ['stable', 'canary', 'beta', 'dev'] as const,
78-
conflicts: ['browserUrl', 'executablePath'],
79-
},
80-
logFile: {
81-
type: 'string' as const,
82-
describe: 'Save the logs to file.',
83-
hidden: true,
84-
},
85-
};
86-
87-
function readPackageJson(): {version?: string} {
88-
const currentDir = import.meta.dirname;
89-
const packageJsonPath = path.join(currentDir, '..', '..', 'package.json');
90-
if (!fs.existsSync(packageJsonPath)) {
91-
return {};
92-
}
93-
try {
94-
const json = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
95-
assert.strict(json['name'], 'chrome-devtools-mcp');
96-
return json;
97-
} catch {
98-
return {};
99-
}
100-
}
101-
102-
const version = readPackageJson().version ?? 'unknown';
103-
104-
const yargsInstance = yargs(hideBin(process.argv))
105-
.scriptName('npx chrome-devtools-mcp@latest')
106-
.options(cliOptions)
107-
.check(args => {
108-
// We can't set default in the options else
109-
// Yargs will complain
110-
if (!args.channel && !args.browserUrl) {
111-
args.channel = 'stable';
112-
}
113-
return true;
114-
})
115-
.example([
116-
[
117-
'$0 --browserUrl http://127.0.0.1:9222',
118-
'Connect to an existing browser instance',
119-
],
120-
['$0 --channel beta', 'Use Chrome Beta installed on this system'],
121-
['$0 --channel canary', 'Use Chrome Canary installed on this system'],
122-
['$0 --channel dev', 'Use Chrome Dev installed on this system'],
123-
['$0 --channel stable', 'Use stable Chrome installed on this system'],
124-
['$0 --logFile /tmp/log.txt', 'Save logs to a file'],
125-
['$0 --help', 'Print CLI options'],
126-
]);
127-
128-
export const args = yargsInstance
129-
.wrap(Math.min(120, yargsInstance.terminalWidth()))
130-
.help()
131-
.version(version)
132-
.parseSync();
9+
const [major, minor] = process.version.substring(1).split('.').map(Number);
13310

134-
const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
135-
136-
logger(`Starting Chrome DevTools MCP Server v${version}`);
137-
const server = new McpServer(
138-
{
139-
name: 'chrome_devtools',
140-
title: 'Chrome DevTools MCP server',
141-
version,
142-
},
143-
{capabilities: {logging: {}}},
144-
);
145-
server.server.setRequestHandler(SetLevelRequestSchema, () => {
146-
return {};
147-
});
148-
149-
let context: McpContext;
150-
async function getContext(): Promise<McpContext> {
151-
const browser = await resolveBrowser({
152-
browserUrl: args.browserUrl,
153-
headless: args.headless,
154-
executablePath: args.executablePath,
155-
customDevTools: args.customDevtools,
156-
channel: args.channel as Channel,
157-
isolated: args.isolated,
158-
logFile,
159-
});
160-
if (context?.browser !== browser) {
161-
context = await McpContext.from(browser, logger);
162-
}
163-
return context;
164-
}
165-
166-
const logDisclaimers = () => {
11+
if (major < 22 || (major === 22 && minor < 12)) {
16712
console.error(
168-
`chrome-devtools-mcp exposes content of the browser instance to the MCP clients allowing them to inspect,
169-
debug, and modify any data in the browser or DevTools.
170-
Avoid sharing sensitive or personal information that you do want to share with MCP clients.`,
171-
);
172-
};
173-
174-
const toolMutex = new Mutex();
175-
176-
function registerTool(tool: ToolDefinition): void {
177-
server.registerTool(
178-
tool.name,
179-
{
180-
description: tool.description,
181-
inputSchema: tool.schema,
182-
annotations: tool.annotations,
183-
},
184-
async (params): Promise<CallToolResult> => {
185-
const guard = await toolMutex.acquire();
186-
try {
187-
logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
188-
const context = await getContext();
189-
const response = new McpResponse();
190-
await tool.handler(
191-
{
192-
params,
193-
},
194-
response,
195-
context,
196-
);
197-
try {
198-
const content = await response.handle(tool.name, context);
199-
return {
200-
content,
201-
};
202-
} catch (error) {
203-
const errorText =
204-
error instanceof Error ? error.message : String(error);
205-
206-
return {
207-
content: [
208-
{
209-
type: 'text',
210-
text: errorText,
211-
},
212-
],
213-
isError: true,
214-
};
215-
}
216-
} finally {
217-
guard.dispose();
218-
}
219-
},
13+
`ERROR: \`chrome-devtools-mcp\` does not support Node ${process.version}. Please upgrade to Node 22+.`,
22014
);
15+
process.exit(1);
22116
}
22217

223-
const tools = [
224-
...Object.values(consoleTools),
225-
...Object.values(emulationTools),
226-
...Object.values(inputTools),
227-
...Object.values(networkTools),
228-
...Object.values(pagesTools),
229-
...Object.values(performanceTools),
230-
...Object.values(screenshotTools),
231-
...Object.values(scriptTools),
232-
...Object.values(snapshotTools),
233-
];
234-
for (const tool of tools) {
235-
registerTool(tool as unknown as ToolDefinition);
236-
}
237-
238-
const transport = new StdioServerTransport();
239-
await server.connect(transport);
240-
logger('Chrome DevTools MCP Server connected');
241-
logDisclaimers();
18+
await import('./main.js');

0 commit comments

Comments
 (0)