@@ -88,8 +88,6 @@ export async function stopServer(server: McpServer) {
8888 console . error ( 'Error occurred during server stop:' , error ) ;
8989 }
9090 finally {
91- // Best-effort cleanup of project JSON
92- try { rmSync ( joinPath ( process . cwd ( ) , '.browser-echo-mcp.json' ) , { force : true } ) ; } catch { }
9391 process . exit ( 0 ) ;
9492 }
9593}
@@ -224,7 +222,27 @@ export async function startHttpServer(
224222 } ) ;
225223 } catch { }
226224
227- await new Promise < void > ( ( resolve ) => nodeServer . listen ( opts . port , opts . host , ( ) => resolve ( ) ) ) ;
225+ // Attempt to listen on the requested port (fail fast if already in use)
226+ try {
227+ await new Promise < void > ( ( resolve , reject ) => {
228+ nodeServer . listen ( opts . port , opts . host , ( ) => resolve ( ) ) ;
229+ nodeServer . on ( 'error' , reject ) ;
230+ } ) ;
231+ } catch ( err : any ) {
232+ const isAddrInUse = err && ( err . code === 'EADDRINUSE' || String ( err . message || '' ) . includes ( 'EADDRINUSE' ) ) ;
233+ if ( isAddrInUse ) {
234+ const errorMsg = [
235+ `Failed to start MCP server: Port ${ opts . port } is already in use.` ,
236+ `Another instance may be running. Please either:` ,
237+ ` - Stop the other instance, or` ,
238+ ` - Use a different port with --port flag`
239+ ] . join ( '\n' ) ;
240+ console . error ( errorMsg ) ;
241+ process . exit ( 1 ) ;
242+ }
243+ throw err ;
244+ }
245+
228246 // For Streamable HTTP, we intentionally do not write project JSON here. The per-project
229247 // source of truth is written by the stdio ingest server only.
230248
@@ -299,7 +317,7 @@ export async function startIngestOnlyServer(
299317 } ) ;
300318 }
301319
302- // Prefer requested port (usually 5179); fall back to ephemeral if it's taken
320+ // Prefer requested port (usually 5179); do not fall back (single-server mode)
303321 let nodeServer = createNodeServer ( toNodeListener ( app ) ) ;
304322 configureNodeServer ( nodeServer ) ;
305323
@@ -308,33 +326,19 @@ export async function startIngestOnlyServer(
308326 actualPort = await listenWithResult ( nodeServer , opts . host , opts . port ) ;
309327 } catch ( err : any ) {
310328 const isAddrInUse = err && ( err . code === 'EADDRINUSE' || String ( err . message || '' ) . includes ( 'EADDRINUSE' ) ) ;
311- if ( isAddrInUse && opts . port !== 0 ) {
312- try { nodeServer . close ?.( ) ; } catch { }
313- nodeServer = createNodeServer ( toNodeListener ( app ) ) ;
314- configureNodeServer ( nodeServer ) ;
315- actualPort = await listenWithResult ( nodeServer , opts . host , 0 ) ;
316- } else {
317- throw err ;
329+ if ( isAddrInUse ) {
330+ const base = `http://${ opts . host } :${ opts . port } ` ;
331+ // eslint-disable-next-line no-console
332+ console . error ( `Failed to start ingest-only server: Port in use at ${ base } ${ opts . logsRoute } ` ) ;
318333 }
334+ throw err ;
319335 }
320336
321- // Write project-local config for providers
322- writeProjectJson ( opts . host , actualPort , opts . logsRoute ) ;
323-
324337 // eslint-disable-next-line no-console
325338 console . error ( `Log ingest endpoint → http://${ opts . host } :${ actualPort } ${ opts . logsRoute } ` ) ;
326339}
327340
328- function writeProjectJson ( host : string , port : number , route : `/${string } `) {
329- try {
330- const baseUrl = `http://${ host } :${ port } ` ;
331- const payload = JSON . stringify ( { url : baseUrl , route, timestamp : Date . now ( ) , pid : typeof process !== 'undefined' ? process . pid : undefined } ) ;
332- const file = joinPath ( process . cwd ( ) , '.browser-echo-mcp.json' ) ;
333- const tmp = file + '.tmp' ;
334- try { writeFileSync ( tmp , payload ) ; renameSync ( tmp , file ) ; }
335- catch { try { writeFileSync ( file , payload ) ; } catch { } }
336- } catch { }
337- }
341+ // Removed project JSON discovery in single-server mode
338342
339343/** Create log ingest routes that can be attached to any H3 app */
340344function createLogIngestRoutes ( store : LogStore , logsRoute : `/${string } `) {
@@ -361,6 +365,10 @@ function createLogIngestRoutes(store: LogStore, logsRoute: `/${string}`) {
361365 return 'invalid payload' ;
362366 }
363367 const sid = String ( payload . sessionId ?? 'anon' ) ;
368+ // Extract optional project metadata from headers
369+ const hdrs = event . node . req . headers ;
370+ const projectHeader = ( hdrs [ 'x-browser-echo-project-name' ] || hdrs [ 'x-project-name' ] || hdrs [ 'x-project' ] || '' ) as string | string [ ] | undefined ;
371+ const projectName = Array . isArray ( projectHeader ) ? String ( projectHeader [ 0 ] || '' ) : String ( projectHeader || '' ) ;
364372 for ( const entry of payload . entries as Array < { level : BrowserLogLevel | string ; text : string ; time ?: number ; stack ?: string ; source ?: string ; } > ) {
365373 const level = normalizeLevel ( entry . level ) ;
366374 store . append ( {
@@ -370,7 +378,8 @@ function createLogIngestRoutes(store: LogStore, logsRoute: `/${string}`) {
370378 time : entry . time ,
371379 source : entry . source ,
372380 stack : entry . stack ,
373- tag : '[browser]'
381+ tag : '[browser]' ,
382+ project : projectName ? projectName : undefined
374383 } ) ;
375384 }
376385 setResponseStatus ( event , 204 ) ;
0 commit comments