import { Server, IncomingMessage, ServerResponse } from 'node:http'; import { Socket } from 'node:net'; /** * Early port bind for the neuromcp daemon — boot-race fix. * * Problem this solves: on cold boot the daemon spent ~100s loading its * module graph (better-sqlite3, MCP SDK, embeddings probe, …) BEFORE * binding its port. Every MCP client that connected in that window got * ECONNREFUSED — Claude Code connects over plain HTTP with no wait/retry * wrapper and surfaced "Could not attach to MCP server neuromcp". * * `startEarlyBind` owns the TCP port within milliseconds of process start: * - `GET /health` answers `503 {"status":"starting"}` so pollers * (mcp-remote-wait.sh uses `curl -sf`) keep waiting instead of failing. * - Every other request is buffered — bounded queue, per-request hold * timeout — and replayed into the real handler once the heavy daemon * core has loaded and called `takeover()`. * * CONSTRAINT: this module must stay dependency-free (node builtins only). * Importing anything from the daemon's module graph here would reintroduce * the exact slow-load window it exists to hide. */ type BootstrapRequestHandler = (req: IncomingMessage, res: ServerResponse) => void | Promise; /** Contract between the bootstrap and the daemon core it hands the server to. */ interface DaemonBootstrapHandoff { /** The already-listening HTTP server whose port was bound at process start. */ readonly server: Server; /** * Live sockets tracked since the first accept. The daemon core reuses this * set for graceful shutdown so sockets accepted BEFORE takeover are not * invisible to it. */ readonly sockets: Set; /** * Swap the bootstrap's queueing dispatcher for the real request handler * and replay all buffered requests into it, FIFO. */ readonly takeover: (handler: BootstrapRequestHandler) => void; } interface DaemonCoreModule { runDaemon(handoff: DaemonBootstrapHandoff): Promise; } interface EarlyBindOptions { readonly port: number; readonly host: string; /** Typically `() => import('./daemon-core.js')`; injectable for tests. */ readonly importCore: () => Promise; /** Max requests held while the core loads; beyond this → immediate 503. */ readonly queueLimit?: number; /** Max time a request may wait for the core before it gets a 503. */ readonly holdTimeoutMs?: number; } interface EarlyBindHandle { readonly server: Server; /** * Resolves once the core has loaded and taken over. Rejects — after the * queue is flushed with 503 and the server is closed — when the core * fails to load or start. */ readonly core: Promise; /** Flush queued requests with 503, close the server, destroy sockets. */ readonly shutdown: () => Promise; } /** * Env parsing + bind-host validation live here (not in the daemon core) * because the bootstrap must resolve port/host BEFORE the heavy module * graph loads. The core reuses these so both entries stay in lockstep. */ declare function readPortFromEnv(name: string, fallback: number): number; declare function validateHost(host: string): void; declare function startEarlyBind(options: EarlyBindOptions): Promise; export { type BootstrapRequestHandler, type DaemonBootstrapHandoff, type DaemonCoreModule, type EarlyBindHandle, type EarlyBindOptions, readPortFromEnv, startEarlyBind, validateHost };