@@ -54,21 +54,22 @@ async function ensureBrowserConnected(browserURL: string) {
5454 return browser ;
5555}
5656
57- async function ensureBrowserLaunched (
58- headless : boolean ,
59- isolated : boolean ,
60- customDevToolsPath ?: string ,
61- executablePath ?: string ,
62- channel ?: Channel ,
63- ) : Promise < Browser > {
64- if ( browser ?. connected ) {
65- return browser ;
66- }
57+ type McpLaunchOptions = {
58+ executablePath ?: string ;
59+ customDevTools ?: string ;
60+ channel ?: Channel ;
61+ userDataDir ?: string ;
62+ headless : boolean ;
63+ isolated : boolean ;
64+ } ;
65+
66+ export async function launch ( options : McpLaunchOptions ) : Promise < Browser > {
67+ const { channel, executablePath, customDevTools, headless, isolated} = options ;
6768 const profileDirName =
6869 channel && channel !== 'stable' ? `mcp-profile-${ channel } ` : 'mcp-profile' ;
6970
70- let userDataDir : string | undefined ;
71- if ( ! isolated ) {
71+ let userDataDir = options . userDataDir ;
72+ if ( ! isolated && ! userDataDir ) {
7273 userDataDir = path . join (
7374 os . homedir ( ) ,
7475 '.cache' ,
@@ -85,8 +86,8 @@ async function ensureBrowserLaunched(
8586 '--no-first-run' ,
8687 '--hide-crash-restore-bubble' ,
8788 ] ;
88- if ( customDevToolsPath ) {
89- args . push ( `--custom-devtools-frontend=file://${ customDevToolsPath } ` ) ;
89+ if ( customDevTools ) {
90+ args . push ( `--custom-devtools-frontend=file://${ customDevTools } ` ) ;
9091 }
9192 let puppeterChannel : ChromeReleaseChannel | undefined ;
9293 if ( ! executablePath ) {
@@ -95,16 +96,45 @@ async function ensureBrowserLaunched(
9596 ? ( `chrome-${ channel } ` as ChromeReleaseChannel )
9697 : 'chrome' ;
9798 }
98- browser = await puppeteer . launch ( {
99- ...connectOptions ,
100- channel : puppeterChannel ,
101- executablePath,
102- defaultViewport : null ,
103- userDataDir,
104- pipe : true ,
105- headless,
106- args,
107- } ) ;
99+
100+ try {
101+ return await puppeteer . launch ( {
102+ ...connectOptions ,
103+ channel : puppeterChannel ,
104+ executablePath,
105+ defaultViewport : null ,
106+ userDataDir,
107+ pipe : true ,
108+ headless,
109+ args,
110+ } ) ;
111+ } catch ( error ) {
112+ // TODO: check browser logs for `Failed to create a ProcessSingleton for
113+ // your profile directory` instead.
114+ if (
115+ userDataDir &&
116+ ( error as Error ) . message . includes (
117+ '(Target.setDiscoverTargets): Target closed' ,
118+ )
119+ ) {
120+ throw new Error (
121+ `The browser is already running for ${ userDataDir } . Use --isolated to run multiple browser instances.` ,
122+ {
123+ cause : error ,
124+ } ,
125+ ) ;
126+ }
127+ throw error ;
128+ }
129+ }
130+
131+ async function ensureBrowserLaunched (
132+ options : McpLaunchOptions ,
133+ ) : Promise < Browser > {
134+ if ( browser ?. connected ) {
135+ return browser ;
136+ }
137+ browser = await launch ( options ) ;
108138 return browser ;
109139}
110140
@@ -118,13 +148,7 @@ export async function resolveBrowser(options: {
118148} ) {
119149 const browser = options . browserUrl
120150 ? await ensureBrowserConnected ( options . browserUrl )
121- : await ensureBrowserLaunched (
122- options . headless ,
123- options . isolated ,
124- options . customDevTools ,
125- options . executablePath ,
126- options . channel ,
127- ) ;
151+ : await ensureBrowserLaunched ( options ) ;
128152
129153 return browser ;
130154}
0 commit comments