/** * `scai mcp serve --transport http` — Streamable HTTP transport. * * Why a second transport file? `server.ts` owns the stdio path, where * one long-lived `McpServer` is bound to the process for its whole * lifetime. HTTP is request-scoped: the MCP Streamable HTTP spec wants * a fresh server + transport per POST so concurrent clients can't * collide on JSON-RPC request ids. * * We run **stateless** (no `Mcp-Session-Id`). scai's dispatch rwlock * already serializes writes process-wide, so there is no per-session * state worth keeping alive between requests, and progress * notifications still ride the per-request response stream. * * Security: binds to loopback by default and validates the `Host` * header against the bound address (DNS-rebinding defense — a * malicious web page cannot point its own hostname at this loopback * port). CORS is permissive on `Origin` so a browser-hosted MCP client * can connect; the real boundary is the loopback bind + Host check + * the per-call `allowWrite` gate every write tool already enforces. */ import { type McpServerOptions } from "./server.js"; export interface HttpTransportOptions extends McpServerOptions { /** Bind address. Callers default this to loopback. */ host: string; /** Listener port. `0` lets the OS assign one (used by tests). */ port: number; } export interface HttpTransportHandle { /** Full MCP endpoint URL, e.g. `http://127.0.0.1:3399/mcp`. */ url: string; /** Actually-bound port — resolves `port: 0` to the OS-assigned port. */ port: number; /** Stop the listener and release the port. */ close: () => Promise; } /** * Start the Streamable HTTP listener. Resolves once the socket is bound * (or rejects on a bind error, e.g. `EADDRINUSE`). The returned handle * keeps the process alive via the open server; `close()` releases it. */ export declare const startHttpTransport: (options: HttpTransportOptions) => Promise;