@@ -47,6 +47,39 @@ export async function startServer(
4747 server : McpServer ,
4848 options : StartOptions ,
4949) : Promise < void > {
50+ // Debug context: where and how this MCP server is started
51+ const __debugEnabled = ( ( ) => {
52+ try {
53+ const v = String ( process . env . BROWSER_ECHO_DEBUG ?? '' ) . trim ( ) . toLowerCase ( ) ;
54+ return v !== '' && v !== '0' && v !== 'false' ;
55+ } catch { return false ; }
56+ } ) ( ) ;
57+
58+ if ( __debugEnabled ) {
59+ try {
60+ const envSnapshot : Record < string , string > = { } ;
61+ for ( const k of Object . keys ( process . env ) ) {
62+ if ( k . startsWith ( 'BROWSER_ECHO_' ) || k === 'NODE_ENV' ) {
63+ const val = process . env [ k ] ;
64+ envSnapshot [ k ] = typeof val === 'string' ? val : '' ;
65+ }
66+ }
67+ const transport = options . type ;
68+ const host = transport === 'http' ? options . host : ( options . host || '127.0.0.1' ) ;
69+ const port = transport === 'http'
70+ ? ( options as any ) . port
71+ : ( ( ) => {
72+ const envIngest = process . env . BROWSER_ECHO_INGEST_PORT ;
73+ const preferred = ( ( options as any ) . port ?? ( envIngest ? ( Number ( envIngest ) | 0 ) : 5179 ) ) | 0 ;
74+ return preferred > 0 ? preferred : 5179 ;
75+ } ) ( ) ;
76+ const route = transport === 'http' ? ( options as any ) . logsRoute : ( ( options as any ) . logsRoute || '/__client-logs' ) ;
77+ // eslint-disable-next-line no-console
78+ console . error ( `[MCP debug] pid=${ process . pid } cwd=${ process . cwd ( ) } transport=${ transport } host=${ host } port=${ port } route=${ route } ` ) ;
79+ // eslint-disable-next-line no-console
80+ console . error ( `[MCP debug] env: ${ JSON . stringify ( envSnapshot ) } ` ) ;
81+ } catch { }
82+ }
5083 // Create store
5184 const bufferMax = Number ( process . env . BROWSER_ECHO_BUFFER_SIZE ?? 1000 ) | 0 ;
5285 const store = new LogStore ( bufferMax > 0 ? bufferMax : 1000 ) ;
@@ -273,19 +306,10 @@ export async function startHttpServer(
273306 throw err ;
274307 }
275308
276- // For Streamable HTTP, we intentionally do not write project JSON here. The per-project
277- // source of truth is written by the stdio ingest server only.
278-
279309 // eslint-disable-next-line no-console
280310 console . log ( `MCP (Streamable HTTP) listening → http://${ opts . host } :${ opts . port } ${ opts . endpoint } ` ) ;
281311 // eslint-disable-next-line no-console
282312 console . log ( `Log ingest endpoint → http://${ opts . host } :${ opts . port } ${ opts . logsRoute } ` ) ;
283- // Expose ingest discovery for other MCP instances in the same process tree
284- try {
285- process . env . BROWSER_ECHO_INGEST_BASE = `http://${ opts . host } :${ opts . port } ` ;
286- process . env . BROWSER_ECHO_LOGS_ROUTE = String ( opts . logsRoute ) ;
287- process . env . BROWSER_ECHO_INGEST_OWNER = '1' ;
288- } catch { }
289313}
290314
291315/** Start a minimal HTTP server exposing ONLY:
@@ -390,18 +414,6 @@ export async function startIngestOnlyServer(
390414
391415 // eslint-disable-next-line no-console
392416 console . error ( `Log ingest endpoint → http://${ opts . host } :${ actualPort } ${ opts . logsRoute } ` ) ;
393- // Write project-local discovery file for frameworks/tools to find ingest
394- try {
395- const discPath = join ( process . cwd ( ) , '.browser-echo-mcp.json' ) ;
396- const payload = { url : `http://${ opts . host } :${ actualPort } ` , route : String ( opts . logsRoute ) , timestamp : Date . now ( ) } ;
397- writeFileSync ( discPath , JSON . stringify ( payload ) ) ;
398- } catch { }
399- // Expose discovery for owner instance
400- try {
401- process . env . BROWSER_ECHO_INGEST_BASE = `http://${ opts . host } :${ actualPort } ` ;
402- process . env . BROWSER_ECHO_LOGS_ROUTE = String ( opts . logsRoute ) ;
403- process . env . BROWSER_ECHO_INGEST_OWNER = '1' ;
404- } catch { }
405417}
406418
407419// Removed project JSON discovery in single-server mode
@@ -424,6 +436,15 @@ function createLogIngestRoutes(store: LogStore, logsRoute: `/${string}`) {
424436 // Log ingest (POST)
425437 router . post ( logsRoute , defineEventHandler ( async ( event ) => {
426438 try {
439+ const __dbg = String ( process . env . BROWSER_ECHO_DEBUG || '' ) . trim ( ) . toLowerCase ( ) ;
440+ const __debug = __dbg !== '' && __dbg !== '0' && __dbg !== 'false' ;
441+ if ( __debug ) {
442+ try {
443+ const hdrs = event . node . req . headers || { } as any ;
444+ // eslint-disable-next-line no-console
445+ console . error ( '[MCP ingest debug] headers:' , JSON . stringify ( hdrs ) ) ;
446+ } catch { }
447+ }
427448 const raw = await readRawBody ( event ) ;
428449 const payload = typeof raw === 'string' ? JSON . parse ( raw ) : ( raw ? JSON . parse ( Buffer . from ( raw as any ) . toString ( 'utf-8' ) ) : undefined ) ;
429450 if ( ! payload || ! Array . isArray ( payload . entries ) ) {
@@ -435,10 +456,20 @@ function createLogIngestRoutes(store: LogStore, logsRoute: `/${string}`) {
435456 const hdrs = event . node . req . headers ;
436457 const projectHeader = ( hdrs [ 'x-browser-echo-project-name' ] || hdrs [ 'x-project-name' ] || hdrs [ 'x-project' ] || '' ) as string | string [ ] | undefined ;
437458 const projectName = Array . isArray ( projectHeader ) ? String ( projectHeader [ 0 ] || '' ) : String ( projectHeader || '' ) ;
459+ // Extract dev id for ACK handshake
460+ const devIdHeader = ( hdrs [ 'x-browser-echo-dev-id' ] || hdrs [ 'x-dev-id' ] || '' ) as string | string [ ] | undefined ;
461+ const devId = Array . isArray ( devIdHeader ) ? String ( devIdHeader [ 0 ] || '' ) : String ( devIdHeader || '' ) ;
462+ if ( __debug ) {
463+ try {
464+ // eslint-disable-next-line no-console
465+ console . error ( `[MCP ingest debug] session=${ sid } project=${ projectName } devId=${ devId } entries=${ payload . entries . length } ` ) ;
466+ } catch { }
467+ }
438468 // Special command: remote clear request
439469 const isClear = payload . entries . length === 1 && String ( payload . entries [ 0 ] ?. text || '' ) === '__BROWSER_ECHO_CLEAR__' ;
440470 if ( isClear ) {
441471 store . clear ( { session : sid ? String ( sid ) . slice ( 0 , 8 ) : undefined , scope : 'hard' , project : projectName || undefined } ) ;
472+ try { event . node . res . setHeader ( 'X-Browser-Echo-Ack' , `project=${ projectName || '' } ;devId=${ devId || '' } ` ) ; } catch { }
442473 setResponseStatus ( event , 204 ) ;
443474 return '' ;
444475 }
@@ -455,6 +486,10 @@ function createLogIngestRoutes(store: LogStore, logsRoute: `/${string}`) {
455486 project : projectName ? projectName : undefined
456487 } ) ;
457488 }
489+ // Mark this instance as the ingest owner so clients can distinguish our server from stray ones
490+ try { event . node . res . setHeader ( 'X-Browser-Echo-Ack' , `project=${ projectName || '' } ;devId=${ devId || '' } ;owner=1` ) ; } catch { }
491+ // Mark this instance as the ingest owner so clients can distinguish our server from stray ones
492+ try { event . node . res . setHeader ( 'X-Browser-Echo-Ack' , `project=${ projectName || '' } ;devId=${ devId || '' } ;owner=1` ) ; } catch { }
458493 setResponseStatus ( event , 204 ) ;
459494 return '' ;
460495 } catch {
0 commit comments