import { type CallerClient } from '@zhixuan92/multi-model-agent-core'; /** A single resolved DNS answer — the subset of `dns.lookup(host, {all:true})` we need. */ interface ResolvedAddress { address: string; } /** * Dependencies for `runMcpBridge`. Every piece of I/O and DNS behavior is * injected so tests are fully deterministic (no real network, no real DNS, * no real filesystem). */ interface McpBridgeDeps { /** The daemon base URL (already resolved via buildServerUrl + loadConfig). */ daemonUrl: string; /** Environment variables (for MMA_AUTH_TOKEN / MMA_TOKEN_FILE lookup). */ env: Record; /** Home directory, used for the default `~/.mma/auth-token` fallback. */ homeDir: string; /** * Which client this bridge is speaking for, forwarded as `X-MMA-Client` so * the daemon attributes the run to a real client instead of `other`. * * Optional on purpose: an existing registration that predates the flag (and * every hand-written `mma mcp` entry) keeps working and simply attributes as * `other`, exactly as it did before. The header is the ONLY way the daemon * can tell an Agent-Plugins client apart from a bespoke one, so every * generated registration sets it. */ callerClient?: CallerClient; /** Async iterable of raw stdin lines (one JSON-RPC frame per line). */ stdin: AsyncIterable; /** Write a line to stdout. */ stdout: (s: string) => boolean; /** Write a line to stderr. */ stderr: (s: string) => boolean; /** Injectable fetch. */ fetch: typeof fetch; /** * Resolve a hostname to its numeric addresses. Called AT MOST ONCE per * bridge run — only when the configured host is not already a numeric * IP literal. */ resolveHost: (hostname: string) => Promise; /** * Read a token file's raw contents (sync). May throw (e.g. ENOENT) — the * caller treats a throw as "unusable" and falls through to the next * token source. */ readFile: (filePath: string) => string; /** Per-frame upper bound in ms (POST + SSE body read). Defaults to * {@link FRAME_TIMEOUT_MS}; tests override it so the abandon path is coverable * without waiting two minutes. */ frameTimeoutMs?: number; } /** * Run the stdio↔HTTP MCP bridge. Resolves to a process exit code: `0` only * after stdin reaches EOF; nonzero for any of the three fatal startup * failures (bad token, failed loopback validation/resolution, failed health * preflight). Every failure once frames are being processed is reported as * a per-frame JSON-RPC error and does not stop the loop. */ /** * Buffer every line a readline interface emits, from the moment it is created. * * `readline.createInterface()` starts consuming its input immediately, but * {@link runMcpBridge} only begins iterating AFTER its async startup (token * resolution, DNS pinning, health preflight). Lines emitted during that window are * delivered to no one and silently lost — and because a host writes its `initialize` * frame the instant it spawns the bridge, that race is the common case rather than an * edge case. Piping input loses every frame. * * Iterating the interface directly is therefore unsafe here. This wraps it so lines * are queued as they arrive and handed over once iteration starts. */ export declare function bufferedLines(source: { on(event: 'line', listener: (line: string) => void): unknown; on(event: 'close', listener: () => void): unknown; }): AsyncIterable; export declare function runMcpBridge(deps: McpBridgeDeps): Promise; export {}; //# sourceMappingURL=mcp.d.ts.map