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 ( ) ;
133-
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 = ( ) => {
167- 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- } ,
220- ) ;
221- }
9+ const [ major ] = process . version . substring ( 1 ) . split ( '.' ) . map ( Number ) ;
22210
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 ) ;
11+ if ( major < 22 ) {
12+ console . error ( `ERROR: \`chrome-devtools-mcp\` does not support Node ${ process . version } . Please upgrade to Node 22+.` ) ;
13+ process . exit ( 1 ) ;
23614}
23715
238- const transport = new StdioServerTransport ( ) ;
239- await server . connect ( transport ) ;
240- logger ( 'Chrome DevTools MCP Server connected' ) ;
241- logDisclaimers ( ) ;
16+ await import ( './main.js' ) ;
0 commit comments