11// Avoid exporting Vite types to prevent cross-version type mismatches in consumers
22import ansis from 'ansis' ;
33import type { BrowserLogLevel , NetworkCaptureOptions } from '@browser-echo/core' ;
4- import { mkdirSync , appendFileSync , existsSync , readFileSync } from 'node:fs' ;
4+ import { mkdirSync , appendFileSync } from 'node:fs' ;
55import { join as joinPath , dirname } from 'node:path' ;
66
77export interface BrowserLogsToTerminalOptions {
@@ -123,25 +123,7 @@ function attachMiddleware(server: any, options: ResolvedOptions) {
123123 return ;
124124 }
125125
126- // 2) Project-local discovery file in CWD (do not default to port 5179)
127- try {
128- const discPath = joinPath ( process . cwd ( ) , '.browser-echo-mcp.json' ) ;
129- if ( existsSync ( discPath ) ) {
130- try {
131- const txt = readFileSync ( discPath , 'utf-8' ) ;
132- const obj = JSON . parse ( txt || '{}' ) as { url ?: string ; route ?: string } ;
133- if ( obj && obj . url ) {
134- const base = String ( obj . url ) . trim ( ) . replace ( / \/ $ / , '' ) . replace ( / \/ m c p $ / i, '' ) ;
135- const route = ( obj as any ) . route || options . mcp . routeLogs || '/__client-logs' ;
136- resolvedBase = base ;
137- resolvedIngest = `${ base } ${ route as `/${string } `} ` ;
138- return ;
139- }
140- } catch { }
141- }
142- } catch { }
143-
144- // 3) No configured or discovered MCP → keep base empty; do not probe, always print locally
126+ // No configured MCP → keep base empty; do not suppress; optional safe auto-detect happens in probe loop
145127 resolvedBase = '' ;
146128 resolvedIngest = '' ;
147129 }
@@ -167,12 +149,55 @@ function attachMiddleware(server: any, options: ResolvedOptions) {
167149 }
168150 }
169151
152+ async function probeIsMcp ( base : string ) : Promise < boolean > {
153+ try {
154+ // Expect GET to return 405 Method Not Allowed for MCP endpoint
155+ const ctrl = new AbortController ( ) ;
156+ const t = setTimeout ( ( ) => ctrl . abort ( ) , 400 ) ;
157+ const res = await fetch ( `${ base } /mcp` , { method : 'GET' , signal : ctrl . signal as any , cache : 'no-store' as any } ) ;
158+ clearTimeout ( t ) ;
159+ if ( res && res . status === 405 ) return true ;
160+ // Fallback OPTIONS 200/204
161+ const ctrl2 = new AbortController ( ) ;
162+ const t2 = setTimeout ( ( ) => ctrl2 . abort ( ) , 400 ) ;
163+ const res2 = await fetch ( `${ base } /mcp` , { method : 'OPTIONS' , signal : ctrl2 . signal as any , cache : 'no-store' as any } ) ;
164+ clearTimeout ( t2 ) ;
165+ return ! ! res2 && ( res2 . status === 200 || res2 . status === 204 ) ;
166+ } catch {
167+ return false ;
168+ }
169+ }
170+
170171 function startHealthProbe ( ) {
171172 // Only starts once per dev server process
172173 let started = false ;
173174 if ( started ) return ;
174175 started = true ;
175176 const interval = setInterval ( async ( ) => {
177+ // If no explicit config and nothing resolved yet, try safe auto-detect in dev (skip during tests)
178+ const hasExplicitConfig = ! ! ( options . mcp . url || process . env . BROWSER_ECHO_MCP_URL ) ;
179+ if ( ! hasExplicitConfig && ! resolvedBase && process . env . NODE_ENV !== 'test' ) {
180+ const candidate = 'http://127.0.0.1:5179' ;
181+ // First check health
182+ let healthy = false ;
183+ try {
184+ const ctrl = new AbortController ( ) ;
185+ const t = setTimeout ( ( ) => ctrl . abort ( ) , 400 ) ;
186+ const res = await fetch ( `${ candidate } /health` , { signal : ctrl . signal as any , cache : 'no-store' as any } ) ;
187+ clearTimeout ( t ) ;
188+ healthy = ! ! res && res . ok ;
189+ } catch { }
190+ if ( healthy ) {
191+ const isMcp = await probeIsMcp ( candidate ) ;
192+ if ( isMcp ) {
193+ resolvedBase = candidate ;
194+ resolvedIngest = `${ candidate } ${ options . mcp . routeLogs } ` ;
195+ isRemoteAvailable = true ; // but we will not suppress unless explicit config is present
196+ announce ( `${ options . tag } forwarding logs to MCP ingest at ${ resolvedIngest } ` ) ;
197+ }
198+ }
199+ }
200+
176201 if ( ! resolvedBase ) return ; // nothing to probe without a known/current MCP
177202 const ok = await probeHealth ( ) ;
178203 if ( ok && ! isRemoteAvailable ) {
@@ -233,8 +258,10 @@ function attachMiddleware(server: any, options: ResolvedOptions) {
233258 } catch { }
234259
235260 const logger = server . config . logger ;
236- // Suppress when user requests it AND a current MCP endpoint is configured AND is considered available
237- const shouldPrint = ! options . mcp . suppressTerminal || ! resolvedBase || ! isRemoteAvailable ;
261+ const hasExplicitConfig = ! ! ( options . mcp . url || process . env . BROWSER_ECHO_MCP_URL ) ;
262+ // Suppress only when explicitly configured AND MCP considered available
263+ const shouldSuppress = hasExplicitConfig && options . mcp . suppressTerminal && isRemoteAvailable ;
264+ const shouldPrint = ! shouldSuppress ;
238265 const sid = ( payload . sessionId ?? 'anon' ) . slice ( 0 , 8 ) ;
239266 for ( const entry of payload . entries ) {
240267 const level = normalizeLevel ( entry . level ) ;
0 commit comments