Skip to content

Commit f6031a3

Browse files
committed
chore: update MCP configuration with new server port settings, enhance error handling in server setup, and refine discovery file management
1 parent b787ece commit f6031a3

3 files changed

Lines changed: 71 additions & 23 deletions

File tree

.browser-echo-mcp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"url":"http://127.0.0.1:57918","routeLogs":"/__client-logs","timestamp":1755854608470,"pid":49241}
1+
{"url":"http://127.0.0.1:61582","routeLogs":"/__client-logs","timestamp":1755861948201,"pid":45951,"projectRoot":"/Users/kregenrek/projects/vite-browser-logs"}

.cursor/mcp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"mcpServers": {
33
"browser-echo": {
44
"command": "npx",
5-
"args": ["https://pkg.pr.new/instructa/browser-echo/@browser-echo/mcp@7123834"]
5+
"args": ["https://pkg.pr.new/instructa/browser-echo/@browser-echo/mcp@b787ece"]
66
}
77
}
88
}

packages/mcp/src/server.ts

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ export async function startServer(
6161

6262
// Always bring up an ingest-only HTTP endpoint so clients/frameworks can POST logs
6363
const host = options.host || '127.0.0.1';
64-
// Use env override if provided, otherwise let OS pick an ephemeral port
64+
// Prefer stable 5179 for discovery; allow override via options/env; fallback handled in startIngestOnlyServer
6565
const envIngest = process.env.BROWSER_ECHO_INGEST_PORT;
66-
const requested = Number(envIngest || 0) | 0;
67-
const port = requested > 0 ? requested : 0;
66+
const preferred = (options.port ?? (envIngest ? Number(envIngest) | 0 : 5179)) | 0;
67+
const port = preferred > 0 ? preferred : 5179;
6868
const logsRoute = options.logsRoute || '/__client-logs';
6969
await startIngestOnlyServer(store, { host, port, logsRoute });
7070

@@ -251,34 +251,80 @@ export async function startIngestOnlyServer(
251251

252252
app.use(router);
253253

254-
const nodeServer = createNodeServer(toNodeListener(app));
254+
function configureNodeServer(srv: any) {
255+
try {
256+
srv.requestTimeout = 0;
257+
srv.headersTimeout = 0;
258+
typeof srv.setTimeout === 'function' && srv.setTimeout(0);
259+
srv.on('connection', (socket: any) => {
260+
try {
261+
socket.setKeepAlive?.(true, 60_000);
262+
socket.setNoDelay?.(true);
263+
} catch {}
264+
});
265+
} catch {}
266+
}
255267

256-
try {
257-
nodeServer.requestTimeout = 0;
258-
nodeServer.headersTimeout = 0;
259-
typeof nodeServer.setTimeout === 'function' && nodeServer.setTimeout(0);
260-
nodeServer.on('connection', (socket: any) => {
268+
function listenWithResult(srv: any, host: string, port: number): Promise<number> {
269+
return new Promise<number>((resolve, reject) => {
270+
const onListening = () => {
271+
try {
272+
const addr = srv.address();
273+
const actual = addr && typeof addr === 'object' && 'port' in addr ? (addr.port as number) : port;
274+
cleanup();
275+
resolve(actual);
276+
} catch (e) {
277+
cleanup();
278+
reject(e);
279+
}
280+
};
281+
const onError = (err: any) => { cleanup(); reject(err); };
282+
const cleanup = () => {
283+
try { srv.off?.('listening', onListening); } catch {}
284+
try { srv.off?.('error', onError); } catch {}
285+
try { srv.removeListener?.('listening', onListening); } catch {}
286+
try { srv.removeListener?.('error', onError); } catch {}
287+
};
261288
try {
262-
socket.setKeepAlive?.(true, 60_000);
263-
socket.setNoDelay?.(true);
264-
} catch {}
289+
srv.once('listening', onListening);
290+
srv.once('error', onError);
291+
srv.listen(port, host);
292+
} catch (e) {
293+
cleanup();
294+
reject(e);
295+
}
265296
});
266-
} catch {}
297+
}
267298

268-
await new Promise<void>((resolve) => nodeServer.listen(opts.port, opts.host, () => resolve()));
299+
// Prefer requested port (usually 5179); fall back to ephemeral if it's taken
300+
let nodeServer = createNodeServer(toNodeListener(app));
301+
configureNodeServer(nodeServer);
269302

270-
// Determine the actual port assigned (when listening on port 0)
271-
const addr = nodeServer.address() as any;
272-
const actualPort = (addr && typeof addr === 'object' && 'port' in addr) ? (addr.port as number) : opts.port;
303+
let actualPort: number;
304+
try {
305+
actualPort = await listenWithResult(nodeServer, opts.host, opts.port);
306+
} catch (err: any) {
307+
const isAddrInUse = err && (err.code === 'EADDRINUSE' || String(err.message || '').includes('EADDRINUSE'));
308+
if (isAddrInUse && opts.port !== 0) {
309+
try { nodeServer.close?.(); } catch {}
310+
nodeServer = createNodeServer(toNodeListener(app));
311+
configureNodeServer(nodeServer);
312+
actualPort = await listenWithResult(nodeServer, opts.host, 0);
313+
} else {
314+
throw err;
315+
}
316+
}
273317

274-
// Advertise discovery for tooling that wants to auto-detect the ingest endpoint
275-
await advertiseDiscovery(opts.host, actualPort, opts.logsRoute, { projectRoot: process.cwd(), scope: 'stdio' });
318+
// Only the primary (5179) should advertise to OS tmp to avoid discovery flapping
319+
const requestedPort = opts.port;
320+
const isAggregator = requestedPort !== 0 && actualPort === requestedPort;
321+
await advertiseDiscovery(opts.host, actualPort, opts.logsRoute, { projectRoot: process.cwd(), scope: 'stdio', aggregator: isAggregator });
276322

277323
// eslint-disable-next-line no-console
278324
console.error(`Log ingest endpoint → http://${opts.host}:${actualPort}${opts.logsRoute}`);
279325
}
280326

281-
async function advertiseDiscovery(host: string, port: number, logsRoute: `/${string}`, meta?: { projectRoot?: string; token?: string; scope?: 'http' | 'stdio' }) {
327+
async function advertiseDiscovery(host: string, port: number, logsRoute: `/${string}`, meta?: { projectRoot?: string; token?: string; scope?: 'http' | 'stdio'; aggregator?: boolean }) {
282328
try {
283329
const { writeFileSync } = await import('node:fs');
284330
const { join } = await import('node:path');
@@ -296,7 +342,9 @@ async function advertiseDiscovery(host: string, port: number, logsRoute: `/${str
296342

297343
const files = meta?.scope === 'http'
298344
? [ join(tmpdir(), 'browser-echo-mcp.json') ]
299-
: [ join(process.cwd(), '.browser-echo-mcp.json'), join(tmpdir(), 'browser-echo-mcp.json') ];
345+
: meta?.aggregator
346+
? [ join(process.cwd(), '.browser-echo-mcp.json'), join(tmpdir(), 'browser-echo-mcp.json') ]
347+
: [ join(process.cwd(), '.browser-echo-mcp.json') ];
300348

301349
for (const f of files) {
302350
try { writeFileSync(f, payload); } catch {}

0 commit comments

Comments
 (0)