@orkestrel/mcp 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["#response","#lifecycle","#interval","#signal","#bridged","#timer","#pulling","#abort","#release","#emitter","#url","#headers","#fetch","#timeout","#pending","#session","#closed","#exchange","#buildHeaders","#deliver","#protocol","#capture","#id","#events","#streams","#capacity","#ttl","#append","#evict","#counter","#emitter","#socket","#frame","#receive","#ending","#onClose","#failure","#started","#closed","#release","#emitter","#url","#headers","#frame","#receive","#ending","#onClose","#failure","#socket","#closed","#connect","#httpURL","#request","#release","#bind","#emitter","#command","#args","#env","#process","#closing","#closed","#onExit","#pump","#teardown","#report","#emitter","#input","#output","#data","#receive","#ending","#onClose","#failure","#started","#closed","#flowing","#release","#buffer"],"sources":["../../../src/server/constants.ts","../../../src/server/helpers.ts","../../../src/server/inferers.ts","../../../src/server/transports/HTTPDisconnect.ts","../../../src/server/handlers.ts","../../../src/server/transports/HTTPClientTransport.ts","../../../src/server/MCPSession.ts","../../../src/server/transports/WebSocketServerTransport.ts","../../../src/server/transports/WebSocketClientTransport.ts","../../../src/server/transports/StdioClientTransport.ts","../../../src/server/transports/StdioServerTransport.ts","../../../src/server/factories.ts","../../../src/server/middlewares.ts"],"sourcesContent":["// The MCP HTTP-transport constants — the wire-level header\n// names, the default mount path, and the folded event-log bounds. The HEADER names are\n// the Streamable-HTTP transport's session /\n// protocol-version headers. `createMCPSession` owns the optional session id, while\n// `createMCPRoutes` validates a present protocol version on every POST. The\n// transport-agnostic dispatch core deliberately does NOT carry these — header names\n// belong to the HTTP transport, here.\n\n/**\n * The Streamable-HTTP transport header that carries the MCP session id. When a {@link\n * import('./middlewares.js').createMCPSession} middleware is mounted, it SETS this header on\n * the `initialize` response (the minted id) and READS it on every subsequent request\n * (validating the session); the stateless `createMCPRoutes` default neither sets nor reads it.\n */\nexport const MCP_SESSION_HEADER = 'mcp-session-id'\n\n/**\n * The Streamable-HTTP transport header carrying the negotiated MCP protocol version\n * on every post-initialize client request.\n *\n * @remarks\n * Required by MCP 2025-06-18 after initialization. Both HTTP client transports\n * capture the initialize result's `protocolVersion` and send it on subsequent\n * requests; `createMCPRoutes` rejects a present unsupported value before dispatch.\n */\nexport const MCP_PROTOCOL_VERSION_HEADER = 'mcp-protocol-version'\n\n/** The modern Streamable-HTTP request header carrying the JSON-RPC method name. */\nexport const MCP_METHOD_HEADER = 'mcp-method'\n\n/** The modern Streamable-HTTP request header carrying a named method's target. */\nexport const MCP_NAME_HEADER = 'mcp-name'\n\n/** The reverse-proxy response header controlling buffering of an SSE response. */\nexport const SSE_BUFFERING_HEADER = 'x-accel-buffering'\n\n/** The `X-Accel-Buffering` value that disables reverse-proxy buffering. */\nexport const SSE_BUFFERING_DISABLED = 'no'\n\n/** The default request path `createMCPRoutes` mounts the transport's `POST` route at. */\nexport const DEFAULT_MCP_PATH = '/mcp'\n\n/**\n * The default interval in milliseconds between SSE keepalive comments on held-open MCP\n * responses.\n *\n * @remarks\n * Fifteen seconds is infrequent enough to avoid chatty idle connections while bounding dead\n * client detection and staying comfortably inside common intermediary idle windows.\n */\nexport const DEFAULT_MCP_KEEPALIVE_INTERVAL = 15_000\n\n/** The comment text written by the held-open MCP response keepalive. */\nexport const SSE_KEEPALIVE_COMMENT = 'keepalive'\n\n/**\n * The WebSocket subprotocol the MCP-over-WebSocket transports negotiate — sent by the\n * client in `Sec-WebSocket-Protocol`, echoed by the server in its `101` handshake.\n *\n * @remarks\n * `createWebSocketServer` echoes it in the upgrade response and `createWebSocketClientTransport`\n * requests it, so an MCP WebSocket endpoint is distinguishable from any other WebSocket on the\n * same path. The default WebSocket upgrade path is {@link DEFAULT_MCP_PATH} (the same `'/mcp'`\n * the HTTP transport mounts at) — the upgrade is selected by the `Upgrade: websocket` header,\n * not a separate path.\n */\nexport const MCP_WEBSOCKET_SUBPROTOCOL = 'mcp'\n\n/**\n * The default capacity of a session's FOLDED resumable event log (the per-{@link\n * import('./MCPSession.js').MCPSession} replay log) — the maximum number of pushed\n * server→client messages retained for replay before the OLDEST is evicted.\n *\n * @remarks\n * Bounds the replay log's memory: only the most-recent {@link DEFAULT_MCP_SESSION_CAPACITY}\n * pushes are retained, so a client reconnecting with a `Last-Event-ID` older than that window\n * replays nothing (its cursor fell off the back). Override per `createMCPSession`'s `capacity`\n * for a deeper / shallower window.\n */\nexport const DEFAULT_MCP_SESSION_CAPACITY = 1024\n\n/**\n * The default per-event idle lifetime (ms) of a session's folded resumable event log — an\n * entry older than this is lazily evicted on the next access (no background timer), bounding\n * how far back a reconnecting client may replay.\n *\n * @remarks\n * Five minutes — a generous reconnection window for a dropped SSE stream without retaining\n * stale pushes indefinitely. The session's own idle TTL is the `createMCPSession` `ttl` knob;\n * this bounds the replay log paired with it.\n */\nexport const DEFAULT_MCP_SESSION_TTL = 300_000\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n\tMCPTransportInterface,\n} from '@src/core'\nimport type { MCPStreamControllerInterface } from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { SSEParserInterface } from '@orkestrel/sse'\nimport type { StreamInterface } from '@orkestrel/server'\nimport type { IncomingMessage } from 'node:http'\nimport type { LineExtraction, MCPOriginOptions } from './types.js'\nimport { createSSEParser } from '@orkestrel/sse'\nimport {\n\tisJSONRPCInvocation,\n\tJSONRPC_INVALID_REQUEST,\n\tbuildJSONRPCError,\n\tparseJSONRPCMessage,\n} from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { MCP_SESSION_HEADER } from './constants.js'\n\n/**\n * Creates a readable stream from its pull and cancellation behaviours.\n *\n * @param pull - The behaviour that supplies the stream's next chunk\n * @param cancel - The behaviour that releases the stream after consumer cancellation\n * @returns A readable stream backed by the supplied behaviours\n */\nexport function createReadableStream<T>(\n\tpull: (controller: ReadableStreamDefaultController<T>) => void | PromiseLike<void>,\n\tcancel: (reason?: unknown) => void | PromiseLike<void>,\n): ReadableStream<T> {\n\treturn new ReadableStream<T>({ pull, cancel })\n}\n\n/**\n * Pumps a controlled held-open exchange onto an open SSE stream — one `data:` event per\n * notification in order, then the terminating response — and END the exchange however the\n * pump leaves.\n *\n * @remarks\n * The Streamable-HTTP twin of {@link import('@orkestrel/mcp').sendStream}, and it owns exactly what\n * that owns. The `finally` releases the exchange on EVERY exit — the normal terminal, a\n * producer that threw, a `write` that threw, and an abort alike — because nothing else will:\n * a request whose client vanished cancels nothing by itself, so an exchange this pump walks\n * away from keeps its producer, its request lifetime, and its live subscription slot forever.\n * The exchange is released BEFORE the body ends, so the slot is already back when the response\n * completes.\n *\n * Total — never throws and never rejects. A held-open SSE response has already sent its\n * headers and part of its body, so there is no failure the transport could still convert into\n * a different answer; the honest end of a broken stream is a closed one, and the fault itself\n * is already legible on `server.emitter`'s `error` event, which is where a contained fault\n * belongs.\n *\n * @param stream - The controlled held-open answer to write out and then end\n * @param sse - The open SSE stream to write each serialized message onto\n * @returns Resolves once the exchange has ended and the SSE body has been closed\n *\n * @example\n * ```ts\n * const answer = await mcp.dispatch(invocation, { signal: disconnect.signal })\n * if (answer !== undefined && Symbol.asyncIterator in answer) {\n * \tconst sse = openStream()\n * \tqueueMicrotask(() => void sendEventStream(answer, sse))\n * }\n * ```\n */\nexport async function sendEventStream(\n\tstream: MCPStreamControllerInterface,\n\tsse: StreamInterface,\n): Promise<void> {\n\ttry {\n\t\t// The inner `finally` is what makes the totality claim above true of the RELEASE too:\n\t\t// a dispose that threw would otherwise escape past the outer catch that swallows the\n\t\t// pump's own faults.\n\t\ttry {\n\t\t\tlet next = await stream.next()\n\t\t\twhile (next.done !== true) {\n\t\t\t\tsse.write({ data: JSON.stringify(next.value) })\n\t\t\t\tnext = await stream.next()\n\t\t\t}\n\t\t\tsse.write({ data: JSON.stringify(next.value) })\n\t\t} finally {\n\t\t\tawait stream[Symbol.asyncDispose]()\n\t\t}\n\t} catch {\n\t\t// A producer failure, a write fault, or an abort ends this response — see @remarks.\n\t} finally {\n\t\tsse.end()\n\t}\n}\n\n// The MCP server-transport helpers — module-scope names, so they carry no entity context.\n// The server-side reader `acceptsEventStream` reads the request's `Accept` header to\n// decide whether a Streamable-HTTP SSE response is allowed; `readSessionHeader` reads the\n// request's `mcp-session-id` header (the stateful transport's session validation);\n// `readLastEventId` reads the request's `Last-Event-ID` header (the resumable GET-SSE\n// replay cursor); the CLIENT-side reader `readEventStream` decodes a `fetch` Response's SSE\n// body back into JSON-RPC messages (the egress mirror, reusing `@orkestrel/sse`'s\n// `SSEParser`); `upgradeRequestPath` reads a raw `node:http` upgrade request's path (the\n// WebSocket transport's upgrade-path match). All are total and narrow at the boundary,\n// never `as` — a missing / non-string Accept reads as \"no\", a missing session /\n// last-event header reads as `undefined`, a non-message SSE `data:` event is dropped, an\n// absent `url` reads as `'/'`.\n\n/**\n * Whether the request's `Accept` header opts into a Server-Sent-Events response.\n *\n * @remarks\n * Reads the fetch-standard `Request.headers.get('accept')` and returns `true` when it\n * contains `text/event-stream` (case-insensitive). The MCP `POST` handler uses it\n * (together with the `streaming` option) to pick the Streamable-HTTP SSE response\n * framing over a plain JSON body; the JSON-RPC envelope is identical either way. Total\n * — an absent / unmatched header returns `false`.\n *\n * @param request - The fetch-standard `Request`\n * @returns `true` when the client `Accept`s `text/event-stream`, else `false`\n */\nexport function acceptsEventStream(request: Request): boolean {\n\tconst accept = request.headers.get('accept')\n\tif (accept === null) return false\n\treturn accept.toLowerCase().includes('text/event-stream')\n}\n\n/**\n * Whether an HTTP request satisfies the endpoint's origin gate.\n *\n * @remarks\n * Validation is enabled by default. A request without `Origin` is allowed. A canonical origin\n * whose host is the `localhost` or `[::1]` literal, or belongs to the `127.0.0.0/8` literal\n * range, is allowed without configuration; every other present origin must occur exactly in\n * the caller-supplied list. Invalid and opaque (`null`) origins are denied. `enabled: false`\n * delegates validation to an upstream layer and allows the request through this gate.\n *\n * @param request - The fetch-standard request to validate\n * @param options - Shared origin validation and delegation options\n * @returns `true` when the request may reach MCP dispatch\n */\nexport function allowsOrigin(request: Request, options?: MCPOriginOptions): boolean {\n\tif (options?.enabled === false) return true\n\tconst origin = request.headers.get('origin')\n\tif (origin === null) return true\n\tlet parsed: URL\n\ttry {\n\t\tparsed = new URL(origin)\n\t} catch {\n\t\treturn false\n\t}\n\tif (parsed.origin !== origin) return false\n\tif (\n\t\tparsed.hostname === 'localhost' ||\n\t\tparsed.hostname === '[::1]' ||\n\t\t/^127(?:\\.\\d{1,3}){3}$/.test(parsed.hostname)\n\t) {\n\t\treturn true\n\t}\n\treturn options?.origins?.includes(parsed.origin) ?? false\n}\n\n/**\n * Reads the request's `mcp-session-id` header — the session id a stateful transport\n * validates, or `undefined` when absent.\n *\n * @remarks\n * Reads `request.headers.get(MCP_SESSION_HEADER)` — a fetch-standard `Headers` lookup\n * (single-valued by construction, never an array) — so a missing header reads as\n * `undefined` (no session). {@link import('./middlewares.js').createMCPSession} uses it on\n * every `POST` / `GET` / `DELETE` to look the session up in its closure store; an\n * `undefined` id is treated exactly like an unknown one (a `404`). Total — never throws.\n *\n * @param request - The fetch-standard `Request`\n * @returns The session id, or `undefined` when the header is absent\n */\nexport function readSessionHeader(request: Request): string | undefined {\n\tconst id = request.headers.get(MCP_SESSION_HEADER)\n\treturn id === null ? undefined : id\n}\n\n/**\n * Reads the request's `Last-Event-ID` header — the SSE resume cursor a client sends when it\n * reconnects to the resumable `GET {path}` stream, or `undefined` when absent.\n *\n * @remarks\n * Reads `request.headers.get('last-event-id')` — a fetch-standard `Headers` lookup — so a\n * missing header reads as `undefined` (no resume, the stream starts fresh). The resumable\n * `GET` handler in {@link import('./middlewares.js').createMCPSession} passes a present value\n * to the session's {@link import('./types.js').MCPSessionInterface.replay} to re-deliver the\n * missed events before attaching the stream for live pushes. Total — never throws.\n *\n * @param request - The fetch-standard `Request`\n * @returns The last-event-id, or `undefined` when the header is absent\n */\nexport function readLastEventId(request: Request): string | undefined {\n\tconst id = request.headers.get('last-event-id')\n\treturn id === null ? undefined : id\n}\n\n/**\n * Builds the stateful transport's \"unknown session\" rejection — an HTTP `404` carrying a\n * JSON-RPC error body.\n *\n * @remarks\n * Returns `Response.json(buildJSONRPCError(undefined, JSONRPC_INVALID_REQUEST, 'Session not\n * found'), { status: 404 })`, mirroring `createMCPRoutes`'s `400` transport-failure shape (a\n * JSON-RPC error BODY with NO id) but at the session-not-found status. Shared by\n * every {@link import('./middlewares.js').createMCPSession} validation site — the\n * non-`initialize` `POST` path, the resumable `GET {path}` open, and the `DELETE {path}`\n * session-end (each a missing / unknown / TTL-evicted id) — so the single `404` envelope\n * is defined once. Total — never throws.\n *\n * @returns The `404` JSON-RPC error `Response`\n */\nexport function rejectUnknownSession(): Response {\n\treturn Response.json(buildJSONRPCError(undefined, JSONRPC_INVALID_REQUEST, 'Session not found'), {\n\t\tstatus: 404,\n\t})\n}\n\n/**\n * Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it\n * carried — the CLIENT-side inverse of the server's Streamable-HTTP SSE response.\n *\n * @remarks\n * Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({\n * stream: true })` (handling a multi-byte char split across reads) and `@orkestrel/sse`'s\n * {@link SSEParserInterface} (handling a partial line / in-progress event split across\n * reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage} with\n * `parseJSONRPCMessage` (so a non-message / non-JSON `data:` event is DROPPED, never\n * thrown — total). It reuses the SAME `SSEParser` the server's `openStream` seam\n * serializes against, so the wire round-trips. A `null` body (no stream) yields no\n * messages; the {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}\n * reads a request/response SSE reply (the server sends one `data:` event then ends), so\n * this drains to completion.\n *\n * @param response - The SSE `fetch` Response to decode (its `body` is read to completion)\n * @returns Every {@link JSONRPCMessage} the stream carried, in order\n */\nexport async function readEventStream(response: Response): Promise<readonly JSONRPCMessage[]> {\n\tconst body = response.body\n\tif (body === null) return []\n\tconst reader = body.getReader()\n\tconst decoder = new TextDecoder()\n\tconst parser: SSEParserInterface = createSSEParser()\n\tconst messages: JSONRPCMessage[] = []\n\ttry {\n\t\tfor (;;) {\n\t\t\tconst { done, value } = await reader.read()\n\t\t\tif (done) break\n\t\t\tfor (const event of parser.parse(decoder.decode(value, { stream: true }))) {\n\t\t\t\t// JSON-parse the event's `data` (the JSON-RPC envelope the server wrote), then\n\t\t\t\t// narrow it — a malformed / non-message payload is dropped, never thrown.\n\t\t\t\tconst message = decodeEvent(event.data)\n\t\t\t\tif (message !== undefined) messages.push(message)\n\t\t\t}\n\t\t}\n\t} finally {\n\t\treader.releaseLock()\n\t}\n\treturn messages\n}\n\n/**\n * Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`\n * when it is not one — the per-event step {@link readEventStream} folds over.\n *\n * @remarks\n * `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the event's\n * `data`) inside a try/catch and narrows the parsed value with `parseJSONRPCMessage`.\n * Total: malformed JSON or a non-message value yields `undefined`, never throws.\n *\n * @param data - One SSE event's `data` payload\n * @returns The decoded {@link JSONRPCMessage}, or `undefined`\n */\nexport function decodeEvent(data: string): JSONRPCMessage | undefined {\n\ttry {\n\t\treturn parseJSONRPCMessage(JSON.parse(data))\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/**\n * Reads the path (without the query string) of a raw `node:http` protocol-upgrade request —\n * the `createWebSocketServer` upgrade-path match.\n *\n * @remarks\n * A `node:http` {@link import('node:http').IncomingMessage}'s `url` is the request TARGET\n * (`'/mcp?x=1'`), narrowed with `isString` (never `as`) and defaulting to `'/'` for an\n * absent target; it is parsed against a placeholder base (only the pathname matters for the upgrade\n * decision) and the `pathname` returned. The upgrade handler compares this against its\n * configured `path` to decide whether to claim the socket. Total — never throws on an\n * adversarial / absent target.\n *\n * @param request - The raw upgrade {@link import('node:http').IncomingMessage}\n * @returns The request's path (the `pathname`, no query), or `'/'` when the target is absent\n */\nexport function upgradeRequestPath(request: IncomingMessage): string {\n\tconst target = isString(request.url) ? request.url : '/'\n\treturn new URL(target, 'http://localhost').pathname\n}\n\n/**\n * Folds one more chunk of raw stdio bytes into a newline-framed buffer — the shared\n * line-framing step both stdio transports (client and server) read their inbound\n * newline-delimited JSON-RPC messages through.\n *\n * @remarks\n * Concatenates `buffer` (the carried-forward partial line from the previous call)\n * with `chunk`, splits on `'\\n'`, and returns every COMPLETE line (a `'\\r'` trailing\n * a line, from a CRLF-framed peer, is trimmed) plus the final, possibly-empty\n * fragment as the new `remainder` — the caller threads it back in as the next call's\n * `buffer`. A chunk containing no `'\\n'` yields no lines and the whole (buffer +\n * chunk) as `remainder`. Pure — no I/O, no instance state.\n *\n * @param buffer - The partial line carried forward from the previous chunk (`''` initially)\n * @param chunk - The newly-read raw bytes (already decoded to a string)\n * @returns The complete `lines` extracted (in order) and the trailing `remainder`\n */\nexport function extractLines(buffer: string, chunk: string): LineExtraction {\n\tconst combined = buffer + chunk\n\tconst parts = combined.split('\\n')\n\tconst remainder = parts[parts.length - 1] ?? ''\n\tconst lines = parts.slice(0, -1).map((line) => (line.endsWith('\\r') ? line.slice(0, -1) : line))\n\treturn { lines, remainder }\n}\n\n/**\n * Decodes and delivers each complete newline-framed line onto a {@link\n * MCPClientTransportEventMap} emitter — the shared per-chunk dispatch step both stdio\n * transports run their framed lines through: the server transport frames with {@link\n * extractLines}, the client transport takes its lines from the process supervisor.\n *\n * @remarks\n * A blank line is skipped (a stray trailing newline). Every other line is decoded\n * with {@link decodeEvent} (`JSON.parse` + `parseJSONRPCMessage`, guarded); a\n * well-formed {@link JSONRPCMessage} emits `message`, a malformed / non-message line\n * emits `error` (total, never throws). Pure w.r.t. its own state — the emit is\n * the caller-owned side effect.\n *\n * @param emitter - The transport's {@link EmitterInterface} to emit `message` / `error` onto\n * @param lines - The complete lines to decode and deliver\n */\nexport function dispatchLines(\n\temitter: EmitterInterface<MCPClientTransportEventMap>,\n\tlines: readonly string[],\n): void {\n\tfor (const line of lines) {\n\t\tif (line.length === 0) continue\n\t\tconst message = decodeEvent(line)\n\t\tif (message === undefined) {\n\t\t\temitter.emit('error', new Error('non-JSON-RPC stdio line'))\n\t\t\tcontinue\n\t\t}\n\t\temitter.emit('message', message)\n\t}\n}\n\n/**\n * Bridges a message-channel {@link MCPClientTransportInterface} (the shape the stdio and\n * WebSocket SERVER transports already implement) into the environment-agnostic\n * {@link import('@orkestrel/mcp').MCPTransportInterface} port — the adapter\n * {@link import('./factories.js').createStdioServer} and {@link\n * import('./factories.js').createWebSocketServer} pipe through `bindServer`, so the\n * request/reply/error pump those factories used to hand-roll identically now\n * lives ONCE in the core binder.\n *\n * @remarks\n * `send` decodes the already-serialized reply string back to a {@link JSONRPCMessage}\n * and writes it through `transport.send` (the same `JSON.stringify` the underlying\n * transport already performs, so the wire bytes are unchanged). `listen` filters\n * `transport`'s `message` event to INVOCATIONS ONLY — requests and notifications, never a\n * stray response, exactly as the prior hand-rolled pumps did — and re-serializes each one\n * back to a string for `bindServer`. `closed` bridges `transport`'s `close` event. `close`\n * closes the underlying `transport`.\n *\n * @remarks A message crossing this bridge is decoded and re-encoded TWICE, and that is\n * ACCEPTED rather than accidental. Inbound: the carrier already parsed the frame into a\n * {@link JSONRPCMessage}, and `listen` re-serializes it so `bindServer` can decode it again\n * under the server's own `limit`. Outbound: `bindServer` serialized the reply, `send` parses\n * it back, and the carrier stringifies it once more. The cost is two extra `JSON.parse` /\n * `JSON.stringify` round trips per message, paid to keep ONE pump in the core binder instead\n * of a hand-rolled one per carrier. It is BOUNDED rather than unbounded because the binder\n * decodes within `server.limit.message`, so an oversized frame is refused before the second\n * decode rather than after it. Removing the cost means giving `MCPTransportInterface` a\n * message-shaped face beside its string one, which every transport would then carry.\n *\n * @remarks Per {@link import('@orkestrel/mcp').MCPTransportInterface}, `listen`/`closed`\n * each hold THE SINGLE current handler (a second call REPLACES the first, never adds).\n * Because the underlying `transport.emitter` is ADD-based (`on` subscribes, never\n * replaces), this bridge installs ONE stable emitter listener per event on first use\n * and re-routes it to whichever handler is active (`undefined` while\n * none is), so rebinding never double-dispatches.\n *\n * @remarks A response whose `result` serializes away (for example, `undefined`) is dropped by\n * the message validators on the wire's decode side — an asymmetry the stdio/WS carrier\n * shares with the streamable-HTTP face, because both round-trip through `JSON.stringify`\n * / `JSON.parse` before re-validation.\n *\n * @param transport - The message-channel transport to bridge (stdio or WebSocket)\n * @returns An {@link import('@orkestrel/mcp').MCPTransportInterface} `bindServer` can drive\n *\n * @example\n * ```ts\n * import { bindServer } from '@orkestrel/mcp'\n *\n * const transport = new StdioServerTransport(process.stdin, process.stdout)\n * bindServer(mcp, bridgeMessageTransport(transport))\n * ```\n */\nexport function bridgeMessageTransport(\n\ttransport: MCPClientTransportInterface,\n): MCPTransportInterface {\n\tlet onMessage: ((message: string) => void) | undefined\n\tlet onClosed: (() => void) | undefined\n\ttransport.emitter.on('message', (message) => {\n\t\tif (!isJSONRPCInvocation(message)) return\n\t\tonMessage?.(JSON.stringify(message))\n\t})\n\ttransport.emitter.on('close', () => {\n\t\tonClosed?.()\n\t})\n\treturn {\n\t\tasync send(message) {\n\t\t\tconst decoded = decodeEvent(message)\n\t\t\tif (decoded === undefined) return\n\t\t\tawait transport.send(decoded)\n\t\t},\n\t\tlisten(handler) {\n\t\t\tonMessage = handler\n\t\t},\n\t\tclosed(handler) {\n\t\t\tonClosed = handler\n\t\t},\n\t\tasync close() {\n\t\t\tawait transport.close()\n\t\t},\n\t}\n}\n","import type { JSONRPCInvocation, JSONRPCResponse, MCPEra, MCPVersion } from '@src/core'\nimport type { MCPHeaderIssue } from './types.js'\nimport {\n\tJSONRPC_INVALID_PARAMS,\n\tJSONRPC_METHOD_NOT_FOUND,\n\tMCP_HEADER_MISMATCH,\n\tMCP_META_VERSION,\n\tMCP_MISSING_CAPABILITY,\n\tMCP_PROTOCOL_VERSION,\n\tMCP_UNSUPPORTED_VERSION,\n\tinferEra,\n\tinferVersion,\n\tisInitializeRequest,\n\tisModernRequest,\n} from '@src/core'\nimport { isRecord, isString } from '@orkestrel/contract'\nimport { MCP_METHOD_HEADER, MCP_NAME_HEADER, MCP_PROTOCOL_VERSION_HEADER } from './constants.js'\n\n/**\n * Infers the first required MCP HTTP header that is missing or mismatched.\n *\n * @remarks\n * A modern request derives its protocol, method, and tools/call-only name expectations from\n * the JSON-RPC body. A legacy request body requires a protocol header after initialization,\n * while a supplied legacy session version additionally diagnoses a header that disagrees with\n * the active session. Messages name the expected value but never echo the client-supplied one.\n *\n * @param request - The HTTP request carrying the headers\n * @param reference - The parsed invocation body, or the active legacy session version\n * @returns The first header issue, or `undefined` when the applicable headers agree\n *\n * @example\n * ```ts\n * const issue = inferHeaderIssue(request, rpcRequest)\n * issue?.header // 'Mcp-Method' when that field is absent or mismatched\n * ```\n */\nexport function inferHeaderIssue(\n\trequest: Request,\n\treference: JSONRPCInvocation | MCPVersion,\n): MCPHeaderIssue | undefined {\n\tconst protocol = request.headers.get(MCP_PROTOCOL_VERSION_HEADER)\n\tif (isString(reference)) {\n\t\tif (protocol === null) {\n\t\t\treturn {\n\t\t\t\theader: 'MCP-Protocol-Version',\n\t\t\t\treason: 'missing',\n\t\t\t\tmessage: `Required MCP-Protocol-Version header is missing; the active session uses '${reference}'.`,\n\t\t\t}\n\t\t}\n\t\tif (protocol !== reference) {\n\t\t\treturn {\n\t\t\t\theader: 'MCP-Protocol-Version',\n\t\t\t\treason: 'mismatched',\n\t\t\t\tmessage: `MCP-Protocol-Version header does not match the active session version '${reference}'.`,\n\t\t\t}\n\t\t}\n\t\treturn undefined\n\t}\n\tif (!isModernRequest(reference)) {\n\t\tif (isInitializeRequest(reference) || protocol !== null) return undefined\n\t\treturn {\n\t\t\theader: 'MCP-Protocol-Version',\n\t\t\treason: 'missing',\n\t\t\tmessage: `Required MCP-Protocol-Version header is missing; this server offers '${MCP_PROTOCOL_VERSION}'.`,\n\t\t}\n\t}\n\tconst message = reference\n\tconst metadata = isRecord(message.params?.['_meta']) ? message.params['_meta'] : undefined\n\tconst version = metadata?.[MCP_META_VERSION]\n\tif (!isString(version)) return undefined\n\tif (protocol === null) {\n\t\treturn {\n\t\t\theader: 'MCP-Protocol-Version',\n\t\t\treason: 'missing',\n\t\t\tmessage: `Required MCP-Protocol-Version header is missing; the request body version is '${version}'.`,\n\t\t}\n\t}\n\tif (protocol !== version) {\n\t\treturn {\n\t\t\theader: 'MCP-Protocol-Version',\n\t\t\treason: 'mismatched',\n\t\t\tmessage: `MCP-Protocol-Version header does not match the request body version '${version}'.`,\n\t\t}\n\t}\n\tconst method = request.headers.get(MCP_METHOD_HEADER)\n\tif (method === null) {\n\t\treturn {\n\t\t\theader: 'Mcp-Method',\n\t\t\treason: 'missing',\n\t\t\tmessage: `Required Mcp-Method header is missing; the request body method is '${message.method}'.`,\n\t\t}\n\t}\n\tif (method !== message.method) {\n\t\treturn {\n\t\t\theader: 'Mcp-Method',\n\t\t\treason: 'mismatched',\n\t\t\tmessage: `Mcp-Method header does not match the request body method '${message.method}'.`,\n\t\t}\n\t}\n\tif (message.method !== 'tools/call') return undefined\n\tconst name = message.params?.['name']\n\tif (!isString(name)) return undefined\n\tconst header = request.headers.get(MCP_NAME_HEADER)\n\tif (header === null) {\n\t\treturn {\n\t\t\theader: 'Mcp-Name',\n\t\t\treason: 'missing',\n\t\t\tmessage: `Required Mcp-Name header is missing; the request body tool name is '${name}'.`,\n\t\t}\n\t}\n\tif (header !== name) {\n\t\treturn {\n\t\t\theader: 'Mcp-Name',\n\t\t\treason: 'mismatched',\n\t\t\tmessage: `Mcp-Name header does not match the request body tool name '${name}'.`,\n\t\t}\n\t}\n\treturn undefined\n}\n\n/**\n * Infers the legacy revision an `initialize` request negotiates.\n *\n * @remarks\n * A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported\n * request selects the newest supported legacy revision, matching the core initialize result.\n *\n * @param request - The legacy initialize invocation\n * @returns The negotiated legacy protocol revision\n */\nexport function inferLegacyVersion(request: JSONRPCInvocation): MCPVersion {\n\tconst requested = request.params?.['protocolVersion']\n\tconst version = inferVersion(isString(requested) ? [requested] : [])\n\tif (version !== undefined && inferEra(version) === 'legacy') return version\n\treturn MCP_PROTOCOL_VERSION\n}\n\n/**\n * Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.\n *\n * @remarks\n * Notifications are accepted with `202`. Legacy response envelopes retain uniform `200`\n * status semantics, including in-band errors. Modern header/capability/version/parameter\n * failures map to `400`, method-not-found maps to `404`, and every other modern result maps\n * to `200`.\n *\n * @param response - The dispatch response, or `undefined` for a notification\n * @param era - The structurally selected request era\n * @returns The HTTP response status\n */\nexport function inferStatus(response: JSONRPCResponse | undefined, era: MCPEra): number {\n\tif (response === undefined) return 202\n\tif (era === 'legacy' || response.error === undefined) return 200\n\tif (response.error.code === JSONRPC_METHOD_NOT_FOUND) return 404\n\tif (\n\t\tresponse.error.code === MCP_HEADER_MISMATCH ||\n\t\tresponse.error.code === MCP_MISSING_CAPABILITY ||\n\t\tresponse.error.code === MCP_UNSUPPORTED_VERSION ||\n\t\tresponse.error.code === JSONRPC_INVALID_PARAMS\n\t) {\n\t\treturn 400\n\t}\n\treturn 200\n}\n","import type { StreamInterface } from '@orkestrel/server'\nimport type { MCPKeepaliveOptions } from '../types.js'\nimport { sanitizeBudget } from '@orkestrel/contract'\nimport { DEFAULT_MCP_KEEPALIVE_INTERVAL, SSE_KEEPALIVE_COMMENT } from '../constants.js'\nimport { createReadableStream } from '../helpers.js'\n\n/**\n * Composes one incoming HTTP request lifetime with one MCP-owned SSE response lifetime.\n *\n * @remarks\n * The composed {@link signal} observes request abort and EVERY way this response can end\n * without one: consumer cancellation of the bridged body, a forwarding failure mid-pump, and a\n * keepalive tick that finds the SSE stream already closed. That last pair is the whole point of\n * the composition — a client that vanishes mid-stream aborts nothing by itself, so unless this\n * object raises the signal on its own failure paths, the handler, the controlled stream, and\n * the producer behind them all keep running for a response that can no longer be written.\n * Graceful upstream completion is the one terminal that does NOT abort: the body simply closes,\n * because the exchange finished rather than ended.\n *\n * {@link bridge} preserves the source response status and headers, forwards its body bytes, and\n * owns keepalive comments plus listener/timer cleanup until upstream completion, request abort,\n * or consumer cancellation. This is a single-response lifecycle object, not a reusable bridge:\n * a second {@link bridge} call THROWS rather than arming a second keepalive over one lifecycle.\n * It supplies no handler or session policy.\n *\n * The keepalive interval is a BUDGET, sanitized like every other numeric knob in this package:\n * anything that is not a positive integer — `0`, a negative, a fractional value, `NaN`,\n * `Infinity` — falls back to {@link DEFAULT_MCP_KEEPALIVE_INTERVAL}, and a larger value clamps\n * to Node's `2_147_483_647` ms timer maximum. None may reach the platform's timer floor, where\n * an idle-liveness tick becomes the polling this package forbids everywhere else.\n *\n * @example\n * ```ts\n * import { HTTPDisconnect } from '@orkestrel/mcp/server'\n * import { openStream } from '@orkestrel/server'\n *\n * const disconnect = new HTTPDisconnect(request.signal, { interval: 15_000 })\n * const stream = openStream()\n * const response = disconnect.bridge(stream)\n * ```\n */\nexport class HTTPDisconnect {\n\treadonly #response = new AbortController()\n\treadonly #lifecycle = new AbortController()\n\treadonly #interval: number\n\treadonly #signal: AbortSignal\n\t#timer: ReturnType<typeof setInterval> | undefined\n\t#bridged = false\n\t#pulling = false\n\n\t/**\n\t * Creates the lifecycle composition for one request and its future SSE response.\n\t *\n\t * @param signal - The incoming request signal\n\t * @param options - Optional keepalive `interval` in milliseconds; an invalid value falls back\n\t * to {@link DEFAULT_MCP_KEEPALIVE_INTERVAL}, and one above Node's timer maximum clamps to it\n\t */\n\tconstructor(signal: AbortSignal, options?: MCPKeepaliveOptions) {\n\t\t// `sanitizeBudget` rejects `NaN`, `Infinity`, a negative, and a fractional value; `0` is a\n\t\t// non-negative integer so it passes, and `setInterval(fn, 0)` is the same busy loop every\n\t\t// one of those produces. A keepalive cadence is therefore bounded below at one, not zero.\n\t\tconst interval = sanitizeBudget(options?.interval, DEFAULT_MCP_KEEPALIVE_INTERVAL)\n\t\tthis.#interval =\n\t\t\tinterval > 0 ? Math.min(interval, 2_147_483_647) : DEFAULT_MCP_KEEPALIVE_INTERVAL\n\t\tthis.#signal = AbortSignal.any([signal, this.#response.signal])\n\t}\n\n\t/**\n\t * The signal aborted by the incoming request, or by any end of this response that is not\n\t * its graceful completion.\n\t *\n\t * @returns The composed lifecycle signal\n\t */\n\tget signal(): AbortSignal {\n\t\treturn this.#signal\n\t}\n\n\t/**\n\t * Bridges one open SSE response through cancellation-aware byte forwarding and keepalives.\n\t *\n\t * Consumer cancellation, a read failure while forwarding, and a keepalive tick that finds the\n\t * SSE stream already closed each abort {@link signal}; consumer cancellation also cancels the\n\t * upstream reader. Upstream completion closes the returned body without inventing an abort.\n\t * Every terminal path clears the keepalive timer and detaches the bridge-owned abort listener.\n\t *\n\t * @param stream - The open SSE stream whose response will be consumed by the HTTP writer\n\t * @returns A one-use response preserving status, status text, headers, and SSE body bytes\n\t * @throws When this disconnect has already bridged a stream, or the supplied SSE response\n\t * has no body\n\t */\n\tbridge(stream: StreamInterface): Response {\n\t\t// One disconnect composes ONE request with ONE response, and the guard is what makes that\n\t\t// enforced rather than merely documented. A second call used to overwrite `#timer`, which\n\t\t// left the first interval running with no handle able to clear it, and its own abort\n\t\t// listener registered against a `#lifecycle` the first bridge's terminal had already\n\t\t// aborted — so the second bridge carried neither cleanup. Refuse before taking the\n\t\t// reader, so the stream a mis-wired caller passed is still bridgeable elsewhere.\n\t\tif (this.#bridged) throw new Error('MCP SSE response is already bridged')\n\t\tthis.#bridged = true\n\t\tconst response = stream.response\n\t\tconst body = response.body\n\t\tif (body === null) throw new Error('MCP SSE response has no body')\n\t\tconst reader = body.getReader()\n\t\t// This timer is SSE transport liveness, not polling for producer work: an idle response\n\t\t// must write to let the HTTP writer observe a dead socket and cancel the body.\n\t\tthis.#timer = setInterval(() => {\n\t\t\tif (stream.closed) {\n\t\t\t\t// A close observed while `reader.read()` is still outstanding is the graceful\n\t\t\t\t// `end()` drain window. The read itself will release once it observes the terminal.\n\t\t\t\tif (!this.#pulling) this.#abort()\n\t\t\t} else stream.comment(SSE_KEEPALIVE_COMMENT)\n\t\t}, this.#interval)\n\t\t// The composed signal already aborted, so this listener only has to release the bridge's\n\t\t// own timer and listener — raising the signal again would be answering an event with\n\t\t// itself.\n\t\tthis.#signal.addEventListener('abort', () => this.#release(), {\n\t\t\tonce: true,\n\t\t\tsignal: this.#lifecycle.signal,\n\t\t})\n\t\tif (this.#signal.aborted) this.#release()\n\t\telse if (stream.closed) this.#abort()\n\t\treturn new Response(\n\t\t\tcreateReadableStream<Uint8Array>(\n\t\t\t\tasync (controller) => {\n\t\t\t\t\tthis.#pulling = true\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst chunk = await reader.read()\n\t\t\t\t\t\tif (chunk.done) {\n\t\t\t\t\t\t\t// The exchange FINISHED. Release the bridge without raising the signal:\n\t\t\t\t\t\t\t// an abort here would tell a handler that already answered that its\n\t\t\t\t\t\t\t// request was cancelled.\n\t\t\t\t\t\t\tthis.#release()\n\t\t\t\t\t\t\tcontroller.close()\n\t\t\t\t\t\t} else controller.enqueue(chunk.value)\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthis.#abort()\n\t\t\t\t\t\tcontroller.error(error)\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tthis.#pulling = false\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tasync (reason) => {\n\t\t\t\t\tthis.#abort()\n\t\t\t\t\tawait reader.cancel(reason)\n\t\t\t\t},\n\t\t\t),\n\t\t\t{\n\t\t\t\tstatus: response.status,\n\t\t\t\tstatusText: response.statusText,\n\t\t\t\theaders: response.headers,\n\t\t\t},\n\t\t)\n\t}\n\n\t// Give up this bridge's OWN resources — the keepalive timer and the abort listener — without\n\t// saying anything about the request. The terminal a graceful completion takes.\n\t#release(): void {\n\t\tif (this.#timer !== undefined) {\n\t\t\tclearInterval(this.#timer)\n\t\t\tthis.#timer = undefined\n\t\t}\n\t\tthis.#lifecycle.abort()\n\t}\n\n\t// End the response lifetime: release first, so the listener is already detached, then raise\n\t// the composed signal for every owner still holding it. Idempotent — `abort()` is.\n\t#abort(): void {\n\t\tthis.#release()\n\t\tthis.#response.abort()\n\t}\n}\n","import type { MCPDispatcherInterface } from '@src/core'\nimport type { RouteContext } from '@orkestrel/router'\nimport type { HTTPHandlerOptions } from './types.js'\nimport {\n\tJSONRPC_INVALID_REQUEST,\n\tJSONRPC_INVALID_PARAMS,\n\tJSONRPC_PARSE_ERROR,\n\tMCP_HEADER_MISMATCH,\n\tMCP_UNSUPPORTED_VERSION,\n\tSUPPORTED_PROTOCOL_VERSIONS,\n\tbuildJSONRPCError,\n\tisMCPVersion,\n\tisModernRequest,\n\tparseRequestContext,\n\tparseJSONRPCMessage,\n} from '@src/core'\nimport { openStream } from '@orkestrel/server'\nimport {\n\tMCP_PROTOCOL_VERSION_HEADER,\n\tSSE_BUFFERING_DISABLED,\n\tSSE_BUFFERING_HEADER,\n} from './constants.js'\nimport { acceptsEventStream, allowsOrigin, sendEventStream } from './helpers.js'\nimport { inferHeaderIssue, inferStatus } from './inferers.js'\nimport { HTTPDisconnect } from './transports/HTTPDisconnect.js'\n\n/**\n * Creates the Streamable-HTTP POST handler used by `createMCPRoutes`.\n *\n * @remarks\n * Modern requests require matching protocol/method headers and a matching name header only\n * for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is\n * accepted, while every other headerless request needs a live legacy session to supply its\n * pinned version. A present origin must occur in `origin.origins` unless validation is\n * explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy\n * errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request\n * signal with response-body cancellation and supplies the result to every dispatched modern\n * handler through `MCPDispatchOptions.signal`. After every transport validation and immediately\n * before dispatch, the optional synchronous `caller` extractor reads front-middleware state; a\n * defined value is added to `MCPDispatchOptions`, while `undefined` is omitted.\n *\n * @typeParam TState - The consumer's opaque per-request route state type\n * @param mcp - The transport-agnostic MCP dispatcher to dispatch through\n * @param options - Optional streaming, origin-validation, SSE keepalive, and caller-extraction options\n * @returns A request handler for the stateless MCP POST route\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createMCPPostHandler } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * const handler = createMCPPostHandler(createMCPLegacy(mcp), { streaming: true }) // answers `initialize` too; pass `mcp` alone for modern-only\n * await handler(new Request('http://localhost/mcp', {\n * \tmethod: 'POST',\n * \tbody: '{\"jsonrpc\":\"2.0\",\"method\":\"ping\",\"id\":1}',\n * }))\n * ```\n */\nexport function createMCPPostHandler<TState = unknown>(\n\tmcp: MCPDispatcherInterface,\n\toptions?: HTTPHandlerOptions<TState>,\n): (request: Request, context?: RouteContext<string, TState>) => Promise<Response> {\n\tconst streaming = options?.streaming ?? true\n\tconst origin = options?.origin\n\treturn async (request, context): Promise<Response> => {\n\t\tif (!allowsOrigin(request, origin)) return new Response(null, { status: 403 })\n\t\tlet text: string\n\t\ttry {\n\t\t\ttext = await request.text()\n\t\t} catch {\n\t\t\treturn Response.json(buildJSONRPCError(undefined, JSONRPC_PARSE_ERROR, 'Parse error'), {\n\t\t\t\tstatus: 400,\n\t\t\t})\n\t\t}\n\t\tlet parsed: unknown\n\t\ttry {\n\t\t\tparsed = JSON.parse(text)\n\t\t} catch {\n\t\t\treturn Response.json(buildJSONRPCError(undefined, JSONRPC_PARSE_ERROR, 'Parse error'), {\n\t\t\t\tstatus: 400,\n\t\t\t})\n\t\t}\n\t\tconst invocation = parseJSONRPCMessage(parsed)\n\t\tif (invocation === undefined || !('method' in invocation)) {\n\t\t\treturn Response.json(\n\t\t\t\tbuildJSONRPCError(undefined, JSONRPC_INVALID_REQUEST, 'Invalid Request'),\n\t\t\t\t{ status: 400 },\n\t\t\t)\n\t\t}\n\t\tconst era = isModernRequest(invocation) ? 'modern' : 'legacy'\n\t\tconst id = invocation.id\n\t\tconst protocol = request.headers.get(MCP_PROTOCOL_VERSION_HEADER)\n\t\tif (era === 'modern') {\n\t\t\tif (parseRequestContext(invocation) === undefined) {\n\t\t\t\treturn Response.json(\n\t\t\t\t\tbuildJSONRPCError(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tJSONRPC_INVALID_PARAMS,\n\t\t\t\t\t\t'Invalid params: malformed modern request metadata',\n\t\t\t\t\t),\n\t\t\t\t\t{ status: 400 },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tconst issue = inferHeaderIssue(request, invocation)\n\t\tif (issue !== undefined) {\n\t\t\treturn Response.json(buildJSONRPCError(id, MCP_HEADER_MISMATCH, issue.message), {\n\t\t\t\tstatus: 400,\n\t\t\t})\n\t\t}\n\t\tif (era === 'legacy') {\n\t\t\tif (protocol !== null && !isMCPVersion(protocol)) {\n\t\t\t\treturn Response.json(\n\t\t\t\t\tbuildJSONRPCError(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tMCP_UNSUPPORTED_VERSION,\n\t\t\t\t\t\t`Unsupported MCP protocol version '${protocol}'`,\n\t\t\t\t\t\t{ supported: SUPPORTED_PROTOCOL_VERSIONS, requested: protocol },\n\t\t\t\t\t),\n\t\t\t\t\t{ status: 400 },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tconst disconnect = new HTTPDisconnect(request.signal, options?.keepalive)\n\t\tconst caller = options?.caller?.(request, context)\n\t\tconst response = await mcp.dispatch(invocation, {\n\t\t\tsignal: disconnect.signal,\n\t\t\t...(caller === undefined ? {} : { caller }),\n\t\t})\n\t\tif (response !== undefined && Symbol.asyncIterator in response) {\n\t\t\tconst stream = openStream()\n\t\t\tstream.response.headers.set(SSE_BUFFERING_HEADER, SSE_BUFFERING_DISABLED)\n\t\t\tqueueMicrotask(() => void sendEventStream(response, stream))\n\t\t\treturn disconnect.bridge(stream)\n\t\t}\n\t\tconst status = inferStatus(response, era)\n\t\tif (response === undefined) return new Response(null, { status })\n\t\tif (status === 200 && streaming && acceptsEventStream(request)) {\n\t\t\tconst stream = openStream()\n\t\t\tstream.response.headers.set(SSE_BUFFERING_HEADER, SSE_BUFFERING_DISABLED)\n\t\t\tstream.write({ data: JSON.stringify(response) })\n\t\t\tstream.end()\n\t\t\treturn stream.response\n\t\t}\n\t\treturn Response.json(response, { status })\n\t}\n}\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { HTTPClientTransportOptions } from '../types.js'\nimport {\n\tinferRequestVersion,\n\tisJSONRPCResponse,\n\tisMCPVersion,\n\tisModernRequest,\n\tparseJSONRPCMessage,\n} from '@src/core'\nimport { isRecord, isString } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\nimport {\n\tMCP_METHOD_HEADER,\n\tMCP_NAME_HEADER,\n\tMCP_PROTOCOL_VERSION_HEADER,\n\tMCP_SESSION_HEADER,\n} from '../constants.js'\nimport { readEventStream } from '../helpers.js'\n\n/**\n * The HTTP CLIENT transport for the Model Context Protocol — a\n * {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server over\n * `fetch`, the egress mirror of the server's `createMCPRoutes`.\n *\n * @remarks\n * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized\n * message to `options.url` with `content-type: application/json` and an\n * `Accept` of BOTH `application/json` and `text/event-stream` (so the server may\n * answer with either framing) — plus any `options.headers` (for example, an `Authorization`\n * bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on\n * the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes\n * to.\n * - **Both reply framings.** A `200` with an `application/json` body is parsed with\n * `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the\n * `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} ({@link\n * readEventStream}) — the inverse of the server's `openStream` seam, so the wire\n * round-trips. A `202`\n * Accepted (a notification) carries no body and emits nothing.\n * - **Session and protocol headers.** `start()` is a no-op (a\n * request/response transport opens no long-lived connection). The\n * `mcp-session-id` response header, when a STATEFUL server sends one (on\n * `initialize`), is captured into `session` and then ECHOED as the\n * `mcp-session-id` request header on every SUBSEQUENT request — so an\n * `MCPClient` passes a stateful server's session validation. The\n * initialize result's `protocolVersion` is likewise captured, but only\n * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on\n * subsequent legacy requests. Modern requests instead derive protocol and method\n * headers from the message, plus the name header only for `tools/call`.\n * Before initialize returns, neither captured legacy header is sent.\n * `close()` clears the captured protocol so a reconnect's `initialize`\n * POST is headerless; the captured `session` persists across `close()`.\n * - **`close()` releases what is in flight.** Every `fetch` this transport still has open is\n * ABORTED, which cancels the response body a `send` is reading — an SSE reply the server\n * never ends would otherwise outlive the transport, with nothing left able to reach it. The\n * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is\n * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.\n * - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,\n * the SSE decoder) — a non-message reply is dropped, never asserted; a `fetch` /\n * decode failure surfaces on the `error` event rather than escaping `send`.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires\n * `message` per decoded reply, `error` on a fault, and `close` on `close()`.\n *\n * @example\n * ```ts\n * const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })\n * const client = new MCPClient({ transport })\n * await client.connect()\n * ```\n */\nexport class HTTPClientTransport implements MCPClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #url: string\n\treadonly #headers: Readonly<Record<string, string>>\n\treadonly #fetch: typeof fetch\n\treadonly #timeout: number | undefined\n\t// The requests on the wire, one controller each. `close` is the only thing that can\n\t// reach them: a `send` parked on a reply that never ends holds both the request and its\n\t// response reader, and no other seam this transport exposes leads back to either.\n\treadonly #pending = new Set<AbortController>()\n\t#session: string | undefined = undefined\n\t#protocol: string | undefined = undefined\n\t#closed = false\n\n\tconstructor(options: HTTPClientTransportOptions) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#url = options.url\n\t\tthis.#headers = options.headers ?? {}\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch\n\t\tthis.#timeout = options.timeout\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\treturn this.#session\n\t}\n\n\tget duplex(): boolean {\n\t\t// Streamable HTTP carries no client-initiated notification: the dated revision defines\n\t\t// none over it, and closing the response stream is the cancellation signal instead.\n\t\treturn false\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// A request/response transport opens no long-lived connection — `send` issues each\n\t\t// `fetch` on demand. There is nothing to arm; opening the next connected lifetime is all\n\t\t// this does, so a transport an earlier `close` ended sends again from here.\n\t\tthis.#closed = false\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\tconst request = new AbortController()\n\t\tthis.#pending.add(request)\n\t\ttry {\n\t\t\tawait this.#exchange(message, request.signal)\n\t\t} finally {\n\t\t\tthis.#pending.delete(request)\n\t\t}\n\t}\n\n\t// One request/response exchange under `signal`: `close` aborts it, and a `timeout` option\n\t// composes with it so whichever fires first ends the same fetch and the same body read.\n\tasync #exchange(message: JSONRPCMessage, signal: AbortSignal): Promise<void> {\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await this.#fetch(this.#url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'content-type': 'application/json',\n\t\t\t\t\taccept: 'application/json, text/event-stream',\n\t\t\t\t\t// Echo a captured session id so a STATEFUL server validates the request; before\n\t\t\t\t\t// `initialize` returns one `#session` is undefined → no header (safe for a\n\t\t\t\t\t// stateless server). A caller `headers` key still wins (merged last).\n\t\t\t\t\t...(this.#session === undefined ? {} : { [MCP_SESSION_HEADER]: this.#session }),\n\t\t\t\t\t...this.#buildHeaders(message),\n\t\t\t\t\t...this.#headers,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(message),\n\t\t\t\tsignal:\n\t\t\t\t\tthis.#timeout === undefined\n\t\t\t\t\t\t? signal\n\t\t\t\t\t\t: AbortSignal.any([signal, AbortSignal.timeout(this.#timeout)]),\n\t\t\t})\n\t\t} catch (error) {\n\t\t\t// A network-level failure (connection refused, DNS) — surface it for observation;\n\t\t\t// the client's per-request deadline still rejects the pending request.\n\t\t\tthis.#emitter.emit('error', error)\n\t\t\treturn\n\t\t}\n\t\t// Capture a server-assigned session id (a stateless server sends none) so it is echoed\n\t\t// on subsequent requests; a missing header leaves `session` unchanged.\n\t\tconst session = response.headers.get(MCP_SESSION_HEADER)\n\t\tif (session !== null) this.#session = session\n\t\tawait this.#deliver(response)\n\t}\n\n\t// Abort every request still on the wire, then clear the captured protocol before emitting\n\t// `close`, so a reconnect's `initialize` POST carries no `mcp-protocol-version` header (the\n\t// captured `session` is untouched). Idempotent: a second `close` on a transport this one\n\t// already ended releases nothing and emits nothing.\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tfor (const request of this.#pending) request.abort()\n\t\tthis.#pending.clear()\n\t\tthis.#protocol = undefined\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Modern requests announce their own protocol version, so the header is projected from the\n\t// message through the SHARED `inferRequestVersion` — the same read the server's own\n\t// expectation performs, and the same read the browser face performs. Legacy requests carry\n\t// the version captured from the `initialize` handshake instead.\n\t#buildHeaders(message: JSONRPCMessage): Readonly<Record<string, string>> {\n\t\tif (isModernRequest(message)) {\n\t\t\tconst version = inferRequestVersion(message)\n\t\t\tconst name = message.params?.['name']\n\t\t\treturn {\n\t\t\t\t...(version === undefined ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: version }),\n\t\t\t\t[MCP_METHOD_HEADER]: message.method,\n\t\t\t\t...(message.method === 'tools/call' && isString(name) ? { [MCP_NAME_HEADER]: name } : {}),\n\t\t\t}\n\t\t}\n\t\treturn this.#protocol === undefined ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: this.#protocol }\n\t}\n\n\t// Decode a reply and emit each carried message. A 202 (notification accepted) has no\n\t// body — emit nothing. An `application/json` body is one envelope; a `text/event-stream`\n\t// body is decoded with the core SSEParser (one or more `data:` events). A decode failure\n\t// surfaces on `error` rather than escaping.\n\tasync #deliver(response: Response): Promise<void> {\n\t\tif (response.status === 202) return\n\t\tconst type = response.headers.get('content-type') ?? ''\n\t\ttry {\n\t\t\tif (type.includes('text/event-stream')) {\n\t\t\t\tfor (const message of await readEventStream(response)) this.#capture(message)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (type.includes('application/json')) {\n\t\t\t\tconst message = parseJSONRPCMessage(await response.json())\n\t\t\t\tif (message !== undefined) this.#capture(message)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n\n\t// Capture the negotiated SUPPORTED protocol from the initialize result before emitting\n\t// the message, so the next request carries its required protocol-version header; any\n\t// other value (missing or unsupported) is ignored and leaves `#protocol` unchanged.\n\t#capture(message: JSONRPCMessage): void {\n\t\tif (\n\t\t\tisJSONRPCResponse(message) &&\n\t\t\tisRecord(message.result) &&\n\t\t\tisMCPVersion(message.result['protocolVersion'])\n\t\t) {\n\t\t\tthis.#protocol = message.result['protocolVersion']\n\t\t}\n\t\tthis.#emitter.emit('message', message)\n\t}\n}\n","import type { JSONRPCMessage } from '@src/core'\nimport type { StreamInterface } from '@orkestrel/server'\nimport type { EventStoreEntry, MCPSessionInterface, MCPSessionOptions } from './types.js'\nimport { DEFAULT_MCP_SESSION_CAPACITY, DEFAULT_MCP_SESSION_TTL } from './constants.js'\n\n/**\n * One MCP transport session — the per-session entity a {@link\n * import('./middlewares.js').createMCPSession} middleware owns, keyed by its `id`, carrying the\n * resumable server→client push channel with its bounded replay log FOLDED IN.\n *\n * @remarks\n * The single session entity (the old `SessionState` + `EventStore` merged): it holds the\n * session `id`, its OWN bounded, replayable log of pushed server→client messages (the\n * resumable GET-SSE channel — a private `#events` `Map` + a monotone `#counter`, with\n * `capacity` / `ttl` eviction, not a separate store), and the set of open\n * server→client SSE streams (a resumable `GET {path}` registers through `attach`, unregisters through\n * `detach` on disconnect). Still a small entity (not a record), built minimal + extensible.\n *\n * - **`push` is the server-initiated primitive.** It APPENDS the message to the log (assigning\n * a monotone base36 event id) and FANS it out to every attached stream as one `id:`-tagged\n * SSE event (`stream.write({ id, data })`). A push with NO attached stream is still logged,\n * so a client that connects (or reconnects with a `Last-Event-ID`) LATER replays it from the\n * log. A `write` to a closed stream is a safe no-op (the {@link\n * `@orkestrel/server`'s `openStream` contract), so a just-disconnected stream that\n * has not yet been `detach`ed never throws. A replayed event and the live one carry the\n * IDENTICAL id (the log assigns it once).\n *\n * - **`replay(afterId)` is strictly-after.** It returns every retained log entry whose id sorts\n * AFTER `afterId` in append order — the missed-events list the `GET {path}` handler writes\n * before attaching the stream for live pushes. The decision for an UNKNOWN / already-evicted\n * `afterId` (the client's cursor fell off the back of the capacity window, or never existed):\n * replay NOTHING. Replaying the whole retained log would re-deliver events the client never\n * lost (its cursor is OLDER than everything retained); returning `[]` lets the handler then\n * stream only the fresh pushes that follow `attach` — the spec-sane resume.\n *\n * - **Bounded, append-ordered, plain `Map`.** The log lives in ONE insertion-ordered\n * `Map<id, entry>` — insertion order IS append order IS id order, so `replay` and capacity\n * eviction both walk the map directly. NO database mirror — the log is process-local\n * transport mechanics, not durable state. `push` first drops every entry older than `ttl`\n * (lazy TTL — no background timer, the middleware's lazy-window idiom), appends, then evicts\n * the OLDEST entries until at most `capacity` remain; `replay` also runs the lazy TTL sweep\n * first, so a stale entry is never replayed.\n *\n * - **No transport coupling beyond the SSE seam.** It holds session state + the generic {@link\n * StreamInterface} handles `attach` was handed — never a raw socket, request, or response.\n * The middleware opens the stream (the spine seam) and registers it here; this class only\n * serializes a message onto the already-open streams.\n *\n * - **Injected clock.** `push` / `replay` accept an optional `now` (epoch ms), defaulting to\n * `Date.now()` — so a test drives TTL eviction with an elapsed clock rather than a real\n * timer.\n *\n * @example\n * ```ts\n * const session = new MCPSession(crypto.randomUUID())\n * session.attach(stream) // an open resumable GET-SSE stream\n * session.push({ jsonrpc: '2.0', method: 'notifications/message', params: { text: 'hi' } })\n * // → logged AND written to `stream` as an `id:`-tagged event; a reconnect replays it\n * ```\n */\nexport class MCPSession implements MCPSessionInterface {\n\treadonly #id: string\n\treadonly #events = new Map<string, EventStoreEntry>()\n\treadonly #streams = new Set<StreamInterface>()\n\treadonly #capacity: number\n\treadonly #ttl: number\n\t#counter = 0\n\n\tconstructor(id: string, options?: MCPSessionOptions) {\n\t\tthis.#id = id\n\t\tthis.#capacity = options?.capacity ?? DEFAULT_MCP_SESSION_CAPACITY\n\t\tthis.#ttl = options?.ttl ?? DEFAULT_MCP_SESSION_TTL\n\t}\n\n\tget id(): string {\n\t\treturn this.#id\n\t}\n\n\tattach(stream: StreamInterface): void {\n\t\tthis.#streams.add(stream)\n\t}\n\n\tdetach(stream: StreamInterface): void {\n\t\tthis.#streams.delete(stream)\n\t}\n\n\tpush(message: JSONRPCMessage, now = Date.now()): string {\n\t\t// Append to the log first (assigning the monotone event id), then fan the SAME id out to\n\t\t// every open stream — so a replayed event and a live one carry the identical id.\n\t\tconst id = this.#append(message, now)\n\t\tconst data = JSON.stringify(message)\n\t\tfor (const stream of this.#streams) stream.write({ id, data })\n\t\treturn id\n\t}\n\n\treplay(afterId: string, now = Date.now()): readonly EventStoreEntry[] {\n\t\tthis.#evict(now)\n\t\tconst out: EventStoreEntry[] = []\n\t\tlet found = false\n\t\tfor (const entry of this.#events.values()) {\n\t\t\t// Collect every entry STRICTLY AFTER `afterId`, in append order.\n\t\t\tif (found) out.push(entry)\n\t\t\telse if (entry.id === afterId) found = true\n\t\t}\n\t\t// An unknown / evicted `afterId` was never matched → `found` stays false → replay nothing\n\t\t// (the documented spec-sane choice; never re-deliver un-lost events).\n\t\treturn found ? out : []\n\t}\n\n\t// Append a message to the bounded replay log under a fresh monotone id, evicting stale +\n\t// over-capacity entries — the folded EventStore.append, now private to the session.\n\t#append(message: JSONRPCMessage, now: number): string {\n\t\t// Lazy TTL sweep BEFORE appending so an idle log shrinks as it is written.\n\t\tthis.#evict(now)\n\t\tthis.#counter += 1\n\t\tconst id = this.#counter.toString(36)\n\t\tthis.#events.set(id, { id, message, timestamp: now })\n\t\t// Capacity bound: drop the OLDEST entries (front of the insertion-ordered map) until at\n\t\t// most `capacity` remain — so the log is the most-recent `capacity` pushes.\n\t\twhile (this.#events.size > this.#capacity) {\n\t\t\tconst oldest = this.#events.keys().next().value\n\t\t\tif (oldest === undefined) break\n\t\t\tthis.#events.delete(oldest)\n\t\t}\n\t\treturn id\n\t}\n\n\t// Drop every entry older than the TTL. Entries are append-ordered (oldest first) and the\n\t// timestamp is monotone with insertion, so the stale run is a PREFIX — stop at the first live\n\t// entry. A non-positive ttl is treated as no expiry (nothing ever ages out by time).\n\t#evict(now: number): void {\n\t\tif (this.#ttl <= 0) return\n\t\tconst cutoff = now - this.#ttl\n\t\tfor (const [id, entry] of this.#events) {\n\t\t\tif (entry.timestamp <= cutoff) this.#events.delete(id)\n\t\t\telse break\n\t\t}\n\t}\n}\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { NodeWebSocketInterface } from '@orkestrel/websocket'\nimport { parseJSONRPCMessage } from '@src/core'\nimport { Emitter } from '@orkestrel/emitter'\n\n/**\n * The per-connection JSON-RPC-over-WebSocket SERVER bridge — wraps a\n * {@link NodeWebSocketInterface} (the RFC 6455 wire wrapper) as a\n * {@link MCPClientTransportInterface}, the bidirectional JSON-RPC message channel\n * `createWebSocketServer` pumps `mcp.dispatch` over and the egress mirror's\n * {@link import('./WebSocketClientTransport.js').WebSocketClientTransport} reuses.\n *\n * @remarks\n * - **Reuses `MCPClientTransportInterface`.** It IS the same generic carrier the HTTP\n * client transport implements — `emitter` (`message` / `close` / `error`), `start`,\n * `send`, `close` — so the WebSocket server and client both speak ONE transport contract,\n * no near-duplicate sibling interface. `session` is `undefined` (the stateless v1; a\n * session id is the deferred sessions tier). The name keeps the role explicit even though\n * the shape is shared.\n * - **Inbound (`message`).** `start()` subscribes to the socket's `message` event; each text\n * frame is `JSON.parse`d inside a try/catch and narrowed with `parseJSONRPCMessage` — a\n * well-formed {@link JSONRPCMessage} is re-emitted on this transport's `message` event (the\n * parsed envelope the {@link import('@orkestrel/mcp').MCPServerInterface} pump dispatches), while\n * a non-JSON or non-message frame is surfaced on `error` and DROPPED, never thrown. It\n * also bridges the socket's `close` → this transport's `close`, and the socket's `error`.\n * - **Outbound (`send`).** `send(message)` writes one text frame\n * (`nodeWs.send(JSON.stringify(message))`); the underlying wrapper no-ops a write on a\n * non-open socket, so a closed connection drops silently rather than throwing.\n * - **`close()`** removes the subscriptions `start()` installed on the socket, closes the\n * underlying socket (the RFC 6455 close handshake), and fires the transport's `close` event\n * (idempotent — a second `close`, or a socket-driven close, emits once). A frame that arrives\n * between that release and the peer's close echo reaches nothing: the socket-driven close path\n * releases the same way, so a closed transport is never subscribed to a live socket.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the emitter\n * isolates a listener throw (a buggy observer never corrupts the bridge). `error` is a\n * DOMAIN event (a transport-level fault), distinct from the emitter's listener-error channel.\n */\nexport class WebSocketServerTransport implements MCPClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #socket: NodeWebSocketInterface\n\t// Bound once, as fields, so `close` can remove exactly the subscriptions `start` installed:\n\t// an inline arrow is a new function on every call and can never be removed by reference.\n\treadonly #frame = (text: string): void => this.#receive(text)\n\treadonly #ending = (): void => this.#onClose()\n\treadonly #failure = (error: unknown): void => this.#emitter.emit('error', error)\n\t#started = false\n\t#closed = false\n\n\tconstructor(socket: NodeWebSocketInterface) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#socket = socket\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\t// The stateless v1 holds no session — a server-assigned id is the deferred tier.\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// A socket is bidirectional for its whole life: either side writes a frame whenever it\n\t\t// has one, with no request to attach it to.\n\t\treturn true\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// Arm the socket subscriptions once: a text frame becomes a `message`, the socket's\n\t\t// close / error bridge to this transport's events. Idempotent — a second `start` is a\n\t\t// no-op (the single MCPServer pump subscribes once).\n\t\tif (this.#started || this.#closed) return\n\t\tthis.#started = true\n\t\tthis.#socket.emitter.on('message', this.#frame)\n\t\tthis.#socket.emitter.on('close', this.#ending)\n\t\tthis.#socket.emitter.on('error', this.#failure)\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\t// The wrapper drops a write on a non-open socket, so a closed connection is a\n\t\t// silent no-op rather than a throw.\n\t\tthis.#socket.send(JSON.stringify(message))\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// Release BEFORE the close handshake: the peer has not answered yet, so a frame already\n\t\t// on the wire still decodes, and a subscription left behind would re-emit it on a\n\t\t// transport whose `close` has already fired.\n\t\tthis.#release()\n\t\tthis.#socket.close()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Decode one inbound text frame: `JSON.parse` → `parseJSONRPCMessage`. A well-formed\n\t// message re-emits on `message`; a malformed / non-message frame surfaces on `error` and\n\t// is dropped (the bridge never throws on adversarial wire input).\n\t#receive(text: string): void {\n\t\tlet parsed: unknown\n\t\ttry {\n\t\t\tparsed = JSON.parse(text)\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t\treturn\n\t\t}\n\t\tconst message = parseJSONRPCMessage(parsed)\n\t\tif (message === undefined) {\n\t\t\tthis.#emitter.emit('error', new Error('non-JSON-RPC WebSocket frame'))\n\t\t\treturn\n\t\t}\n\t\tthis.#emitter.emit('message', message)\n\t}\n\n\t// The socket closed (peer close frame, transport teardown) — fire this transport's `close`\n\t// once. No peer identity check is needed: the socket is constructor-fixed and `start()` is\n\t// idempotent, so no superseded socket can report a close against a replacement.\n\t#onClose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tthis.#release()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Hand the socket back exactly as it was found: the wrapper is owned by the ingress that\n\t// claimed the upgrade, so this transport removes its own subscriptions and touches\n\t// nothing else on it.\n\t#release(): void {\n\t\tthis.#socket.emitter.off('message', this.#frame)\n\t\tthis.#socket.emitter.off('close', this.#ending)\n\t\tthis.#socket.emitter.off('error', this.#failure)\n\t}\n}\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { NodeWebSocketInterface } from '@orkestrel/websocket'\nimport type { WebSocketClientTransportOptions } from '../types.js'\nimport type { ClientRequest, IncomingMessage } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport { randomBytes } from 'node:crypto'\nimport { request as httpRequest } from 'node:http'\nimport { request as httpsRequest } from 'node:https'\nimport { parseJSONRPCMessage } from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\nimport {\n\tcomputeWebSocketAccept,\n\tcreateNodeWebSocket,\n\tWEBSOCKET_VERSION,\n} from '@orkestrel/websocket'\nimport { MCP_WEBSOCKET_SUBPROTOCOL } from '../constants.js'\n\n/**\n * The WebSocket CLIENT transport for the Model Context Protocol — a\n * {@link MCPClientTransportInterface} that drives a REMOTE MCP server over a WebSocket, the\n * egress mirror of {@link import('./factories.js').createWebSocketServer} and the WebSocket\n * sibling of {@link import('./HTTPClientTransport.js').HTTPClientTransport}.\n *\n * @remarks\n * - **Persistent bidirectional channel (unlike the HTTP transport).** `start()` performs the\n * RFC 6455 client handshake: it opens a `node:http`(`s`) `GET` carrying `Connection: Upgrade`\n * / `Upgrade: websocket` / a random `Sec-WebSocket-Key` / `Sec-WebSocket-Version: 13` /\n * `Sec-WebSocket-Protocol: mcp` (plus any `options.headers`), awaits the client `'upgrade'`\n * event, and VALIDATES `Sec-WebSocket-Accept === computeWebSocketAccept(key)` (the D2 helper)\n * — a mismatch (or a non-`101` response, or a request error) REJECTS `start()` and the socket\n * is destroyed. On success it wraps the raw upgraded socket in `createNodeWebSocket({ socket,\n * head })` (CLIENT mode — no key → frames are MASKED per RFC 6455 §5.3) and bridges its\n * `message`.\n * - **The arriving socket is RE-ASKED for, never assumed.** `start()` suspends across that\n * connect and upgrade, so it re-checks the transport's state before installing anything: a\n * concurrent `start()` that already installed a socket, or a {@link close} that ended the\n * transport while the handshake was on the wire, both WIN — the socket that arrives late is\n * DESTROYED and never bound, so no orphan is left re-emitting frames at nobody. Both\n * `start()` calls still resolve; exactly one socket is ever bound.\n * - **Inbound (`message`).** Each decoded text frame is `JSON.parse`d (guarded) and narrowed\n * with `parseJSONRPCMessage` — a {@link JSONRPCMessage} re-emits on this transport's `message`\n * event (the reply the {@link import('@orkestrel/mcp').MCPClientInterface} correlates by `id`); a\n * non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`\n * / `error` bridge to this transport's events.\n * - **Outbound (`send`).** `send(message)` writes one masked text frame.\n * - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An\n * upgrade still on the wire is DESTROYED, so a `close()` during the handshake ends the\n * transport at once instead of waiting for a peer that may never answer — the suspended\n * `start()` resolves, because the close is the outcome its caller asked for.\n * - **URL scheme.** `options.url` accepts a `ws://` / `wss://` URL or an `http://` / `https://`\n * one; a `ws(s)` scheme is converted to `http(s)` for the underlying upgrade request (`wss`\n * → TLS through `node:https`). Either reaches the same endpoint.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); every emit\n * the emitter isolates a listener throw (a buggy observer never corrupts the transport);\n * `error` is a DOMAIN event (a transport-level fault).\n *\n * @example\n * ```ts\n * const transport = new WebSocketClientTransport({ url: 'ws://localhost:3000/mcp' })\n * const client = new MCPClient({ transport })\n * await client.connect() // start() handshakes, then the MCP initialize runs over WS frames\n * ```\n */\nexport class WebSocketClientTransport implements MCPClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #url: string\n\treadonly #headers: Readonly<Record<string, string>>\n\t// Bound once, as fields, so `close` can remove exactly the subscriptions `#bind` installed:\n\t// an inline arrow is a new function on every call and can never be removed by reference.\n\treadonly #frame = (text: string): void => this.#receive(text)\n\treadonly #ending = (): void => this.#onClose()\n\treadonly #failure = (error: unknown): void => this.#emitter.emit('error', error)\n\t#socket: NodeWebSocketInterface | undefined = undefined\n\t// The upgrade on the wire. Nothing else holds it, so a `close` during the\n\t// handshake can only cancel through this.\n\t#request: ClientRequest | undefined = undefined\n\t#closed = false\n\n\tconstructor(options: WebSocketClientTransportOptions) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#url = options.url\n\t\tthis.#headers = options.headers ?? {}\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// A socket is bidirectional for its whole life: either side writes a frame whenever it\n\t\t// has one, with no request to attach it to.\n\t\treturn true\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// Already connected — a second `connect()` short-circuits in the client, but guard here\n\t\t// too (idempotent open).\n\t\tif (this.#socket !== undefined) return\n\t\tthis.#closed = false\n\t\ttry {\n\t\t\tawait this.#connect(this.#httpURL(), randomBytes(16).toString('base64'))\n\t\t} finally {\n\t\t\t// Whatever the handshake did, it is no longer on the wire, so nothing is left for a\n\t\t\t// later `close` to cancel.\n\t\t\tthis.#request = undefined\n\t\t}\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\tconst socket = this.#socket\n\t\tif (socket === undefined) throw new Error('WebSocket transport is not connected')\n\t\tsocket.send(JSON.stringify(message))\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// The upgrade still on the wire is this transport's to cancel. Without it a close during\n\t\t// the handshake waits out the peer, and the socket that finally arrives is destroyed long\n\t\t// after the caller was told the transport had ended.\n\t\tthis.#request?.destroy()\n\t\tconst socket = this.#socket\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\tif (socket !== undefined) socket.close()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Run the RFC 6455 client handshake and bind the socket it produces. Split from `start` so\n\t// the retained request is cleared on every settlement path, including a rejection.\n\tasync #connect(url: URL, key: string): Promise<void> {\n\t\tconst secure = url.protocol === 'https:'\n\t\tconst send = secure ? httpsRequest : httpRequest\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst request = send({\n\t\t\t\thostname: url.hostname,\n\t\t\t\tport: url.port.length > 0 ? Number(url.port) : secure ? 443 : 80,\n\t\t\t\tpath: `${url.pathname}${url.search}`,\n\t\t\t\theaders: {\n\t\t\t\t\tConnection: 'Upgrade',\n\t\t\t\t\tUpgrade: 'websocket',\n\t\t\t\t\t'Sec-WebSocket-Key': key,\n\t\t\t\t\t'Sec-WebSocket-Version': WEBSOCKET_VERSION,\n\t\t\t\t\t'Sec-WebSocket-Protocol': MCP_WEBSOCKET_SUBPROTOCOL,\n\t\t\t\t\t...this.#headers,\n\t\t\t\t},\n\t\t\t})\n\t\t\tthis.#request = request\n\n\t\t\t// The server accepted the upgrade: validate the handshake accept, then wrap the\n\t\t\t// raw socket in a CLIENT-mode NodeWebSocket (masks its frames).\n\t\t\trequest.on('upgrade', (response: IncomingMessage, socket: Duplex, head: Buffer) => {\n\t\t\t\tconst accept = response.headers['sec-websocket-accept']\n\t\t\t\tif (!isString(accept) || accept !== computeWebSocketAccept(key)) {\n\t\t\t\t\tsocket.destroy()\n\t\t\t\t\treject(new Error('WebSocket handshake failed: Sec-WebSocket-Accept mismatch'))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// RE-ASK. Every state the guard at the top of `start()` read is stale by now: this\n\t\t\t\t// handshake suspended across a real TCP connect and HTTP upgrade, during which a\n\t\t\t\t// second `start()` may have installed its own socket or `close()` may have ended the\n\t\t\t\t// transport. Nobody wants this one, so DESTROY it rather than binding a second,\n\t\t\t\t// never-closed peer that keeps re-emitting frames at a transport that has moved on.\n\t\t\t\t// `start()` still resolves: the winner (or the close) is the outcome the caller asked\n\t\t\t\t// for, not a failure.\n\t\t\t\tif (this.#closed || this.#socket !== undefined) {\n\t\t\t\t\tsocket.destroy()\n\t\t\t\t\tresolve()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst ws = createNodeWebSocket({ socket, head })\n\t\t\t\tthis.#socket = ws\n\t\t\t\tthis.#bind(ws)\n\t\t\t\tresolve()\n\t\t\t})\n\n\t\t\t// A plain (non-101) response means the server declined the upgrade.\n\t\t\trequest.on('response', (response) => {\n\t\t\t\tresponse.resume()\n\t\t\t\treject(new Error(`WebSocket upgrade declined with status ${response.statusCode ?? 0}`))\n\t\t\t})\n\t\t\t// A connection-level failure (refused, DNS, reset) — or the reset that follows the\n\t\t\t// `close()` above destroying this request, which IS that close arriving rather than a\n\t\t\t// fault: the caller asked for the transport to end, and it has.\n\t\t\trequest.on('error', (error) => {\n\t\t\t\tif (this.#closed) {\n\t\t\t\t\tresolve()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treject(error instanceof Error ? error : new Error(String(error)))\n\t\t\t})\n\t\t\trequest.end()\n\t\t})\n\t}\n\n\t// Bridge the upgraded socket's events onto the transport: a text frame → `message`\n\t// (decoded + narrowed), the socket close → `close`, a socket fault → `error`.\n\t#bind(ws: NodeWebSocketInterface): void {\n\t\tws.emitter.on('message', this.#frame)\n\t\tws.emitter.on('close', this.#ending)\n\t\tws.emitter.on('error', this.#failure)\n\t}\n\n\t// Unsubscribe from the socket this transport holds. The socket itself belongs to\n\t// the peer connection, so nothing else on it is touched.\n\t#release(): void {\n\t\tconst socket = this.#socket\n\t\tif (socket === undefined) return\n\t\tsocket.emitter.off('message', this.#frame)\n\t\tsocket.emitter.off('close', this.#ending)\n\t\tsocket.emitter.off('error', this.#failure)\n\t}\n\n\t// Decode one inbound text frame: `JSON.parse` → `parseJSONRPCMessage`. A well-formed\n\t// message re-emits on `message`; a malformed / non-message frame surfaces on `error` and\n\t// is dropped (never throws on adversarial wire input).\n\t#receive(text: string): void {\n\t\tlet parsed: unknown\n\t\ttry {\n\t\t\tparsed = JSON.parse(text)\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t\treturn\n\t\t}\n\t\tconst message = parseJSONRPCMessage(parsed)\n\t\tif (message === undefined) {\n\t\t\tthis.#emitter.emit('error', new Error('non-JSON-RPC WebSocket frame'))\n\t\t\treturn\n\t\t}\n\t\tthis.#emitter.emit('message', message)\n\t}\n\n\t// The current socket closed underneath us — fire `close` once. Only the socket this\n\t// transport still holds can reach here: a superseded one was unsubscribed when it was\n\t// released, so its own later close reports to nobody and cannot clear the live socket.\n\t#onClose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Normalize `options.url` to the `http(s)` URL the underlying upgrade request uses: a\n\t// `ws://` → `http://`, a `wss://` → `https://`; an `http(s)://` URL passes through. Any\n\t// other scheme throws (a clear boundary error, not a silent mis-dial).\n\t#httpURL(): URL {\n\t\tconst url = new URL(this.#url)\n\t\tif (url.protocol === 'ws:') url.protocol = 'http:'\n\t\telse if (url.protocol === 'wss:') url.protocol = 'https:'\n\t\telse if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n\t\t\tthrow new Error(`unsupported WebSocket URL scheme '${url.protocol}'`)\n\t\t}\n\t\treturn url\n\t}\n}\n","import type { MCPClientTransportEventMap, JSONRPCMessage } from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { ProcessExit } from '@orkestrel/process'\nimport type { StdioClientTransportInterface, StdioClientTransportOptions } from '../types.js'\nimport { Process } from '@orkestrel/process/server'\nimport { PROCESS_GRACE } from '@orkestrel/process'\nimport { Emitter } from '@orkestrel/emitter'\nimport { dispatchLines } from '../helpers.js'\n\n/**\n * The stdio CLIENT transport for the Model Context Protocol — a\n * {@link StdioClientTransportInterface} that drives a CHILD PROCESS MCP server over\n * newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link\n * import('./HTTPClientTransport.js').HTTPClientTransport} and {@link\n * import('./WebSocketClientTransport.js').WebSocketClientTransport}.\n *\n * @remarks\n * - **Composes `@orkestrel/process`.** `start()` builds one supervised\n * {@link import('@orkestrel/process/server').Process} with `writable: true`, so the child's\n * `stdin`/`stdout` are the JSON-RPC channel and its `stderr` is retained as bounded evidence\n * rather than parsed as protocol. The supervisor owns spawn, framing, and termination.\n * - **Inbound (`message`).** Standard output is drained eagerly through the supervisor's\n * `readline`-framed `lines` iterable, so a multi-byte UTF-8 sequence split across two reads is\n * decoded whole and a final line written without a trailing newline still arrives. Each framed\n * line is decoded and delivered through the shared {@link dispatchLines} helper — a well-formed\n * {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (never throws).\n * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line\n * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the\n * host reports the line handled rather than the moment the write is queued. The supervisor never\n * rejects — it answers `false` for a channel that was closed, destroyed, or ended, and for a write\n * that failed — so a `false` answer REJECTS here with the same not-connected error a transport\n * that was never started raises. A dead peer surfaces at the caller instead of vanishing.\n * - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once\n * (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes\n * `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of\n * its own to get its line pump back: the stream ends under the pump rather than throwing at it.\n * A line the supervisor had already framed behind the one being delivered is dropped rather than\n * emitted onto a transport whose teardown has begun. A `close()` issued while that teardown runs\n * joins it rather than opening a second one, so it resolves only after `close` has fired, and a\n * `start()` issued while it runs waits behind the same barrier, so lifetimes never overlap. A\n * descendant can retain an inherited stdout pipe after the child exits; the supervisor's `drain`\n * bound cuts that wait off, so this transport's `close()` settles within that bound rather than\n * on the descendant. The termination itself belongs to the host: a POSIX host signals the\n * child's own process group `SIGTERM`, waits the grace window, then `SIGKILL`s through the same\n * route, so the kill reaches grandchildren rather than orphaning them, while Windows ends the\n * tree with `taskkill /F /T`, which nothing in the child can intercept.\n * - **Evidence.** `evidence` reports that retained stderr tail off the HELD child — its live tail\n * while the child runs, and the value the supervisor froze at that child's terminal moment\n * afterwards. The reference is held past that moment and replaced only by the next `start()`,\n * which is what keeps a post-`close()` read stable without a private copy: the frozen value\n * never moves again, so a detached descendant writing to the inherited stderr after the cutoff\n * cannot grow it. See {@link StdioClientTransportInterface.evidence} for the readings and the\n * byte bound.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the\n * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level\n * fault, including the child spawn cause the supervisor surfaces and the notice that this\n * lifetime's `evidence` was cut off at the `drain` bound), distinct from the emitter's own\n * listener-error channel.\n *\n * @example\n * ```ts\n * const transport = new StdioClientTransport({ command: 'node', args: ['./server.js'] })\n * const client = new MCPClient({ transport })\n * await client.connect() // start() spawns the child, then the MCP initialize runs over stdio\n * ```\n */\nexport class StdioClientTransport implements StdioClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #command: string\n\treadonly #args: readonly string[]\n\treadonly #env: Readonly<Record<string, string>> | undefined\n\t#process: Process | undefined = undefined\n\t#closing: Promise<void> | undefined = undefined\n\t#closed = false\n\n\tconstructor(options: StdioClientTransportOptions) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#command = options.command\n\t\tthis.#args = options.args ?? []\n\t\tthis.#env = options.env\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// A spawned child's stdin stays writable for the process's life, so a client frame\n\t\t// reaches the peer at any moment — stdio is the transport MCP defines cancellation on.\n\t\treturn true\n\t}\n\n\tget evidence(): string | undefined {\n\t\t// The held child answers every reading: its live tail while it runs (`''` while it has\n\t\t// written nothing), and the value the supervisor FROZE at its terminal moment afterwards.\n\t\t// The frozen value never moves again, so the ended lifetime's tail stays readable off the\n\t\t// child itself and a detached descendant writing to the inherited stderr after the cutoff\n\t\t// cannot grow it. The reference is therefore held past the end of the lifetime and released\n\t\t// only when `start()` installs a replacement.\n\t\treturn this.#process?.evidence\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// A teardown still running owns the ending lifetime until it has reported `close`, so a\n\t\t// replacement WAITS on that barrier rather than racing it. This is what makes the ordering\n\t\t// rule total rather than usually right: `#closing` is assigned in the same synchronous turn\n\t\t// the `close()` or the exit that installed it runs in, and the call that installed it is the\n\t\t// only one that clears it, only after it has resolved. No child is therefore ever installed\n\t\t// while an older child's teardown is outstanding, so no interleaving can strand a `close`\n\t\t// listener on a tail the replacement already replaced.\n\t\t// The wait covers EVERY barrier this call meets, not only the first. A `close()` issued\n\t\t// while this one was resuming installs a NEWER barrier, and clearing that one would strand\n\t\t// the teardown it belongs to: a later `close()` would find no barrier, find this transport\n\t\t// already closed, and resolve through a no-op before the running teardown had reported\n\t\t// `close`. Waiting that newer barrier out keeps the same guard and leaves nothing behind —\n\t\t// a barrier still assigned when this call installs its child is one a later `close()`\n\t\t// resolves through as a no-op while that child is live.\n\t\tlet closing = this.#closing\n\t\twhile (closing !== undefined) {\n\t\t\tawait closing\n\t\t\tif (this.#closing === closing) {\n\t\t\t\tthis.#closing = undefined\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tclosing = this.#closing\n\t\t}\n\t\t// Already spawned and still open — a second `start()` (such as through `connect()`)\n\t\t// short-circuits (idempotent). An ENDED child stays held for its frozen tail, so the held\n\t\t// reference no longer answers whether a lifetime is open; this transport's own closed state\n\t\t// does, and a held ended child is exactly what a replacement replaces.\n\t\tif (this.#process !== undefined && !this.#closed) return\n\t\tthis.#closed = false\n\t\tconst child = new Process({\n\t\t\tcommand: {\n\t\t\t\tfile: this.#command,\n\t\t\t\targuments: [...this.#args],\n\t\t\t\t...(this.#env === undefined ? {} : { environment: this.#env }),\n\t\t\t},\n\t\t\tworkspace: process.cwd(),\n\t\t\tgrace: PROCESS_GRACE,\n\t\t\twritable: true,\n\t\t})\n\t\tthis.#process = child\n\t\tchild.emitter.on('error', (cause) => this.#emitter.emit('error', cause))\n\t\tvoid child.exit.then((exit) => this.#onExit(child, exit))\n\t\tvoid this.#pump(child)\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\t// A closed lifetime's child is still HELD for its tail, and a tail is not a channel: this\n\t\t// transport's own closed state is what says the channel is gone, so a write issued after a\n\t\t// `close()` or after the child's own exit reports not-connected here rather than depending\n\t\t// on the supervisor to answer for a channel it has already torn down.\n\t\tconst child = this.#closed ? undefined : this.#process\n\t\t// The supervisor's `send` never rejects: it ANSWERS `false` when the channel was closed,\n\t\t// destroyed, ended, or the write failed. Awaiting that answer is what keeps a dead peer from\n\t\t// vanishing — an unawaited call resolves this `send` before the line reaches the host.\n\t\tconst delivered = child === undefined ? false : await child.send(JSON.stringify(message))\n\t\tif (!delivered) throw new Error('stdio transport is not connected')\n\t}\n\n\tasync close(): Promise<void> {\n\t\t// A CLOSED lifetime with no barrier assigned has already reached its terminal moment and\n\t\t// reported it, so there is nothing to tear down and nothing to join: return directly. Going\n\t\t// through `??=` here would assign the resolved promise an early-returning teardown produces\n\t\t// and leave that NO-OP barrier behind, which is not inert — a `start()` a later `close`\n\t\t// listener calls waits on every barrier it meets, so it would park on the microtask queue\n\t\t// and open its replacement after the emit rather than inside it, and the natural-exit\n\t\t// restart this transport documents would stop reaching the listeners after it.\n\t\tif (this.#closed && this.#closing === undefined) return\n\t\t// Concurrent calls share ONE teardown. A second `close()` that returned early would resolve\n\t\t// before this lifetime had reached its terminal moment, and a consumer reading `evidence`\n\t\t// off that resolution would find a tail still moving under it. A barrier assigned over a\n\t\t// closed lifetime is exactly that case — an explicit teardown still running, or the report\n\t\t// barrier a natural exit holds across its `error` — so those still join it here.\n\t\tthis.#closing ??= this.#teardown()\n\t\tawait this.#closing\n\t}\n\n\t// Run the supervisor's bounded teardown and report `close` once. That teardown resolves at the\n\t// child's terminal moment, so `evidence` is frozen and `lines` has ended by the time this\n\t// resumes. The child's own exit ends the lifetime the same way.\n\tasync #teardown(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tconst child = this.#process\n\t\tif (child !== undefined) {\n\t\t\t// The child stays HELD past this point. Its frozen tail is what `evidence` answers for\n\t\t\t// the ended lifetime, and only the next `start()` replaces the reference.\n\t\t\tawait child.destroy()\n\t\t\tthis.#report(await child.exit)\n\t\t}\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Drain the supervisor's newline-framed stdout lines, decoding + delivering every complete line\n\t// onto this transport's emitter. The stream ENDS at the child's terminal moment rather than\n\t// throwing there, so this loop needs no release of its own — the supervisor's own teardown is\n\t// what releases it. A teardown that has begun and a replacement that superseded this child each\n\t// stop the dispatch before that end: the closed state is what drops a line the supervisor had\n\t// already framed behind the one being delivered, and peer identity is what keeps a stale\n\t// iteration from emitting onto a live child.\n\tasync #pump(child: Process): Promise<void> {\n\t\tfor await (const line of child.lines) {\n\t\t\tif (this.#closed || this.#process !== child) return\n\t\t\tdispatchLines(this.#emitter, [line])\n\t\t}\n\t}\n\n\t// The current child process reached its terminal moment — report a cut-off tail, then fire this\n\t// transport's `close` once. A child an explicit close superseded reports its own exit after\n\t// `start()` has installed a replacement; peer identity keeps that old exit from reporting on a\n\t// tail no reader can reach any more or emitting a second close. A teardown already reported\n\t// this lifetime, so the closed state stops the second report rather than the first.\n\t#onExit(child: Process, exit: ProcessExit): void {\n\t\tif (this.#process !== child) return\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// The report below emits SYNCHRONOUSLY, so a listener on that fault channel runs before this\n\t\t// lifetime has said it ended, and a `start()` it calls there would install its replacement\n\t\t// first: the `close` that followed would then reach every listener over a child the reader\n\t\t// can no longer see. A barrier held across the report is what orders those two — the\n\t\t// `start()` parks on it and resumes on the microtask queue, after `close` has been delivered\n\t\t// — and it is the same barrier `close()` holds across an explicit teardown's own report. A\n\t\t// `close()` called from that same listener finds this barrier through `??=` instead of\n\t\t// opening a second teardown, and resolves against it as the no-op an ended lifetime makes\n\t\t// it, after `close` has fired.\n\t\tconst barrier = Promise.withResolvers<void>()\n\t\tthis.#closing ??= barrier.promise\n\t\tthis.#report(exit)\n\t\t// Release and clear before the emit, so a `start()` called from a `close` listener finds no\n\t\t// barrier and opens the next lifetime inside that emit — the restart this transport\n\t\t// documents on a natural exit. Clear only this lifetime's own barrier: a barrier that is not\n\t\t// this one belongs to a teardown still running, and discarding it would let a later\n\t\t// `close()` resolve through a no-op before that teardown had reported its own `close`.\n\t\tbarrier.resolve()\n\t\tif (this.#closing === barrier.promise) this.#closing = undefined\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// A terminal moment the `drain` bound cut off rather than the child's own stream close leaves\n\t// `evidence` holding the tail as of that cutoff, and later diagnostics may have existed. Say so\n\t// on the fault channel, or a consumer reads a cut-off tail as the child's whole output.\n\t#report(exit: ProcessExit): void {\n\t\tif (exit.drained) return\n\t\tthis.#emitter.emit(\n\t\t\t'error',\n\t\t\tnew Error(\n\t\t\t\t'stdio transport evidence may be incomplete: the child streams stayed open past the supervisor drain bound',\n\t\t\t),\n\t\t)\n\t}\n}\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport { Readable } from 'node:stream'\nimport { Emitter } from '@orkestrel/emitter'\nimport { dispatchLines, extractLines } from '../helpers.js'\n\n/**\n * The stdio SERVER transport for the Model Context Protocol — wraps an injectable\n * readable/writable stream pair (`process.stdin`/`process.stdout` in production, a\n * test double in tests) as a {@link MCPClientTransportInterface}, the newline-delimited\n * JSON-RPC channel {@link import('../factories.js').createStdioServer} pumps\n * `mcp.dispatch` over, the stdio mirror of {@link\n * import('./WebSocketServerTransport.js').WebSocketServerTransport}.\n *\n * @remarks\n * - **Reuses `MCPClientTransportInterface`.** The same generic carrier the HTTP\n * and WebSocket server transports implement — `emitter` (`message` / `close` /\n * `error`), `start`, `send`, `close`. `session` is `undefined` (the stateless v1).\n * - **Inbound (`message`).** `start()` subscribes to `input`'s `data` event; each\n * chunk is folded through the shared {@link extractLines} line-framing helper\n * (buffering a partial trailing line across reads), and every complete line is\n * decoded and delivered through the shared {@link dispatchLines} helper — a\n * well-formed {@link JSONRPCMessage} re-emits on `message`, a malformed line\n * emits `error` (never throws). `input`'s `close` bridges to this\n * transport's `close`.\n * - **Outbound (`send`).** `send(message)` writes one newline-terminated\n * `JSON.stringify`d line to `output`.\n * - **`close()`** removes this transport's input subscriptions and fires its `close`\n * event (idempotent). It pauses the input only when the caller was not already reading\n * it at `start` (`readableFlowing !== true`) AND no `data` listener remains once this\n * transport's own is removed — so a process holding `process.stdin` can exit, and a\n * caller's own flow is never stopped underneath it. The transport preserves flowing versus\n * non-flowing state and restores every caller-owned listener. A Node stream that had never been\n * read starts with `readableFlowing === null` and is left non-flowing (`false`), because Node\n * exposes no public operation that restores `null` after data consumption starts. Attaching a\n * later `data` listener does not resume that stream; the caller must call `resume()` before the\n * listener receives data. The injected streams are owned by the caller (typically\n * `process.stdin`/`process.stdout`), so the transport never destroys, ends, or blanket-clears\n * them.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the\n * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level\n * fault), distinct from the emitter's own listener-error channel.\n */\nexport class StdioServerTransport implements MCPClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #input: NodeJS.ReadableStream\n\treadonly #output: NodeJS.WritableStream\n\treadonly #data = (chunk: Buffer | string): void => this.#receive(chunk.toString())\n\treadonly #ending = (): void => this.#onClose()\n\treadonly #failure = (error: Error): void => this.#emitter.emit('error', error)\n\t#buffer = ''\n\t#started = false\n\t#closed = false\n\t#flowing = false\n\n\tconstructor(input: NodeJS.ReadableStream, output: NodeJS.WritableStream) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#input = input\n\t\tthis.#output = output\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\t// The stateless v1 holds no session — a server-assigned id is the deferred tier.\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// The output stream stays writable for the process's life, so a frame written at any\n\t\t// moment reaches the peer — stdio is the transport MCP defines cancellation on.\n\t\treturn true\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// Arm the stream subscriptions once: an input chunk decodes to `message`, the input's\n\t\t// close bridges to this transport's `close`. Idempotent — a second `start` is a no-op\n\t\t// (the single MCPServer pump subscribes once).\n\t\tif (this.#started || this.#closed) return\n\t\tthis.#started = true\n\t\t// The input's flow state BEFORE anything is attached. `readableFlowing` is `true` only\n\t\t// for a stream a caller already put in flowing mode, so a `true` here says the caller is\n\t\t// reading and this transport is a guest on a flow it did not start. `null` (untouched)\n\t\t// and `false` (explicitly paused) both say it is not. The declared `NodeJS.ReadableStream`\n\t\t// does not carry that state, so a stream that is not a node `Readable` reads as\n\t\t// not-flowing — the same answer an untouched one gives.\n\t\tthis.#flowing = this.#input instanceof Readable && this.#input.readableFlowing === true\n\t\tthis.#input.on('data', this.#data)\n\t\tthis.#input.on('close', this.#ending)\n\t\tthis.#input.on('error', this.#failure)\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\tthis.#output.write(`${JSON.stringify(message)}\\n`)\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tthis.#release()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Buffer a raw input chunk through the shared line-framing helper, then decode + deliver\n\t// every complete line onto this transport's emitter (a partial trailing line carries\n\t// forward to the next chunk).\n\t#receive(chunk: string): void {\n\t\tconst { lines, remainder } = extractLines(this.#buffer, chunk)\n\t\tthis.#buffer = remainder\n\t\tdispatchLines(this.#emitter, lines)\n\t}\n\n\t// The input stream closed (EOF, peer teardown) — fire this transport's `close` once. No peer\n\t// identity check is needed: the input is constructor-fixed and `start()` is idempotent, so no\n\t// superseded stream can report a close against a replacement.\n\t#onClose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tthis.#release()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t#release(): void {\n\t\tthis.#input.removeListener('data', this.#data)\n\t\tthis.#input.removeListener('close', this.#ending)\n\t\tthis.#input.removeListener('error', this.#failure)\n\t\t// Different questions, asked at the moments where each is answerable: the reading\n\t\t// taken at `start` says whether the caller was already reading, and the listener count\n\t\t// taken HERE — after this transport's own `data` handler is gone — says whether a reader\n\t\t// is left. Pause only when neither is true, so a process holding `process.stdin` can exit\n\t\t// and a caller's flow is never seized. A count alone would pause a stream the caller had\n\t\t// resumed; the start reading alone would starve a reader that arrived after `start`.\n\t\tif (!this.#flowing && this.#input.listenerCount('data') === 0) this.#input.pause()\n\t}\n}\n","import type {\n\tMCPClientTransportInterface,\n\tMCPContinuationInterface,\n\tMCPDispatcherInterface,\n} from '@src/core'\nimport type { RouteInput } from '@orkestrel/router'\nimport type { TokenSecret, UpgradeHandler } from '@orkestrel/server'\nimport type {\n\tHTTPClientTransportOptions,\n\tHTTPTransportOptions,\n\tStdioClientTransportInterface,\n\tStdioClientTransportOptions,\n\tStdioServerOptions,\n\tWebSocketClientTransportOptions,\n\tWebSocketServerOptions,\n} from './types.js'\nimport type { IncomingMessage } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport { bindServer } from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { signToken, verifyToken } from '@orkestrel/server'\nimport { createNodeWebSocket, WEBSOCKET_VERSION } from '@orkestrel/websocket'\nimport { DEFAULT_MCP_PATH, MCP_WEBSOCKET_SUBPROTOCOL } from './constants.js'\nimport { createMCPPostHandler } from './handlers.js'\nimport { bridgeMessageTransport, upgradeRequestPath } from './helpers.js'\nimport { HTTPClientTransport } from './transports/HTTPClientTransport.js'\nimport { StdioClientTransport } from './transports/StdioClientTransport.js'\nimport { StdioServerTransport } from './transports/StdioServerTransport.js'\nimport { WebSocketClientTransport } from './transports/WebSocketClientTransport.js'\nimport { WebSocketServerTransport } from './transports/WebSocketServerTransport.js'\n\n/**\n * Adapts the installed server token primitives to the host-neutral MCP continuation port.\n *\n * @param secret - Current signing secret or `[current, ...older]` rotation list\n * @returns A continuation port that seals and opens opaque canonical state strings\n */\nexport function createMCPContinuation(secret: TokenSecret): MCPContinuationInterface {\n\treturn {\n\t\tseal(value) {\n\t\t\treturn signToken(value, { secret })\n\t\t},\n\t\topen(value) {\n\t\t\treturn verifyToken(value, secret)\n\t\t},\n\t}\n}\n\n/**\n * Creates the MCP Streamable-HTTP transport routes — mounts a transport-agnostic\n * {@link MCPDispatcherInterface} (the `@orkestrel/mcp` dispatch boundary) on the fetch-standard router\n * spine, pumping each `POST` body through `mcp.dispatch`. Returns the {@link RouteInput}s to\n * hand to `router.add(...)`.\n *\n * @remarks\n * A SINGLE `POST {path}` route — `createMCPRoutes` is STATELESS. The handler reads its own\n * request body (its own JSON parse try/catch), so it works with or without a session\n * middleware mounted in front. It draws a sharp line between TRANSPORT-level and\n * DISPATCH-level outcomes:\n *\n * - A **transport** failure — a malformed JSON body, or a parsed value that is not a\n * JSON-RPC INVOCATION — is an HTTP `400` carrying a JSON-RPC error BODY (`-32700` Parse\n * error / `-32600` Invalid Request), with the `id` it could not read OMITTED.\n * - Modern protocol/method/name headers are validated against the body; a mismatch is\n * HTTP `400` + `-32020`. Headerless initialize is accepted, a live legacy session supplies\n * its pinned revision, and every other headerless request is rejected.\n * - Legacy dispatch errors stay IN-BAND at HTTP `200`; modern errors map to `400` for\n * `-32020` / `-32021` / `-32022` / `-32602`, `404` for `-32601`, and `200` otherwise.\n * - A **notification** (an invocation with no `id`, which `dispatch` resolves to\n * `undefined`) is a `202 Accepted` with no body.\n *\n * When `streaming` is enabled (the default) and the client `Accept`s `text/event-stream`,\n * the `200` reply is framed as a Streamable-HTTP SSE response (one `data:` event carrying\n * the JSON-RPC envelope, then the stream ends) through `@orkestrel/server`'s generic\n * {@link import('@orkestrel/server').openStream} seam; otherwise it is a plain JSON body.\n *\n * **Sessions are a SEPARATE, plug-and-play middleware.** `createMCPRoutes` mints / reads no\n * session id. To make the transport STATEFUL, mount {@link\n * import('./middlewares.js').createMCPSession} IN FRONT — it owns the same `path`, mints +\n * validates the `mcp-session-id`, and serves the resumable `GET {path}` + `DELETE {path}`,\n * leaving this route to dispatch the validated `POST`.\n *\n * This is MECHANISM, not policy: compose auth / rate-limiting (and the session middleware)\n * IN FRONT as ordinary middleware; the optional `origin` group carries the deployment's shared\n * allowlist or explicitly delegates validation to an upstream layer.\n *\n * @typeParam TState - The consumer's opaque per-request state type\n * @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over HTTP\n * @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}) and `streaming`\n * (default `true`), plus shared origin, keepalive, and synchronous caller-extraction options; see\n * {@link HTTPTransportOptions}\n * @returns The {@link RouteInput}s to register with the router\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createMCPRoutes } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * const routes = createMCPRoutes(createMCPLegacy(mcp)) // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createMCPRoutes<TState = unknown>(\n\tmcp: MCPDispatcherInterface,\n\toptions?: HTTPTransportOptions<TState>,\n): ReadonlyArray<RouteInput<string, TState>> {\n\tconst path = options?.path ?? DEFAULT_MCP_PATH\n\tconst post: RouteInput<string, TState> = {\n\t\tmethod: 'POST',\n\t\tpath,\n\t\tname: 'mcp',\n\t\thandler: createMCPPostHandler<TState>(mcp, options),\n\t}\n\treturn [post]\n}\n\n/**\n * Creates the HTTP CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}\n * — a {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server\n * over `fetch`. The egress mirror of {@link createMCPRoutes}.\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client sends is\n * `POST`ed to `options.url` with `content-type: application/json` and an `Accept` of\n * both `application/json` and `text/event-stream` (the server answers with EITHER — a\n * plain JSON envelope or a Streamable-HTTP SSE `data:` event, decoded with `@orkestrel/sse`),\n * and the reply is surfaced on the transport's `message` event for the client's id\n * correlation. Add `options.headers` (for example, an `Authorization` bearer) to reach a guarded\n * server. `start` / `close` hold no connection; against a STATEFUL server it captures the\n * `mcp-session-id` from `initialize` and echoes it on later requests. It also captures\n * the initialize result's `protocolVersion` and sends `mcp-protocol-version` alone on each\n * subsequent legacy request. Modern requests derive protocol and method headers directly\n * from the message, plus a name header only for `tools/call`.\n *\n * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged onto\n * every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`\n * (ms, applied with `AbortSignal.timeout`); see {@link HTTPClientTransportOptions}\n * @returns A working {@link MCPClientTransportInterface} over `fetch`\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createHTTPClientTransport } from '@orkestrel/mcp/server'\n *\n * const client = createMCPClient({\n * \ttransport: createHTTPClientTransport({ url: 'http://localhost:3000/mcp' }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createHTTPClientTransport(\n\toptions: HTTPClientTransportOptions,\n): MCPClientTransportInterface {\n\treturn new HTTPClientTransport(options)\n}\n\n/**\n * Creates the MCP WebSocket transport INGRESS — an {@link UpgradeHandler} that exposes a\n * transport-agnostic {@link MCPDispatcherInterface} over a WebSocket, the WebSocket mirror of\n * {@link createMCPRoutes}. Register it on the spine's upgrade seam.\n *\n * @remarks\n * It composes the lean RFC 6455 `@orkestrel/websocket` wrapper over `@orkestrel/server`'s\n * generic upgrade seam — the spine speaks no WebSocket, this handler does.\n *\n * - **Declines (returns `false`)** when the upgrade is not for it, so the spine fans the\n * socket to the next handler (or destroys an unclaimed one): the `Upgrade` header is not\n * `websocket`, the request path is not `options.path` (default {@link DEFAULT_MCP_PATH},\n * `'/mcp'`), the `Sec-WebSocket-Key` is absent, or the `Sec-WebSocket-Version` is not `13`.\n * A decline NEVER writes to the socket (it is not yet ours) — the spine owns the unclaimed\n * outcome.\n * - **Claims (returns `true`)** otherwise: it builds `createNodeWebSocket({ socket, key, head,\n * protocol })` (SERVER mode → writes the `101` handshake, selects the configured subprotocol\n * only when the client's offer contains it, and sends UNMASKED frames), wraps it in a\n * {@link WebSocketServerTransport}, and pipes it through the core {@link\n * import('@orkestrel/mcp').MCPTransportInterface} port through {@link\n * import('./helpers.js').bridgeMessageTransport} + {@link import('@orkestrel/mcp').bindServer}:\n * each inbound REQUEST runs through `mcp.dispatch`, and a defined response is written back\n * as a frame — a NOTIFICATION sends nothing, and a non-request message (a stray response) is\n * ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than\n * escaping the (async) message pump.\n * - **Closes on the spine's `stop`.** It holds every socket it claimed and, on `options.emitter`'s\n * `stop` event, closes each one with the RFC 6455 close handshake, so the spine's drain settles\n * at once and each client reads a clean goodbye. Node detaches an upgraded socket from the\n * connection set the spine's own close walks, so the claimant is the only thing that can end\n * it: an ingress that held its sockets open would cost `stop()` the whole `drain` budget and\n * then have the connection cut mid-protocol. A socket the peer already dropped is gone from\n * the set (its transport's `close` removes it), and closing a dead one is a no-op either way.\n *\n * It is MECHANISM, not policy: compose an auth guard IN FRONT by registering an upgrade\n * handler BEFORE this one — that handler can claim (decline + destroy) an unauthenticated\n * upgrade so it never reaches this pump.\n *\n * @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over WebSocket\n * @param options - The spine's `emitter` (REQUIRED — the `stop` event this ingress closes its\n * sockets on), plus optional `path` (default {@link DEFAULT_MCP_PATH}) and `subprotocol`\n * (default {@link MCP_WEBSOCKET_SUBPROTOCOL}); see {@link WebSocketServerOptions}\n * @returns An {@link UpgradeHandler} to register with the spine's `upgrade` seam\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createWebSocketServer } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * // Claims the MCP upgrade at ws://…/mcp:\n * server.upgrade(createWebSocketServer(createMCPLegacy(mcp), { emitter: server.emitter })) // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createWebSocketServer(\n\tmcp: MCPDispatcherInterface,\n\toptions: WebSocketServerOptions,\n): UpgradeHandler {\n\tconst path = options.path ?? DEFAULT_MCP_PATH\n\tconst subprotocol = options.subprotocol ?? MCP_WEBSOCKET_SUBPROTOCOL\n\t// The connections this handler owns, each with the detachment its binding returned\n\t// — a closure store, like the session middleware's. A transport leaves on its own `close`,\n\t// so the map holds exactly the LIVE connections and exactly the bindings still attached.\n\tconst live = new Map<WebSocketServerTransport, () => void>()\n\t// The spine is stopping: detach each binding, then say the RFC 6455 goodbye on every socket\n\t// still open. Nothing else can close them — node detaches an upgraded socket from the\n\t// connection set the spine's close walks — so without this the drain runs to its deadline and\n\t// the connection is cut mid-protocol.\n\toptions.emitter.on('stop', () => {\n\t\tfor (const [transport, unbind] of live) {\n\t\t\tunbind()\n\t\t\tvoid transport.close()\n\t\t}\n\t})\n\treturn (request: IncomingMessage, socket: Duplex, head: Buffer): boolean => {\n\t\t// DECLINE anything that is not our MCP WebSocket upgrade — the spine fans it onward or\n\t\t// destroys it. Never touch the socket on a decline (it is not ours yet).\n\t\tconst upgrade = request.headers['upgrade']\n\t\tif (!isString(upgrade) || upgrade.toLowerCase() !== 'websocket') return false\n\t\tif (upgradeRequestPath(request) !== path) return false\n\t\tconst key = request.headers['sec-websocket-key']\n\t\tif (!isString(key)) return false\n\t\tconst version = request.headers['sec-websocket-version']\n\t\tif (!isString(version) || version !== WEBSOCKET_VERSION) return false\n\n\t\t// CLAIM: the wrapper writes the `101` handshake (server mode) and the transport pipes\n\t\t// through the core port: bindServer dispatches each inbound request and writes back a\n\t\t// defined response (a notification sends nothing); a dispatch / send fault surfaces on\n\t\t// `mcp.emitter`'s `error` event.\n\t\tconst offer = request.headers['sec-websocket-protocol']\n\t\tconst protocol =\n\t\t\tisString(offer) && offer.split(',').some((candidate) => candidate.trim() === subprotocol)\n\t\t\t\t? subprotocol\n\t\t\t\t: undefined\n\t\tconst ws = createNodeWebSocket({\n\t\t\tsocket,\n\t\t\tkey,\n\t\t\thead,\n\t\t\t...(protocol === undefined ? {} : { protocol }),\n\t\t})\n\t\tconst transport = new WebSocketServerTransport(ws)\n\t\t// The binder's detachment is this handler's to hold: the connection it belongs to is one\n\t\t// this factory minted and nothing outside can reach, so a discarded `unbind` leaves the\n\t\t// binding attached to a transport that has ended with no way left to detach it.\n\t\tconst unbind = bindServer(mcp, bridgeMessageTransport(transport))\n\t\tlive.set(transport, unbind)\n\t\ttransport.emitter.on('close', () => {\n\t\t\tlive.delete(transport)\n\t\t\tunbind()\n\t\t})\n\t\tvoid transport.start()\n\t\treturn true\n\t}\n}\n\n/**\n * Creates the WebSocket CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}\n * — a {@link MCPClientTransportInterface} that drives a REMOTE MCP server over a WebSocket. The\n * egress mirror of {@link createWebSocketServer} and the WebSocket sibling of {@link\n * createHTTPClientTransport}.\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`) performs\n * the RFC 6455 client handshake against `options.url` (accepting a `ws://` / `wss://` or an\n * `http://` / `https://` URL — a `ws(s)` scheme is converted to `http(s)` for the underlying\n * upgrade request), validates the `Sec-WebSocket-Accept` (with `@orkestrel/websocket`'s\n * `computeWebSocketAccept`), and opens a persistent bidirectional frame channel; each JSON-RPC\n * message the client `send`s is written as one masked text frame, and each decoded reply is\n * surfaced on the transport's `message` event for the client's id correlation. Add\n * `options.headers` (for example, an `Authorization` bearer) to reach a guarded server.\n *\n * @param options - `url` (the remote WebSocket endpoint; REQUIRED) and optional `headers`\n * merged onto the upgrade request; see {@link WebSocketClientTransportOptions}\n * @returns A working {@link MCPClientTransportInterface} over a WebSocket\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createWebSocketClientTransport } from '@orkestrel/mcp/server'\n *\n * const client = createMCPClient({\n * \ttransport: createWebSocketClientTransport({ url: 'ws://localhost:3000/mcp' }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createWebSocketClientTransport(\n\toptions: WebSocketClientTransportOptions,\n): MCPClientTransportInterface {\n\treturn new WebSocketClientTransport(options)\n}\n\n/**\n * Creates the stdio CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}\n * — a {@link StdioClientTransportInterface} that spawns and drives a CHILD PROCESS MCP server\n * over newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link\n * createHTTPClientTransport} and {@link createWebSocketClientTransport}.\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)\n * spawns `options.command` with `options.args` and `options.env`, piping its\n * `stdin`/`stdout` for the JSON-RPC channel. The child's `stderr` is piped too, and\n * retained as a bounded tail this transport reports as `evidence` — the parent never\n * inherits it. Each JSON-RPC message the client `send`s is written as one\n * newline-terminated line to the child's `stdin`; each decoded reply line from the\n * child's `stdout` is surfaced on the transport's `message` event for the client's\n * id correlation.\n *\n * @param options - `command` (the executable to spawn; REQUIRED), optional `args`,\n * and optional `env`; see {@link StdioClientTransportOptions}\n * @returns A working {@link StdioClientTransportInterface} over a child process's stdio,\n * whose `evidence` carries the supervised child's bounded stderr tail\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createStdioClientTransport } from '@orkestrel/mcp/server'\n *\n * const client = createMCPClient({\n * \ttransport: createStdioClientTransport({ command: 'node', args: ['./server.js'] }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createStdioClientTransport(\n\toptions: StdioClientTransportOptions,\n): StdioClientTransportInterface {\n\treturn new StdioClientTransport(options)\n}\n\n/**\n * Creates the MCP stdio transport INGRESS — pumps a transport-agnostic {@link\n * MCPDispatcherInterface} over newline-delimited JSON-RPC on `stdin`/`stdout` (or an\n * injected stream pair), the stdio mirror of {@link createWebSocketServer}.\n *\n * @remarks\n * Wraps `options.input` (default `process.stdin`) / `options.output` (default\n * `process.stdout`) in a {@link import('./transports/StdioServerTransport.js').StdioServerTransport}\n * and pipes it through the core {@link import('@orkestrel/mcp').MCPTransportInterface} port\n * through {@link import('./helpers.js').bridgeMessageTransport} + {@link\n * import('@orkestrel/mcp').bindServer}: each inbound REQUEST runs through `mcp.dispatch`, and\n * a defined response is written back as a newline-terminated line — a NOTIFICATION\n * writes nothing, and a non-request message is ignored. A `dispatch` / `send` fault\n * surfaces on `mcp.emitter`'s `error` event rather than escaping the (async) message\n * pump.\n *\n * @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over stdio\n * @param options - Optional injectable `input` / `output` streams; see\n * {@link StdioServerOptions}\n * @returns A `{ start(): void; stop(): void }` handle to arm / tear down the pump\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createStdioServer } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * // An MCP client now connects over this process's stdio:\n * createStdioServer(createMCPLegacy(mcp)).start() // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createStdioServer(\n\tmcp: MCPDispatcherInterface,\n\toptions?: StdioServerOptions,\n): { start(): void; stop(): void } {\n\tconst input = options?.input ?? process.stdin\n\tconst output = options?.output ?? process.stdout\n\tconst transport = new StdioServerTransport(input, output)\n\tconst unbind = bindServer(mcp, bridgeMessageTransport(transport))\n\treturn {\n\t\tstart(): void {\n\t\t\tvoid transport.start()\n\t\t},\n\t\tstop(): void {\n\t\t\tunbind()\n\t\t\tvoid transport.close()\n\t\t},\n\t}\n}\n","import type { JSONRPCMessage } from '@src/core'\nimport type { MiddlewareHandler } from '@orkestrel/server'\nimport type { MCPSessionEntry, MCPSessionOptions, MCPSessionState } from './types.js'\nimport {\n\tMCP_HEADER_MISMATCH,\n\tbuildJSONRPCError,\n\tisInitializeRequest,\n\tisModernRequest,\n\tparseJSONRPCMessage,\n} from '@src/core'\nimport { openStream } from '@orkestrel/server'\nimport {\n\tDEFAULT_MCP_PATH,\n\tMCP_PROTOCOL_VERSION_HEADER,\n\tMCP_SESSION_HEADER,\n\tSSE_BUFFERING_DISABLED,\n\tSSE_BUFFERING_HEADER,\n} from './constants.js'\nimport {\n\tallowsOrigin,\n\treadLastEventId,\n\treadSessionHeader,\n\trejectUnknownSession,\n} from './helpers.js'\nimport { inferHeaderIssue, inferLegacyVersion } from './inferers.js'\nimport { MCPSession } from './MCPSession.js'\nimport { HTTPDisconnect } from './transports/HTTPDisconnect.js'\n\n/**\n * Creates the native MCP session {@link MiddlewareHandler} — the plug-and-play stateful layer\n * that fronts a session-agnostic {@link import('./factories.js').createMCPRoutes}. Compose it\n * with `router.use(createMCPSession())` (or the equivalent middleware seam), mirroring any\n * other closure-scoped stateful middleware. Has NO dependency on `@orkestrel/middleware` — the\n * session store, mint-on-`initialize`, and resumable stream are all native to this package.\n *\n * @remarks\n * Owns a closure `Map<string, MCPSessionEntry>` keyed by session id, and a single request\n * `path` (default {@link DEFAULT_MCP_PATH}); a request to any other path passes straight\n * through (`next()`).\n *\n * A modern-shaped POST also passes straight through with `next()`, ignoring any session id.\n * The remaining behavior is the legacy session layer:\n *\n * - **`POST {path}`.** Buffers `const text = await request.text()` (so the downstream route\n * can re-read it from a freshly-built forwarded `Request`). Resolves a session through {@link\n * readSessionHeader}: a VALID id touches the entry and sets `context.state.session`; an\n * ABSENT / unknown id whose (guarded) body parses to an `initialize` request ({@link\n * isInitializeRequest}) MINTS a fresh {@link MCPSession} (`crypto.randomUUID()`, `capacity`)\n * and sets `context.state.session`; neither → {@link rejectUnknownSession} (`404`). The\n * minted entry pins the negotiated legacy revision, which is supplied to a later headerless\n * live-session request. It then\n * FORWARDS a fresh `Request` carrying the buffered `text` (`next(forwarded)`) — never the\n * already-consumed original — so the route re-reads the same body, and stamps the response\n * with {@link MCP_SESSION_HEADER}. The entry's `touched` instant is read AFTER that\n * downstream response, because it means the LAST ACCESS: a request slower than `ttl` would\n * otherwise store a session that is already expired, and the write-back RE-ASKS the store, so\n * a `DELETE` arriving while the request was suspended is not undone.\n * - **`GET {path}`.** Resolves the session the same way (no mint — only `initialize` mints);\n * an invalid / unknown id is the same `404`. A valid session opens the resumable\n * server→client stream through `@orkestrel/server`'s {@link import('@orkestrel/server').openStream}:\n * replays every event after the client's `Last-Event-ID` ({@link readLastEventId}) BEFORE\n * attaching the stream for live pushes, then attaches; cancellation of the streamed response\n * body composes with `request.signal` and detaches it. Long-lived — never `end()`ed here.\n * - **`DELETE {path}`.** Resolves the session; a valid id deletes it from the store and answers\n * `204`; an invalid / unknown id is the same `404`.\n *\n * It is MECHANISM, not policy, and ADDITIVE: omit it entirely for the stateless default\n * ({@link import('./factories.js').createMCPRoutes}'s only behavior). The `path` MUST match the\n * `createMCPRoutes` `path` it fronts. The WebSocket transport is inherently one session per\n * connection (the socket IS the session), so this middleware does not apply to it.\n *\n * @typeParam TState - The consumer's `TState`, which MUST extend {@link MCPSessionState} so\n * the resolved session can be threaded through `context.state.session`\n * @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}), `ttl` (idle-session\n * sweep window, ms — omit for sessions that live until an explicit `DELETE`), `capacity`\n * (the folded per-session replay-log bound), and `clock` (the deterministic epoch-ms clock;\n * defaults to `Date.now`), plus the shared `origin` validation options; see\n * {@link MCPSessionOptions}\n * @returns A {@link MiddlewareHandler} that mints / validates sessions + serves the resumable\n * `GET` / `DELETE`\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createMCPRoutes, createMCPSession } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * router.use(createMCPSession({ ttl: 60_000 })) // stateful: mint + validate + resumable GET / DELETE\n * // The route stays session-agnostic:\n * router.add(createMCPRoutes(createMCPLegacy(mcp))) // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createMCPSession<TState extends MCPSessionState>(\n\toptions?: MCPSessionOptions,\n): MiddlewareHandler<TState> {\n\tconst path = options?.path ?? DEFAULT_MCP_PATH\n\tconst capacity = options?.capacity\n\tconst ttl = options?.ttl\n\tconst clock = options?.clock ?? Date.now\n\tconst origin = options?.origin\n\tconst store = new Map<string, MCPSessionEntry>()\n\n\treturn async (request, context, next) => {\n\t\tif (context.url.pathname !== path) return next()\n\t\tif (!allowsOrigin(request, origin)) return new Response(null, { status: 403 })\n\t\tlet parsed: JSONRPCMessage | undefined\n\t\tlet text: string | undefined\n\t\tif (context.method === 'POST') {\n\t\t\ttry {\n\t\t\t\ttext = await request.text()\n\t\t\t\tparsed = parseJSONRPCMessage(JSON.parse(text))\n\t\t\t} catch {\n\t\t\t\tparsed = undefined\n\t\t\t}\n\t\t\tif (text !== undefined && parsed !== undefined && isModernRequest(parsed)) {\n\t\t\t\treturn next(\n\t\t\t\t\tnew Request(context.url, {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: request.headers,\n\t\t\t\t\t\tbody: text,\n\t\t\t\t\t\tsignal: request.signal,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (ttl !== undefined) {\n\t\t\tconst cutoff = clock() - ttl\n\t\t\tfor (const [id, entry] of store) {\n\t\t\t\tif (entry.touched <= cutoff) store.delete(id)\n\t\t\t}\n\t\t}\n\n\t\tif (context.method === 'DELETE') {\n\t\t\tconst id = readSessionHeader(request)\n\t\t\tif (id === undefined || !store.delete(id)) return rejectUnknownSession()\n\t\t\treturn new Response(null, { status: 204 })\n\t\t}\n\n\t\tlet entry: MCPSessionEntry | undefined\n\t\tconst id = readSessionHeader(request)\n\t\tif (id !== undefined) {\n\t\t\tconst current = store.get(id)\n\t\t\tif (current !== undefined) {\n\t\t\t\tentry = { session: current.session, touched: clock(), version: current.version }\n\t\t\t\tstore.set(id, entry)\n\t\t\t}\n\t\t}\n\n\t\tif (context.method === 'GET') {\n\t\t\tif (entry === undefined) return rejectUnknownSession()\n\t\t\tconst session = entry.session\n\t\t\tconst stream = openStream()\n\t\t\tconst disconnect = new HTTPDisconnect(request.signal, options?.keepalive)\n\t\t\tstream.response.headers.set(SSE_BUFFERING_HEADER, SSE_BUFFERING_DISABLED)\n\t\t\t// A comment write flushes the response headers immediately (the underlying node:http\n\t\t\t// response only sends headers on its first `write`/`end`) — without it a client's fetch\n\t\t\t// hangs waiting for headers until the first replay/push write, which may never come.\n\t\t\tstream.comment('open')\n\t\t\tconst lastEventId = readLastEventId(request)\n\t\t\tif (lastEventId !== undefined) {\n\t\t\t\t// Replay every event STRICTLY AFTER the client's last-seen id BEFORE attaching, so the\n\t\t\t\t// missed events arrive in order ahead of any live push.\n\t\t\t\tfor (const e of session.replay(lastEventId)) {\n\t\t\t\t\tstream.write({ id: e.id, data: JSON.stringify(e.message) })\n\t\t\t\t}\n\t\t\t}\n\t\t\tsession.attach(stream)\n\t\t\tif (disconnect.signal.aborted) session.detach(stream)\n\t\t\telse disconnect.signal.addEventListener('abort', () => session.detach(stream), { once: true })\n\t\t\treturn disconnect.bridge(stream)\n\t\t}\n\n\t\tif (context.method !== 'POST' || text === undefined) return next()\n\n\t\t// POST — only `initialize` mints a fresh session when no valid id is present.\n\t\tlet created: MCPSessionEntry | undefined\n\t\tif (entry === undefined) {\n\t\t\tif (parsed !== undefined && isInitializeRequest(parsed)) {\n\t\t\t\tconst session = new MCPSession(\n\t\t\t\t\tcrypto.randomUUID(),\n\t\t\t\t\tcapacity !== undefined ? { capacity } : {},\n\t\t\t\t)\n\t\t\t\tcreated = { session, touched: clock(), version: inferLegacyVersion(parsed) }\n\t\t\t\tentry = created\n\t\t\t} else {\n\t\t\t\treturn rejectUnknownSession()\n\t\t\t}\n\t\t}\n\t\tif (!Reflect.set(context.state, 'session', entry.session)) {\n\t\t\tthrow new Error('MCP session state is not writable')\n\t\t}\n\t\tconst headers = new Headers(request.headers)\n\t\tif (parsed === undefined || !isInitializeRequest(parsed)) {\n\t\t\tconst issue = inferHeaderIssue(request, entry.version)\n\t\t\tif (issue?.reason === 'missing') {\n\t\t\t\theaders.set(MCP_PROTOCOL_VERSION_HEADER, entry.version)\n\t\t\t} else if (issue !== undefined) {\n\t\t\t\tconst requestId = parsed !== undefined && 'method' in parsed ? parsed.id : undefined\n\t\t\t\treturn Response.json(buildJSONRPCError(requestId, MCP_HEADER_MISMATCH, issue.message), {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tconst forwarded = new Request(context.url, {\n\t\t\tmethod: 'POST',\n\t\t\theaders,\n\t\t\tbody: text,\n\t\t\tsignal: request.signal,\n\t\t})\n\t\tconst response = await next(forwarded)\n\t\tif (created !== undefined) {\n\t\t\tif (!response.ok) return response\n\t\t\t// `touched` is the instant of the LAST ACCESS, so it is read AFTER the suspension. The\n\t\t\t// mint stamp was taken before a whole `initialize` round trip that the middleware has no\n\t\t\t// bound on: a handshake slower than `ttl` used to insert a session that was already\n\t\t\t// expired, and the first request for the id this response advertises swept it away.\n\t\t\tstore.set(created.session.id, { ...created, touched: clock() })\n\t\t} else if (store.get(entry.session.id) === entry) {\n\t\t\t// The same correction on the resolved-existing path — plus a RE-ASK. `store.get` is\n\t\t\t// consulted again because a `DELETE` (or the sweep of a sibling request) may have removed\n\t\t\t// this entry while this request was suspended, and a blind write-back would resurrect a\n\t\t\t// session whose owner already ended it.\n\t\t\tstore.set(entry.session.id, { ...entry, touched: clock() })\n\t\t}\n\t\tresponse.headers.set(MCP_SESSION_HEADER, entry.session.id)\n\t\treturn response\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["#response","#lifecycle","#interval","#signal","#bridged","#timer","#pulling","#abort","#release","#emitter","#url","#headers","#fetch","#timeout","#pending","#session","#closed","#exchange","#buildHeaders","#deliver","#protocol","#capture","#id","#events","#streams","#capacity","#ttl","#append","#evict","#counter","#emitter","#socket","#frame","#receive","#ending","#onClose","#failure","#started","#closed","#release","#emitter","#url","#headers","#frame","#receive","#ending","#onClose","#failure","#socket","#closed","#connect","#httpURL","#request","#release","#bind","#emitter","#command","#args","#env","#delivery","#process","#closing","#closed","#onExit","#pump","#teardown","#report","#emitter","#input","#output","#data","#receive","#ending","#onClose","#failure","#pending","#started","#closed","#flowing","#release","#buffer"],"sources":["../../../src/server/constants.ts","../../../src/server/helpers.ts","../../../src/server/inferers.ts","../../../src/server/transports/HTTPDisconnect.ts","../../../src/server/handlers.ts","../../../src/server/transports/HTTPClientTransport.ts","../../../src/server/MCPSession.ts","../../../src/server/transports/WebSocketServerTransport.ts","../../../src/server/transports/WebSocketClientTransport.ts","../../../src/server/transports/StdioClientTransport.ts","../../../src/server/transports/StdioServerTransport.ts","../../../src/server/factories.ts","../../../src/server/middlewares.ts"],"sourcesContent":["// The MCP server-environment transport constants — the wire-level header\n// names, the default mount path, the folded event-log bounds, and the stdio client\n// transport's default write-delivery bound. The HEADER names are\n// the Streamable-HTTP transport's session /\n// protocol-version headers. `createMCPSession` owns the optional session id, while\n// `createMCPRoutes` validates a present protocol version on every POST. The\n// transport-agnostic dispatch core deliberately does NOT carry these — header names\n// belong to the HTTP transport, here.\n\n/**\n * The Streamable-HTTP transport header that carries the MCP session id. When a {@link\n * import('./middlewares.js').createMCPSession} middleware is mounted, it SETS this header on\n * the `initialize` response (the minted id) and READS it on every subsequent request\n * (validating the session); the stateless `createMCPRoutes` default neither sets nor reads it.\n */\nexport const MCP_SESSION_HEADER = 'mcp-session-id'\n\n/**\n * The Streamable-HTTP transport header carrying the negotiated MCP protocol version\n * on every post-initialize client request.\n *\n * @remarks\n * Required by MCP 2025-06-18 after initialization. Both HTTP client transports\n * capture the initialize result's `protocolVersion` and send it on subsequent\n * requests; `createMCPRoutes` rejects a present unsupported value before dispatch.\n */\nexport const MCP_PROTOCOL_VERSION_HEADER = 'mcp-protocol-version'\n\n/** The modern Streamable-HTTP request header carrying the JSON-RPC method name. */\nexport const MCP_METHOD_HEADER = 'mcp-method'\n\n/** The modern Streamable-HTTP request header carrying a named method's target. */\nexport const MCP_NAME_HEADER = 'mcp-name'\n\n/** The reverse-proxy response header controlling buffering of an SSE response. */\nexport const SSE_BUFFERING_HEADER = 'x-accel-buffering'\n\n/** The `X-Accel-Buffering` value that disables reverse-proxy buffering. */\nexport const SSE_BUFFERING_DISABLED = 'no'\n\n/** The default request path `createMCPRoutes` mounts the transport's `POST` route at. */\nexport const DEFAULT_MCP_PATH = '/mcp'\n\n/**\n * The default interval in milliseconds between SSE keepalive comments on held-open MCP\n * responses.\n *\n * @remarks\n * Fifteen seconds is infrequent enough to avoid chatty idle connections while bounding dead\n * client detection and staying comfortably inside common intermediary idle windows.\n */\nexport const DEFAULT_MCP_KEEPALIVE_INTERVAL = 15_000\n\n/** The comment text written by the held-open MCP response keepalive. */\nexport const SSE_KEEPALIVE_COMMENT = 'keepalive'\n\n/**\n * The WebSocket subprotocol the MCP-over-WebSocket transports negotiate — sent by the\n * client in `Sec-WebSocket-Protocol`, echoed by the server in its `101` handshake.\n *\n * @remarks\n * `createWebSocketServer` echoes it in the upgrade response and `createWebSocketClientTransport`\n * requests it, so an MCP WebSocket endpoint is distinguishable from any other WebSocket on the\n * same path. The default WebSocket upgrade path is {@link DEFAULT_MCP_PATH} (the same `'/mcp'`\n * the HTTP transport mounts at) — the upgrade is selected by the `Upgrade: websocket` header,\n * not a separate path.\n */\nexport const MCP_WEBSOCKET_SUBPROTOCOL = 'mcp'\n\n/**\n * The default capacity of a session's FOLDED resumable event log (the per-{@link\n * import('./MCPSession.js').MCPSession} replay log) — the maximum number of pushed\n * server→client messages retained for replay before the OLDEST is evicted.\n *\n * @remarks\n * Bounds the replay log's memory: only the most-recent {@link DEFAULT_MCP_SESSION_CAPACITY}\n * pushes are retained, so a client reconnecting with a `Last-Event-ID` older than that window\n * replays nothing (its cursor fell off the back). Override per `createMCPSession`'s `capacity`\n * for a deeper / shallower window.\n */\nexport const DEFAULT_MCP_SESSION_CAPACITY = 1024\n\n/**\n * The default per-event idle lifetime (ms) of a session's folded resumable event log — an\n * entry older than this is lazily evicted on the next access (no background timer), bounding\n * how far back a reconnecting client may replay.\n *\n * @remarks\n * Five minutes — a generous reconnection window for a dropped SSE stream without retaining\n * stale pushes indefinitely. The session's own idle TTL is the `createMCPSession` `ttl` knob;\n * this bounds the replay log paired with it.\n */\nexport const DEFAULT_MCP_SESSION_TTL = 300_000\n\n/**\n * The default bound in milliseconds on one unconfirmed write to a stdio client transport's\n * child `stdin` — the `delivery` a `createStdioClientTransport` caller who supplies none gets.\n *\n * @remarks\n * Ten seconds. The load-bearing property is the ordering, not the magnitude: this bound stays\n * BELOW {@link import('@orkestrel/mcp').DEFAULT_MCP_REQUEST_TIMEOUT}, so a write the child never\n * reads fails as an undeliverable message while the request that carried it is still open,\n * rather than being masked by that request's own deadline expiring first. Override per\n * transport with `delivery`; an explicit `0` there removes the bound.\n */\nexport const DEFAULT_MCP_DELIVERY = 10_000\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n\tMCPTransportInterface,\n} from '@src/core'\nimport type { MCPStreamControllerInterface } from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { SSEParserInterface } from '@orkestrel/sse'\nimport type { StreamInterface } from '@orkestrel/server'\nimport type { IncomingMessage } from 'node:http'\nimport type { LineExtraction, MCPOriginOptions } from './types.js'\nimport { createSSEParser } from '@orkestrel/sse'\nimport {\n\tisJSONRPCInvocation,\n\tJSONRPC_INVALID_REQUEST,\n\tbuildJSONRPCError,\n\tparseJSONRPCMessage,\n} from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { MCP_SESSION_HEADER } from './constants.js'\n\n/**\n * Creates a readable stream from its pull and cancellation behaviours.\n *\n * @param pull - The behaviour that supplies the stream's next chunk\n * @param cancel - The behaviour that releases the stream after consumer cancellation\n * @returns A readable stream backed by the supplied behaviours\n */\nexport function createReadableStream<T>(\n\tpull: (controller: ReadableStreamDefaultController<T>) => void | PromiseLike<void>,\n\tcancel: (reason?: unknown) => void | PromiseLike<void>,\n): ReadableStream<T> {\n\treturn new ReadableStream<T>({ pull, cancel })\n}\n\n/**\n * Pumps a controlled held-open exchange onto an open SSE stream — one `data:` event per\n * notification in order, then the terminating response — and END the exchange however the\n * pump leaves.\n *\n * @remarks\n * The Streamable-HTTP twin of {@link import('@orkestrel/mcp').sendStream}, and it owns exactly what\n * that owns. The `finally` releases the exchange on EVERY exit — the normal terminal, a\n * producer that threw, a `write` that threw, and an abort alike — because nothing else will:\n * a request whose client vanished cancels nothing by itself, so an exchange this pump walks\n * away from keeps its producer, its request lifetime, and its live subscription slot forever.\n * The exchange is released BEFORE the body ends, so the slot is already back when the response\n * completes.\n *\n * Total — never throws and never rejects. A held-open SSE response has already sent its\n * headers and part of its body, so there is no failure the transport could still convert into\n * a different answer; the honest end of a broken stream is a closed one, and the fault itself\n * is already legible on `server.emitter`'s `error` event, which is where a contained fault\n * belongs.\n *\n * @param stream - The controlled held-open answer to write out and then end\n * @param sse - The open SSE stream to write each serialized message onto\n * @returns Resolves once the exchange has ended and the SSE body has been closed\n *\n * @example\n * ```ts\n * const answer = await mcp.dispatch(invocation, { signal: disconnect.signal })\n * if (answer !== undefined && Symbol.asyncIterator in answer) {\n * \tconst sse = openStream()\n * \tqueueMicrotask(() => void sendEventStream(answer, sse))\n * }\n * ```\n */\nexport async function sendEventStream(\n\tstream: MCPStreamControllerInterface,\n\tsse: StreamInterface,\n): Promise<void> {\n\ttry {\n\t\t// The inner `finally` is what makes the totality claim above true of the RELEASE too:\n\t\t// a dispose that threw would otherwise escape past the outer catch that swallows the\n\t\t// pump's own faults.\n\t\ttry {\n\t\t\tlet next = await stream.next()\n\t\t\twhile (next.done !== true) {\n\t\t\t\tsse.write({ data: JSON.stringify(next.value) })\n\t\t\t\tnext = await stream.next()\n\t\t\t}\n\t\t\tsse.write({ data: JSON.stringify(next.value) })\n\t\t} finally {\n\t\t\tawait stream[Symbol.asyncDispose]()\n\t\t}\n\t} catch {\n\t\t// A producer failure, a write fault, or an abort ends this response — see @remarks.\n\t} finally {\n\t\tsse.end()\n\t}\n}\n\n// The MCP server-transport helpers — module-scope names, so they carry no entity context.\n// The server-side reader `acceptsEventStream` reads the request's `Accept` header to\n// decide whether a Streamable-HTTP SSE response is allowed; `readSessionHeader` reads the\n// request's `mcp-session-id` header (the stateful transport's session validation);\n// `readLastEventId` reads the request's `Last-Event-ID` header (the resumable GET-SSE\n// replay cursor); the CLIENT-side reader `readEventStream` decodes a `fetch` Response's SSE\n// body back into JSON-RPC messages (the egress mirror, reusing `@orkestrel/sse`'s\n// `SSEParser`); `upgradeRequestPath` reads a raw `node:http` upgrade request's path (the\n// WebSocket transport's upgrade-path match). All are total and narrow at the boundary,\n// never `as` — a missing / non-string Accept reads as \"no\", a missing session /\n// last-event header reads as `undefined`, a non-message SSE `data:` event is dropped, an\n// absent `url` reads as `'/'`.\n\n/**\n * Whether the request's `Accept` header opts into a Server-Sent-Events response.\n *\n * @remarks\n * Reads the fetch-standard `Request.headers.get('accept')` and returns `true` when it\n * contains `text/event-stream` (case-insensitive). The MCP `POST` handler uses it\n * (together with the `streaming` option) to pick the Streamable-HTTP SSE response\n * framing over a plain JSON body; the JSON-RPC envelope is identical either way. Total\n * — an absent / unmatched header returns `false`.\n *\n * @param request - The fetch-standard `Request`\n * @returns `true` when the client `Accept`s `text/event-stream`, else `false`\n */\nexport function acceptsEventStream(request: Request): boolean {\n\tconst accept = request.headers.get('accept')\n\tif (accept === null) return false\n\treturn accept.toLowerCase().includes('text/event-stream')\n}\n\n/**\n * Whether an HTTP request satisfies the endpoint's origin gate.\n *\n * @remarks\n * Validation is enabled by default. A request without `Origin` is allowed. A canonical origin\n * whose host is the `localhost` or `[::1]` literal, or belongs to the `127.0.0.0/8` literal\n * range, is allowed without configuration; every other present origin must occur exactly in\n * the caller-supplied list. Invalid and opaque (`null`) origins are denied. `enabled: false`\n * delegates validation to an upstream layer and allows the request through this gate.\n *\n * @param request - The fetch-standard request to validate\n * @param options - Shared origin validation and delegation options\n * @returns `true` when the request may reach MCP dispatch\n */\nexport function allowsOrigin(request: Request, options?: MCPOriginOptions): boolean {\n\tif (options?.enabled === false) return true\n\tconst origin = request.headers.get('origin')\n\tif (origin === null) return true\n\tlet parsed: URL\n\ttry {\n\t\tparsed = new URL(origin)\n\t} catch {\n\t\treturn false\n\t}\n\tif (parsed.origin !== origin) return false\n\tif (\n\t\tparsed.hostname === 'localhost' ||\n\t\tparsed.hostname === '[::1]' ||\n\t\t/^127(?:\\.\\d{1,3}){3}$/.test(parsed.hostname)\n\t) {\n\t\treturn true\n\t}\n\treturn options?.origins?.includes(parsed.origin) ?? false\n}\n\n/**\n * Reads the request's `mcp-session-id` header — the session id a stateful transport\n * validates, or `undefined` when absent.\n *\n * @remarks\n * Reads `request.headers.get(MCP_SESSION_HEADER)` — a fetch-standard `Headers` lookup\n * (single-valued by construction, never an array) — so a missing header reads as\n * `undefined` (no session). {@link import('./middlewares.js').createMCPSession} uses it on\n * every `POST` / `GET` / `DELETE` to look the session up in its closure store; an\n * `undefined` id is treated exactly like an unknown one (a `404`). Total — never throws.\n *\n * @param request - The fetch-standard `Request`\n * @returns The session id, or `undefined` when the header is absent\n */\nexport function readSessionHeader(request: Request): string | undefined {\n\tconst id = request.headers.get(MCP_SESSION_HEADER)\n\treturn id === null ? undefined : id\n}\n\n/**\n * Reads the request's `Last-Event-ID` header — the SSE resume cursor a client sends when it\n * reconnects to the resumable `GET {path}` stream, or `undefined` when absent.\n *\n * @remarks\n * Reads `request.headers.get('last-event-id')` — a fetch-standard `Headers` lookup — so a\n * missing header reads as `undefined` (no resume, the stream starts fresh). The resumable\n * `GET` handler in {@link import('./middlewares.js').createMCPSession} passes a present value\n * to the session's {@link import('./types.js').MCPSessionInterface.replay} to re-deliver the\n * missed events before attaching the stream for live pushes. Total — never throws.\n *\n * @param request - The fetch-standard `Request`\n * @returns The last-event-id, or `undefined` when the header is absent\n */\nexport function readLastEventId(request: Request): string | undefined {\n\tconst id = request.headers.get('last-event-id')\n\treturn id === null ? undefined : id\n}\n\n/**\n * Builds the stateful transport's \"unknown session\" rejection — an HTTP `404` carrying a\n * JSON-RPC error body.\n *\n * @remarks\n * Returns `Response.json(buildJSONRPCError(undefined, JSONRPC_INVALID_REQUEST, 'Session not\n * found'), { status: 404 })`, mirroring `createMCPRoutes`'s `400` transport-failure shape (a\n * JSON-RPC error BODY with NO id) but at the session-not-found status. Shared by\n * every {@link import('./middlewares.js').createMCPSession} validation site — the\n * non-`initialize` `POST` path, the resumable `GET {path}` open, and the `DELETE {path}`\n * session-end (each a missing / unknown / TTL-evicted id) — so the single `404` envelope\n * is defined once. Total — never throws.\n *\n * @returns The `404` JSON-RPC error `Response`\n */\nexport function rejectUnknownSession(): Response {\n\treturn Response.json(buildJSONRPCError(undefined, JSONRPC_INVALID_REQUEST, 'Session not found'), {\n\t\tstatus: 404,\n\t})\n}\n\n/**\n * Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it\n * carried — the CLIENT-side inverse of the server's Streamable-HTTP SSE response.\n *\n * @remarks\n * Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({\n * stream: true })` (handling a multi-byte char split across reads) and `@orkestrel/sse`'s\n * {@link SSEParserInterface} (handling a partial line / in-progress event split across\n * reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage} with\n * `parseJSONRPCMessage` (so a non-message / non-JSON `data:` event is DROPPED, never\n * thrown — total). It reuses the SAME `SSEParser` the server's `openStream` seam\n * serializes against, so the wire round-trips. A `null` body (no stream) yields no\n * messages; the {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}\n * reads a request/response SSE reply (the server sends one `data:` event then ends), so\n * this drains to completion.\n *\n * @param response - The SSE `fetch` Response to decode (its `body` is read to completion)\n * @returns Every {@link JSONRPCMessage} the stream carried, in order\n */\nexport async function readEventStream(response: Response): Promise<readonly JSONRPCMessage[]> {\n\tconst body = response.body\n\tif (body === null) return []\n\tconst reader = body.getReader()\n\tconst decoder = new TextDecoder()\n\tconst parser: SSEParserInterface = createSSEParser()\n\tconst messages: JSONRPCMessage[] = []\n\ttry {\n\t\tfor (;;) {\n\t\t\tconst { done, value } = await reader.read()\n\t\t\tif (done) break\n\t\t\tfor (const event of parser.parse(decoder.decode(value, { stream: true }))) {\n\t\t\t\t// JSON-parse the event's `data` (the JSON-RPC envelope the server wrote), then\n\t\t\t\t// narrow it — a malformed / non-message payload is dropped, never thrown.\n\t\t\t\tconst message = decodeEvent(event.data)\n\t\t\t\tif (message !== undefined) messages.push(message)\n\t\t\t}\n\t\t}\n\t} finally {\n\t\treader.releaseLock()\n\t}\n\treturn messages\n}\n\n/**\n * Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`\n * when it is not one — the per-event step {@link readEventStream} folds over.\n *\n * @remarks\n * `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the event's\n * `data`) inside a try/catch and narrows the parsed value with `parseJSONRPCMessage`.\n * Total: malformed JSON or a non-message value yields `undefined`, never throws.\n *\n * @param data - One SSE event's `data` payload\n * @returns The decoded {@link JSONRPCMessage}, or `undefined`\n */\nexport function decodeEvent(data: string): JSONRPCMessage | undefined {\n\ttry {\n\t\treturn parseJSONRPCMessage(JSON.parse(data))\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/**\n * Reads the path (without the query string) of a raw `node:http` protocol-upgrade request —\n * the `createWebSocketServer` upgrade-path match.\n *\n * @remarks\n * A `node:http` {@link import('node:http').IncomingMessage}'s `url` is the request TARGET\n * (`'/mcp?x=1'`), narrowed with `isString` (never `as`) and defaulting to `'/'` for an\n * absent target; it is parsed against a placeholder base (only the pathname matters for the upgrade\n * decision) and the `pathname` returned. The upgrade handler compares this against its\n * configured `path` to decide whether to claim the socket. Total — never throws on an\n * adversarial / absent target.\n *\n * @param request - The raw upgrade {@link import('node:http').IncomingMessage}\n * @returns The request's path (the `pathname`, no query), or `'/'` when the target is absent\n */\nexport function upgradeRequestPath(request: IncomingMessage): string {\n\tconst target = isString(request.url) ? request.url : '/'\n\treturn new URL(target, 'http://localhost').pathname\n}\n\n/**\n * Folds one more chunk of raw stdio bytes into a newline-framed buffer — the shared\n * line-framing step both stdio transports (client and server) read their inbound\n * newline-delimited JSON-RPC messages through.\n *\n * @remarks\n * Concatenates `buffer` (the carried-forward partial line from the previous call)\n * with `chunk`, splits on `'\\n'`, and returns every COMPLETE line (a `'\\r'` trailing\n * a line, from a CRLF-framed peer, is trimmed) plus the final, possibly-empty\n * fragment as the new `remainder` — the caller threads it back in as the next call's\n * `buffer`. A chunk containing no `'\\n'` yields no lines and the whole (buffer +\n * chunk) as `remainder`. Pure — no I/O, no instance state.\n *\n * @param buffer - The partial line carried forward from the previous chunk (`''` initially)\n * @param chunk - The newly-read raw bytes (already decoded to a string)\n * @returns The complete `lines` extracted (in order) and the trailing `remainder`\n */\nexport function extractLines(buffer: string, chunk: string): LineExtraction {\n\tconst combined = buffer + chunk\n\tconst parts = combined.split('\\n')\n\tconst remainder = parts[parts.length - 1] ?? ''\n\tconst lines = parts.slice(0, -1).map((line) => (line.endsWith('\\r') ? line.slice(0, -1) : line))\n\treturn { lines, remainder }\n}\n\n/**\n * Writes one line to a Node writable stream and waits for its completion callback.\n *\n * @remarks\n * The completion callback is the writable channel's backpressure boundary. A callback error and\n * a synchronous `write` throw reject the returned promise with the original value.\n *\n * @param output - The writable stream that receives the line\n * @param line - The complete line to write\n * @returns Resolves when the stream confirms the write; rejects when the write fails\n *\n * @example\n * ```ts\n * await writeLine(process.stdout, '{\"jsonrpc\":\"2.0\",\"method\":\"ping\"}\\n')\n * ```\n */\nexport function writeLine(output: NodeJS.WritableStream, line: string): Promise<void> {\n\treturn new Promise<void>((resolve, reject) => {\n\t\ttry {\n\t\t\toutput.write(line, (error) => {\n\t\t\t\tif (error === undefined || error === null) resolve()\n\t\t\t\telse reject(error)\n\t\t\t})\n\t\t} catch (error) {\n\t\t\treject(error)\n\t\t}\n\t})\n}\n\n/**\n * Decodes and delivers each complete newline-framed line onto a {@link\n * MCPClientTransportEventMap} emitter — the shared per-chunk dispatch step both stdio\n * transports run their framed lines through: the server transport frames with {@link\n * extractLines}, the client transport takes its lines from the process supervisor.\n *\n * @remarks\n * A blank line is skipped (a stray trailing newline). Every other line is decoded\n * with {@link decodeEvent} (`JSON.parse` + `parseJSONRPCMessage`, guarded); a\n * well-formed {@link JSONRPCMessage} emits `message`, a malformed / non-message line\n * emits `error` (total, never throws). Pure w.r.t. its own state — the emit is\n * the caller-owned side effect.\n *\n * @param emitter - The transport's {@link EmitterInterface} to emit `message` / `error` onto\n * @param lines - The complete lines to decode and deliver\n */\nexport function dispatchLines(\n\temitter: EmitterInterface<MCPClientTransportEventMap>,\n\tlines: readonly string[],\n): void {\n\tfor (const line of lines) {\n\t\tif (line.length === 0) continue\n\t\tconst message = decodeEvent(line)\n\t\tif (message === undefined) {\n\t\t\temitter.emit('error', new Error('non-JSON-RPC stdio line'))\n\t\t\tcontinue\n\t\t}\n\t\temitter.emit('message', message)\n\t}\n}\n\n/**\n * Bridges a message-channel {@link MCPClientTransportInterface} (the shape the stdio and\n * WebSocket SERVER transports already implement) into the environment-agnostic\n * {@link import('@orkestrel/mcp').MCPTransportInterface} port — the adapter\n * {@link import('./factories.js').createStdioServer} and {@link\n * import('./factories.js').createWebSocketServer} pipe through `bindServer`, so the\n * request/reply/error pump those factories used to hand-roll identically now\n * lives ONCE in the core binder.\n *\n * @remarks\n * `send` decodes the already-serialized reply string back to a {@link JSONRPCMessage}\n * and writes it through `transport.send` (the same `JSON.stringify` the underlying\n * transport already performs, so the wire bytes are unchanged). `listen` filters\n * `transport`'s `message` event to INVOCATIONS ONLY — requests and notifications, never a\n * stray response, exactly as the prior hand-rolled pumps did — and re-serializes each one\n * back to a string for `bindServer`. `closed` bridges `transport`'s `close` event. `close`\n * closes the underlying `transport`.\n *\n * @remarks A message crossing this bridge is decoded and re-encoded TWICE, and that is\n * ACCEPTED rather than accidental. Inbound: the carrier already parsed the frame into a\n * {@link JSONRPCMessage}, and `listen` re-serializes it so `bindServer` can decode it again\n * under the server's own `limit`. Outbound: `bindServer` serialized the reply, `send` parses\n * it back, and the carrier stringifies it once more. The cost is two extra `JSON.parse` /\n * `JSON.stringify` round trips per message, paid to keep ONE pump in the core binder instead\n * of a hand-rolled one per carrier. It is BOUNDED rather than unbounded because the binder\n * decodes within `server.limit.message`, so an oversized frame is refused before the second\n * decode rather than after it. Removing the cost means giving `MCPTransportInterface` a\n * message-shaped face beside its string one, which every transport would then carry.\n *\n * @remarks Per {@link import('@orkestrel/mcp').MCPTransportInterface}, `listen`/`closed`\n * each hold THE SINGLE current handler (a second call REPLACES the first, never adds).\n * Because the underlying `transport.emitter` is ADD-based (`on` subscribes, never\n * replaces), this bridge installs ONE stable emitter listener per event on first use\n * and re-routes it to whichever handler is active (`undefined` while\n * none is), so rebinding never double-dispatches.\n *\n * @remarks A response whose `result` serializes away (for example, `undefined`) is dropped by\n * the message validators on the wire's decode side — an asymmetry the stdio/WS carrier\n * shares with the streamable-HTTP face, because both round-trip through `JSON.stringify`\n * / `JSON.parse` before re-validation.\n *\n * @param transport - The message-channel transport to bridge (stdio or WebSocket)\n * @returns An {@link import('@orkestrel/mcp').MCPTransportInterface} `bindServer` can drive\n *\n * @example\n * ```ts\n * import { bindServer } from '@orkestrel/mcp'\n *\n * const transport = new StdioServerTransport(process.stdin, process.stdout)\n * bindServer(mcp, bridgeMessageTransport(transport))\n * ```\n */\nexport function bridgeMessageTransport(\n\ttransport: MCPClientTransportInterface,\n): MCPTransportInterface {\n\tlet onMessage: ((message: string) => void) | undefined\n\tlet onClosed: (() => void) | undefined\n\ttransport.emitter.on('message', (message) => {\n\t\tif (!isJSONRPCInvocation(message)) return\n\t\tonMessage?.(JSON.stringify(message))\n\t})\n\ttransport.emitter.on('close', () => {\n\t\tonClosed?.()\n\t})\n\treturn {\n\t\tasync send(message) {\n\t\t\tconst decoded = decodeEvent(message)\n\t\t\tif (decoded === undefined) return\n\t\t\tawait transport.send(decoded)\n\t\t},\n\t\tlisten(handler) {\n\t\t\tonMessage = handler\n\t\t},\n\t\tclosed(handler) {\n\t\t\tonClosed = handler\n\t\t},\n\t\tasync close() {\n\t\t\tawait transport.close()\n\t\t},\n\t}\n}\n","import type { JSONRPCInvocation, JSONRPCResponse, MCPEra, MCPVersion } from '@src/core'\nimport type { MCPHeaderIssue } from './types.js'\nimport {\n\tJSONRPC_INVALID_PARAMS,\n\tJSONRPC_METHOD_NOT_FOUND,\n\tMCP_HEADER_MISMATCH,\n\tMCP_META_VERSION,\n\tMCP_MISSING_CAPABILITY,\n\tMCP_PROTOCOL_VERSION,\n\tMCP_UNSUPPORTED_VERSION,\n\tinferEra,\n\tinferVersion,\n\tisInitializeRequest,\n\tisModernRequest,\n} from '@src/core'\nimport { isRecord, isString } from '@orkestrel/contract'\nimport { MCP_METHOD_HEADER, MCP_NAME_HEADER, MCP_PROTOCOL_VERSION_HEADER } from './constants.js'\n\n/**\n * Infers the first required MCP HTTP header that is missing or mismatched.\n *\n * @remarks\n * A modern request derives its protocol, method, and tools/call-only name expectations from\n * the JSON-RPC body. A legacy request body requires a protocol header after initialization,\n * while a supplied legacy session version additionally diagnoses a header that disagrees with\n * the active session. Messages name the expected value but never echo the client-supplied one.\n *\n * @param request - The HTTP request carrying the headers\n * @param reference - The parsed invocation body, or the active legacy session version\n * @returns The first header issue, or `undefined` when the applicable headers agree\n *\n * @example\n * ```ts\n * const issue = inferHeaderIssue(request, rpcRequest)\n * issue?.header // 'Mcp-Method' when that field is absent or mismatched\n * ```\n */\nexport function inferHeaderIssue(\n\trequest: Request,\n\treference: JSONRPCInvocation | MCPVersion,\n): MCPHeaderIssue | undefined {\n\tconst protocol = request.headers.get(MCP_PROTOCOL_VERSION_HEADER)\n\tif (isString(reference)) {\n\t\tif (protocol === null) {\n\t\t\treturn {\n\t\t\t\theader: 'MCP-Protocol-Version',\n\t\t\t\treason: 'missing',\n\t\t\t\tmessage: `Required MCP-Protocol-Version header is missing; the active session uses '${reference}'.`,\n\t\t\t}\n\t\t}\n\t\tif (protocol !== reference) {\n\t\t\treturn {\n\t\t\t\theader: 'MCP-Protocol-Version',\n\t\t\t\treason: 'mismatched',\n\t\t\t\tmessage: `MCP-Protocol-Version header does not match the active session version '${reference}'.`,\n\t\t\t}\n\t\t}\n\t\treturn undefined\n\t}\n\tif (!isModernRequest(reference)) {\n\t\tif (isInitializeRequest(reference) || protocol !== null) return undefined\n\t\treturn {\n\t\t\theader: 'MCP-Protocol-Version',\n\t\t\treason: 'missing',\n\t\t\tmessage: `Required MCP-Protocol-Version header is missing; this server offers '${MCP_PROTOCOL_VERSION}'.`,\n\t\t}\n\t}\n\tconst message = reference\n\tconst metadata = isRecord(message.params?.['_meta']) ? message.params['_meta'] : undefined\n\tconst version = metadata?.[MCP_META_VERSION]\n\tif (!isString(version)) return undefined\n\tif (protocol === null) {\n\t\treturn {\n\t\t\theader: 'MCP-Protocol-Version',\n\t\t\treason: 'missing',\n\t\t\tmessage: `Required MCP-Protocol-Version header is missing; the request body version is '${version}'.`,\n\t\t}\n\t}\n\tif (protocol !== version) {\n\t\treturn {\n\t\t\theader: 'MCP-Protocol-Version',\n\t\t\treason: 'mismatched',\n\t\t\tmessage: `MCP-Protocol-Version header does not match the request body version '${version}'.`,\n\t\t}\n\t}\n\tconst method = request.headers.get(MCP_METHOD_HEADER)\n\tif (method === null) {\n\t\treturn {\n\t\t\theader: 'Mcp-Method',\n\t\t\treason: 'missing',\n\t\t\tmessage: `Required Mcp-Method header is missing; the request body method is '${message.method}'.`,\n\t\t}\n\t}\n\tif (method !== message.method) {\n\t\treturn {\n\t\t\theader: 'Mcp-Method',\n\t\t\treason: 'mismatched',\n\t\t\tmessage: `Mcp-Method header does not match the request body method '${message.method}'.`,\n\t\t}\n\t}\n\tif (message.method !== 'tools/call') return undefined\n\tconst name = message.params?.['name']\n\tif (!isString(name)) return undefined\n\tconst header = request.headers.get(MCP_NAME_HEADER)\n\tif (header === null) {\n\t\treturn {\n\t\t\theader: 'Mcp-Name',\n\t\t\treason: 'missing',\n\t\t\tmessage: `Required Mcp-Name header is missing; the request body tool name is '${name}'.`,\n\t\t}\n\t}\n\tif (header !== name) {\n\t\treturn {\n\t\t\theader: 'Mcp-Name',\n\t\t\treason: 'mismatched',\n\t\t\tmessage: `Mcp-Name header does not match the request body tool name '${name}'.`,\n\t\t}\n\t}\n\treturn undefined\n}\n\n/**\n * Infers the legacy revision an `initialize` request negotiates.\n *\n * @remarks\n * A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported\n * request selects the newest supported legacy revision, matching the core initialize result.\n *\n * @param request - The legacy initialize invocation\n * @returns The negotiated legacy protocol revision\n */\nexport function inferLegacyVersion(request: JSONRPCInvocation): MCPVersion {\n\tconst requested = request.params?.['protocolVersion']\n\tconst version = inferVersion(isString(requested) ? [requested] : [])\n\tif (version !== undefined && inferEra(version) === 'legacy') return version\n\treturn MCP_PROTOCOL_VERSION\n}\n\n/**\n * Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.\n *\n * @remarks\n * Notifications are accepted with `202`. Legacy response envelopes retain uniform `200`\n * status semantics, including in-band errors. Modern header/capability/version/parameter\n * failures map to `400`, method-not-found maps to `404`, and every other modern result maps\n * to `200`.\n *\n * @param response - The dispatch response, or `undefined` for a notification\n * @param era - The structurally selected request era\n * @returns The HTTP response status\n */\nexport function inferStatus(response: JSONRPCResponse | undefined, era: MCPEra): number {\n\tif (response === undefined) return 202\n\tif (era === 'legacy' || response.error === undefined) return 200\n\tif (response.error.code === JSONRPC_METHOD_NOT_FOUND) return 404\n\tif (\n\t\tresponse.error.code === MCP_HEADER_MISMATCH ||\n\t\tresponse.error.code === MCP_MISSING_CAPABILITY ||\n\t\tresponse.error.code === MCP_UNSUPPORTED_VERSION ||\n\t\tresponse.error.code === JSONRPC_INVALID_PARAMS\n\t) {\n\t\treturn 400\n\t}\n\treturn 200\n}\n","import type { StreamInterface } from '@orkestrel/server'\nimport type { MCPKeepaliveOptions } from '../types.js'\nimport { sanitizeBudget } from '@orkestrel/contract'\nimport { DEFAULT_MCP_KEEPALIVE_INTERVAL, SSE_KEEPALIVE_COMMENT } from '../constants.js'\nimport { createReadableStream } from '../helpers.js'\n\n/**\n * Composes one incoming HTTP request lifetime with one MCP-owned SSE response lifetime.\n *\n * @remarks\n * The composed {@link signal} observes request abort and EVERY way this response can end\n * without one: consumer cancellation of the bridged body, a forwarding failure mid-pump, and a\n * keepalive tick that finds the SSE stream already closed. That last pair is the whole point of\n * the composition — a client that vanishes mid-stream aborts nothing by itself, so unless this\n * object raises the signal on its own failure paths, the handler, the controlled stream, and\n * the producer behind them all keep running for a response that can no longer be written.\n * Graceful upstream completion is the one terminal that does NOT abort: the body simply closes,\n * because the exchange finished rather than ended.\n *\n * {@link bridge} preserves the source response status and headers, forwards its body bytes, and\n * owns keepalive comments plus listener/timer cleanup until upstream completion, request abort,\n * or consumer cancellation. This is a single-response lifecycle object, not a reusable bridge:\n * a second {@link bridge} call THROWS rather than arming a second keepalive over one lifecycle.\n * It supplies no handler or session policy.\n *\n * The keepalive interval is a BUDGET, sanitized like every other numeric knob in this package:\n * anything that is not a positive integer — `0`, a negative, a fractional value, `NaN`,\n * `Infinity` — falls back to {@link DEFAULT_MCP_KEEPALIVE_INTERVAL}, and a larger value clamps\n * to Node's `2_147_483_647` ms timer maximum. None may reach the platform's timer floor, where\n * an idle-liveness tick becomes the polling this package forbids everywhere else.\n *\n * @example\n * ```ts\n * import { HTTPDisconnect } from '@orkestrel/mcp/server'\n * import { openStream } from '@orkestrel/server'\n *\n * const disconnect = new HTTPDisconnect(request.signal, { interval: 15_000 })\n * const stream = openStream()\n * const response = disconnect.bridge(stream)\n * ```\n */\nexport class HTTPDisconnect {\n\treadonly #response = new AbortController()\n\treadonly #lifecycle = new AbortController()\n\treadonly #interval: number\n\treadonly #signal: AbortSignal\n\t#timer: ReturnType<typeof setInterval> | undefined\n\t#bridged = false\n\t#pulling = false\n\n\t/**\n\t * Creates the lifecycle composition for one request and its future SSE response.\n\t *\n\t * @param signal - The incoming request signal\n\t * @param options - Optional keepalive `interval` in milliseconds; an invalid value falls back\n\t * to {@link DEFAULT_MCP_KEEPALIVE_INTERVAL}, and one above Node's timer maximum clamps to it\n\t */\n\tconstructor(signal: AbortSignal, options?: MCPKeepaliveOptions) {\n\t\t// `sanitizeBudget` rejects `NaN`, `Infinity`, a negative, and a fractional value; `0` is a\n\t\t// non-negative integer so it passes, and `setInterval(fn, 0)` is the same busy loop every\n\t\t// one of those produces. A keepalive cadence is therefore bounded below at one, not zero.\n\t\tconst interval = sanitizeBudget(options?.interval, DEFAULT_MCP_KEEPALIVE_INTERVAL)\n\t\tthis.#interval =\n\t\t\tinterval > 0 ? Math.min(interval, 2_147_483_647) : DEFAULT_MCP_KEEPALIVE_INTERVAL\n\t\tthis.#signal = AbortSignal.any([signal, this.#response.signal])\n\t}\n\n\t/**\n\t * The signal aborted by the incoming request, or by any end of this response that is not\n\t * its graceful completion.\n\t *\n\t * @returns The composed lifecycle signal\n\t */\n\tget signal(): AbortSignal {\n\t\treturn this.#signal\n\t}\n\n\t/**\n\t * Bridges one open SSE response through cancellation-aware byte forwarding and keepalives.\n\t *\n\t * Consumer cancellation, a read failure while forwarding, and a keepalive tick that finds the\n\t * SSE stream already closed each abort {@link signal}; consumer cancellation also cancels the\n\t * upstream reader. Upstream completion closes the returned body without inventing an abort.\n\t * Every terminal path clears the keepalive timer and detaches the bridge-owned abort listener.\n\t *\n\t * @param stream - The open SSE stream whose response will be consumed by the HTTP writer\n\t * @returns A one-use response preserving status, status text, headers, and SSE body bytes\n\t * @throws When this disconnect has already bridged a stream, or the supplied SSE response\n\t * has no body\n\t */\n\tbridge(stream: StreamInterface): Response {\n\t\t// One disconnect composes ONE request with ONE response, and the guard is what makes that\n\t\t// enforced rather than merely documented. A second call used to overwrite `#timer`, which\n\t\t// left the first interval running with no handle able to clear it, and its own abort\n\t\t// listener registered against a `#lifecycle` the first bridge's terminal had already\n\t\t// aborted — so the second bridge carried neither cleanup. Refuse before taking the\n\t\t// reader, so the stream a mis-wired caller passed is still bridgeable elsewhere.\n\t\tif (this.#bridged) throw new Error('MCP SSE response is already bridged')\n\t\tthis.#bridged = true\n\t\tconst response = stream.response\n\t\tconst body = response.body\n\t\tif (body === null) throw new Error('MCP SSE response has no body')\n\t\tconst reader = body.getReader()\n\t\t// This timer is SSE transport liveness, not polling for producer work: an idle response\n\t\t// must write to let the HTTP writer observe a dead socket and cancel the body.\n\t\tthis.#timer = setInterval(() => {\n\t\t\tif (stream.closed) {\n\t\t\t\t// A close observed while `reader.read()` is still outstanding is the graceful\n\t\t\t\t// `end()` drain window. The read itself will release once it observes the terminal.\n\t\t\t\tif (!this.#pulling) this.#abort()\n\t\t\t} else stream.comment(SSE_KEEPALIVE_COMMENT)\n\t\t}, this.#interval)\n\t\t// The composed signal already aborted, so this listener only has to release the bridge's\n\t\t// own timer and listener — raising the signal again would be answering an event with\n\t\t// itself.\n\t\tthis.#signal.addEventListener('abort', () => this.#release(), {\n\t\t\tonce: true,\n\t\t\tsignal: this.#lifecycle.signal,\n\t\t})\n\t\tif (this.#signal.aborted) this.#release()\n\t\telse if (stream.closed) this.#abort()\n\t\treturn new Response(\n\t\t\tcreateReadableStream<Uint8Array>(\n\t\t\t\tasync (controller) => {\n\t\t\t\t\tthis.#pulling = true\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst chunk = await reader.read()\n\t\t\t\t\t\tif (chunk.done) {\n\t\t\t\t\t\t\t// The exchange FINISHED. Release the bridge without raising the signal:\n\t\t\t\t\t\t\t// an abort here would tell a handler that already answered that its\n\t\t\t\t\t\t\t// request was cancelled.\n\t\t\t\t\t\t\tthis.#release()\n\t\t\t\t\t\t\tcontroller.close()\n\t\t\t\t\t\t} else controller.enqueue(chunk.value)\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthis.#abort()\n\t\t\t\t\t\tcontroller.error(error)\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tthis.#pulling = false\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tasync (reason) => {\n\t\t\t\t\tthis.#abort()\n\t\t\t\t\tawait reader.cancel(reason)\n\t\t\t\t},\n\t\t\t),\n\t\t\t{\n\t\t\t\tstatus: response.status,\n\t\t\t\tstatusText: response.statusText,\n\t\t\t\theaders: response.headers,\n\t\t\t},\n\t\t)\n\t}\n\n\t// Give up this bridge's OWN resources — the keepalive timer and the abort listener — without\n\t// saying anything about the request. The terminal a graceful completion takes.\n\t#release(): void {\n\t\tif (this.#timer !== undefined) {\n\t\t\tclearInterval(this.#timer)\n\t\t\tthis.#timer = undefined\n\t\t}\n\t\tthis.#lifecycle.abort()\n\t}\n\n\t// End the response lifetime: release first, so the listener is already detached, then raise\n\t// the composed signal for every owner still holding it. Idempotent — `abort()` is.\n\t#abort(): void {\n\t\tthis.#release()\n\t\tthis.#response.abort()\n\t}\n}\n","import type { MCPDispatcherInterface } from '@src/core'\nimport type { RouteContext } from '@orkestrel/router'\nimport type { HTTPHandlerOptions } from './types.js'\nimport {\n\tJSONRPC_INVALID_REQUEST,\n\tJSONRPC_INVALID_PARAMS,\n\tJSONRPC_PARSE_ERROR,\n\tMCP_HEADER_MISMATCH,\n\tMCP_UNSUPPORTED_VERSION,\n\tSUPPORTED_PROTOCOL_VERSIONS,\n\tbuildJSONRPCError,\n\tisMCPVersion,\n\tisModernRequest,\n\tparseRequestContext,\n\tparseJSONRPCMessage,\n} from '@src/core'\nimport { openStream } from '@orkestrel/server'\nimport {\n\tMCP_PROTOCOL_VERSION_HEADER,\n\tSSE_BUFFERING_DISABLED,\n\tSSE_BUFFERING_HEADER,\n} from './constants.js'\nimport { acceptsEventStream, allowsOrigin, sendEventStream } from './helpers.js'\nimport { inferHeaderIssue, inferStatus } from './inferers.js'\nimport { HTTPDisconnect } from './transports/HTTPDisconnect.js'\n\n/**\n * Creates the Streamable-HTTP POST handler used by `createMCPRoutes`.\n *\n * @remarks\n * Modern requests require matching protocol/method headers and a matching name header only\n * for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is\n * accepted, while every other headerless request needs a live legacy session to supply its\n * pinned version. A present origin must occur in `origin.origins` unless validation is\n * explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy\n * errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request\n * signal with response-body cancellation and supplies the result to every dispatched modern\n * handler through `MCPDispatchOptions.signal`. After every transport validation and immediately\n * before dispatch, the optional synchronous `caller` extractor reads front-middleware state; a\n * defined value is added to `MCPDispatchOptions`, while `undefined` is omitted.\n *\n * @typeParam TState - The consumer's opaque per-request route state type\n * @param mcp - The transport-agnostic MCP dispatcher to dispatch through\n * @param options - Optional streaming, origin-validation, SSE keepalive, and caller-extraction options\n * @returns A request handler for the stateless MCP POST route\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createMCPPostHandler } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * const handler = createMCPPostHandler(createMCPLegacy(mcp), { streaming: true }) // answers `initialize` too; pass `mcp` alone for modern-only\n * await handler(new Request('http://localhost/mcp', {\n * \tmethod: 'POST',\n * \tbody: '{\"jsonrpc\":\"2.0\",\"method\":\"ping\",\"id\":1}',\n * }))\n * ```\n */\nexport function createMCPPostHandler<TState = unknown>(\n\tmcp: MCPDispatcherInterface,\n\toptions?: HTTPHandlerOptions<TState>,\n): (request: Request, context?: RouteContext<string, TState>) => Promise<Response> {\n\tconst streaming = options?.streaming ?? true\n\tconst origin = options?.origin\n\treturn async (request, context): Promise<Response> => {\n\t\tif (!allowsOrigin(request, origin)) return new Response(null, { status: 403 })\n\t\tlet text: string\n\t\ttry {\n\t\t\ttext = await request.text()\n\t\t} catch {\n\t\t\treturn Response.json(buildJSONRPCError(undefined, JSONRPC_PARSE_ERROR, 'Parse error'), {\n\t\t\t\tstatus: 400,\n\t\t\t})\n\t\t}\n\t\tlet parsed: unknown\n\t\ttry {\n\t\t\tparsed = JSON.parse(text)\n\t\t} catch {\n\t\t\treturn Response.json(buildJSONRPCError(undefined, JSONRPC_PARSE_ERROR, 'Parse error'), {\n\t\t\t\tstatus: 400,\n\t\t\t})\n\t\t}\n\t\tconst invocation = parseJSONRPCMessage(parsed)\n\t\tif (invocation === undefined || !('method' in invocation)) {\n\t\t\treturn Response.json(\n\t\t\t\tbuildJSONRPCError(undefined, JSONRPC_INVALID_REQUEST, 'Invalid Request'),\n\t\t\t\t{ status: 400 },\n\t\t\t)\n\t\t}\n\t\tconst era = isModernRequest(invocation) ? 'modern' : 'legacy'\n\t\tconst id = invocation.id\n\t\tconst protocol = request.headers.get(MCP_PROTOCOL_VERSION_HEADER)\n\t\tif (era === 'modern') {\n\t\t\tif (parseRequestContext(invocation) === undefined) {\n\t\t\t\treturn Response.json(\n\t\t\t\t\tbuildJSONRPCError(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tJSONRPC_INVALID_PARAMS,\n\t\t\t\t\t\t'Invalid params: malformed modern request metadata',\n\t\t\t\t\t),\n\t\t\t\t\t{ status: 400 },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tconst issue = inferHeaderIssue(request, invocation)\n\t\tif (issue !== undefined) {\n\t\t\treturn Response.json(buildJSONRPCError(id, MCP_HEADER_MISMATCH, issue.message), {\n\t\t\t\tstatus: 400,\n\t\t\t})\n\t\t}\n\t\tif (era === 'legacy') {\n\t\t\tif (protocol !== null && !isMCPVersion(protocol)) {\n\t\t\t\treturn Response.json(\n\t\t\t\t\tbuildJSONRPCError(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tMCP_UNSUPPORTED_VERSION,\n\t\t\t\t\t\t`Unsupported MCP protocol version '${protocol}'`,\n\t\t\t\t\t\t{ supported: SUPPORTED_PROTOCOL_VERSIONS, requested: protocol },\n\t\t\t\t\t),\n\t\t\t\t\t{ status: 400 },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tconst disconnect = new HTTPDisconnect(request.signal, options?.keepalive)\n\t\tconst caller = options?.caller?.(request, context)\n\t\tconst response = await mcp.dispatch(invocation, {\n\t\t\tsignal: disconnect.signal,\n\t\t\t...(caller === undefined ? {} : { caller }),\n\t\t})\n\t\tif (response !== undefined && Symbol.asyncIterator in response) {\n\t\t\tconst stream = openStream()\n\t\t\tstream.response.headers.set(SSE_BUFFERING_HEADER, SSE_BUFFERING_DISABLED)\n\t\t\tqueueMicrotask(() => void sendEventStream(response, stream))\n\t\t\treturn disconnect.bridge(stream)\n\t\t}\n\t\tconst status = inferStatus(response, era)\n\t\tif (response === undefined) return new Response(null, { status })\n\t\tif (status === 200 && streaming && acceptsEventStream(request)) {\n\t\t\tconst stream = openStream()\n\t\t\tstream.response.headers.set(SSE_BUFFERING_HEADER, SSE_BUFFERING_DISABLED)\n\t\t\tstream.write({ data: JSON.stringify(response) })\n\t\t\tstream.end()\n\t\t\treturn stream.response\n\t\t}\n\t\treturn Response.json(response, { status })\n\t}\n}\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { HTTPClientTransportOptions } from '../types.js'\nimport {\n\tinferRequestVersion,\n\tisJSONRPCResponse,\n\tisMCPVersion,\n\tisModernRequest,\n\tparseJSONRPCMessage,\n} from '@src/core'\nimport { isRecord, isString } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\nimport {\n\tMCP_METHOD_HEADER,\n\tMCP_NAME_HEADER,\n\tMCP_PROTOCOL_VERSION_HEADER,\n\tMCP_SESSION_HEADER,\n} from '../constants.js'\nimport { readEventStream } from '../helpers.js'\n\n/**\n * The HTTP CLIENT transport for the Model Context Protocol — a\n * {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server over\n * `fetch`, the egress mirror of the server's `createMCPRoutes`.\n *\n * @remarks\n * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized\n * message to `options.url` with `content-type: application/json` and an\n * `Accept` of BOTH `application/json` and `text/event-stream` (so the server may\n * answer with either framing) — plus any `options.headers` (for example, an `Authorization`\n * bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on\n * the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes\n * to.\n * - **Both reply framings.** A `200` with an `application/json` body is parsed with\n * `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the\n * `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} ({@link\n * readEventStream}) — the inverse of the server's `openStream` seam, so the wire\n * round-trips. A `202`\n * Accepted (a notification) carries no body and emits nothing.\n * - **Session and protocol headers.** `start()` is a no-op (a\n * request/response transport opens no long-lived connection). The\n * `mcp-session-id` response header, when a STATEFUL server sends one (on\n * `initialize`), is captured into `session` and then ECHOED as the\n * `mcp-session-id` request header on every SUBSEQUENT request — so an\n * `MCPClient` passes a stateful server's session validation. The\n * initialize result's `protocolVersion` is likewise captured, but only\n * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on\n * subsequent legacy requests. Modern requests instead derive protocol and method\n * headers from the message, plus the name header only for `tools/call`.\n * Before initialize returns, neither captured legacy header is sent.\n * `close()` clears the captured protocol so a reconnect's `initialize`\n * POST is headerless; the captured `session` persists across `close()`.\n * - **`close()` releases what is in flight.** Every `fetch` this transport still has open is\n * ABORTED, which cancels the response body a `send` is reading — an SSE reply the server\n * never ends would otherwise outlive the transport, with nothing left able to reach it. The\n * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is\n * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.\n * - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,\n * the SSE decoder) — a non-message reply is dropped, never asserted; a `fetch` /\n * decode failure surfaces on the `error` event rather than escaping `send`.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires\n * `message` per decoded reply, `error` on a fault, and `close` on `close()`.\n *\n * @example\n * ```ts\n * const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })\n * const client = new MCPClient({ transport })\n * await client.connect()\n * ```\n */\nexport class HTTPClientTransport implements MCPClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #url: string\n\treadonly #headers: Readonly<Record<string, string>>\n\treadonly #fetch: typeof fetch\n\treadonly #timeout: number | undefined\n\t// The requests on the wire, one controller each. `close` is the only thing that can\n\t// reach them: a `send` parked on a reply that never ends holds both the request and its\n\t// response reader, and no other seam this transport exposes leads back to either.\n\treadonly #pending = new Set<AbortController>()\n\t#session: string | undefined = undefined\n\t#protocol: string | undefined = undefined\n\t#closed = false\n\n\tconstructor(options: HTTPClientTransportOptions) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#url = options.url\n\t\tthis.#headers = options.headers ?? {}\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch\n\t\tthis.#timeout = options.timeout\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\treturn this.#session\n\t}\n\n\tget duplex(): boolean {\n\t\t// Streamable HTTP carries no client-initiated notification: the dated revision defines\n\t\t// none over it, and closing the response stream is the cancellation signal instead.\n\t\treturn false\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// A request/response transport opens no long-lived connection — `send` issues each\n\t\t// `fetch` on demand. There is nothing to arm; opening the next connected lifetime is all\n\t\t// this does, so a transport an earlier `close` ended sends again from here.\n\t\tthis.#closed = false\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\tconst request = new AbortController()\n\t\tthis.#pending.add(request)\n\t\ttry {\n\t\t\tawait this.#exchange(message, request.signal)\n\t\t} finally {\n\t\t\tthis.#pending.delete(request)\n\t\t}\n\t}\n\n\t// One request/response exchange under `signal`: `close` aborts it, and a `timeout` option\n\t// composes with it so whichever fires first ends the same fetch and the same body read.\n\tasync #exchange(message: JSONRPCMessage, signal: AbortSignal): Promise<void> {\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await this.#fetch(this.#url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'content-type': 'application/json',\n\t\t\t\t\taccept: 'application/json, text/event-stream',\n\t\t\t\t\t// Echo a captured session id so a STATEFUL server validates the request; before\n\t\t\t\t\t// `initialize` returns one `#session` is undefined → no header (safe for a\n\t\t\t\t\t// stateless server). A caller `headers` key still wins (merged last).\n\t\t\t\t\t...(this.#session === undefined ? {} : { [MCP_SESSION_HEADER]: this.#session }),\n\t\t\t\t\t...this.#buildHeaders(message),\n\t\t\t\t\t...this.#headers,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(message),\n\t\t\t\tsignal:\n\t\t\t\t\tthis.#timeout === undefined\n\t\t\t\t\t\t? signal\n\t\t\t\t\t\t: AbortSignal.any([signal, AbortSignal.timeout(this.#timeout)]),\n\t\t\t})\n\t\t} catch (error) {\n\t\t\t// A network-level failure (connection refused, DNS) — surface it for observation;\n\t\t\t// the client's per-request deadline still rejects the pending request.\n\t\t\tthis.#emitter.emit('error', error)\n\t\t\treturn\n\t\t}\n\t\t// Capture a server-assigned session id (a stateless server sends none) so it is echoed\n\t\t// on subsequent requests; a missing header leaves `session` unchanged.\n\t\tconst session = response.headers.get(MCP_SESSION_HEADER)\n\t\tif (session !== null) this.#session = session\n\t\tawait this.#deliver(response)\n\t}\n\n\t// Abort every request still on the wire, then clear the captured protocol before emitting\n\t// `close`, so a reconnect's `initialize` POST carries no `mcp-protocol-version` header (the\n\t// captured `session` is untouched). Idempotent: a second `close` on a transport this one\n\t// already ended releases nothing and emits nothing.\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tfor (const request of this.#pending) request.abort()\n\t\tthis.#pending.clear()\n\t\tthis.#protocol = undefined\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Modern requests announce their own protocol version, so the header is projected from the\n\t// message through the SHARED `inferRequestVersion` — the same read the server's own\n\t// expectation performs, and the same read the browser face performs. Legacy requests carry\n\t// the version captured from the `initialize` handshake instead.\n\t#buildHeaders(message: JSONRPCMessage): Readonly<Record<string, string>> {\n\t\tif (isModernRequest(message)) {\n\t\t\tconst version = inferRequestVersion(message)\n\t\t\tconst name = message.params?.['name']\n\t\t\treturn {\n\t\t\t\t...(version === undefined ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: version }),\n\t\t\t\t[MCP_METHOD_HEADER]: message.method,\n\t\t\t\t...(message.method === 'tools/call' && isString(name) ? { [MCP_NAME_HEADER]: name } : {}),\n\t\t\t}\n\t\t}\n\t\treturn this.#protocol === undefined ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: this.#protocol }\n\t}\n\n\t// Decode a reply and emit each carried message. A 202 (notification accepted) has no\n\t// body — emit nothing. An `application/json` body is one envelope; a `text/event-stream`\n\t// body is decoded with the core SSEParser (one or more `data:` events). A decode failure\n\t// surfaces on `error` rather than escaping.\n\tasync #deliver(response: Response): Promise<void> {\n\t\tif (response.status === 202) return\n\t\tconst type = response.headers.get('content-type') ?? ''\n\t\ttry {\n\t\t\tif (type.includes('text/event-stream')) {\n\t\t\t\tfor (const message of await readEventStream(response)) this.#capture(message)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (type.includes('application/json')) {\n\t\t\t\tconst message = parseJSONRPCMessage(await response.json())\n\t\t\t\tif (message !== undefined) this.#capture(message)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n\n\t// Capture the negotiated SUPPORTED protocol from the initialize result before emitting\n\t// the message, so the next request carries its required protocol-version header; any\n\t// other value (missing or unsupported) is ignored and leaves `#protocol` unchanged.\n\t#capture(message: JSONRPCMessage): void {\n\t\tif (\n\t\t\tisJSONRPCResponse(message) &&\n\t\t\tisRecord(message.result) &&\n\t\t\tisMCPVersion(message.result['protocolVersion'])\n\t\t) {\n\t\t\tthis.#protocol = message.result['protocolVersion']\n\t\t}\n\t\tthis.#emitter.emit('message', message)\n\t}\n}\n","import type { JSONRPCMessage } from '@src/core'\nimport type { StreamInterface } from '@orkestrel/server'\nimport type { EventStoreEntry, MCPSessionInterface, MCPSessionOptions } from './types.js'\nimport { DEFAULT_MCP_SESSION_CAPACITY, DEFAULT_MCP_SESSION_TTL } from './constants.js'\n\n/**\n * One MCP transport session — the per-session entity a {@link\n * import('./middlewares.js').createMCPSession} middleware owns, keyed by its `id`, carrying the\n * resumable server→client push channel with its bounded replay log FOLDED IN.\n *\n * @remarks\n * The single session entity (the old `SessionState` + `EventStore` merged): it holds the\n * session `id`, its OWN bounded, replayable log of pushed server→client messages (the\n * resumable GET-SSE channel — a private `#events` `Map` + a monotone `#counter`, with\n * `capacity` / `ttl` eviction, not a separate store), and the set of open\n * server→client SSE streams (a resumable `GET {path}` registers through `attach`, unregisters through\n * `detach` on disconnect). Still a small entity (not a record), built minimal + extensible.\n *\n * - **`push` is the server-initiated primitive.** It APPENDS the message to the log (assigning\n * a monotone base36 event id) and FANS it out to every attached stream as one `id:`-tagged\n * SSE event (`stream.write({ id, data })`). A push with NO attached stream is still logged,\n * so a client that connects (or reconnects with a `Last-Event-ID`) LATER replays it from the\n * log. A `write` to a closed stream is a safe no-op (the {@link\n * `@orkestrel/server`'s `openStream` contract), so a just-disconnected stream that\n * has not yet been `detach`ed never throws. A replayed event and the live one carry the\n * IDENTICAL id (the log assigns it once).\n *\n * - **`replay(afterId)` is strictly-after.** It returns every retained log entry whose id sorts\n * AFTER `afterId` in append order — the missed-events list the `GET {path}` handler writes\n * before attaching the stream for live pushes. The decision for an UNKNOWN / already-evicted\n * `afterId` (the client's cursor fell off the back of the capacity window, or never existed):\n * replay NOTHING. Replaying the whole retained log would re-deliver events the client never\n * lost (its cursor is OLDER than everything retained); returning `[]` lets the handler then\n * stream only the fresh pushes that follow `attach` — the spec-sane resume.\n *\n * - **Bounded, append-ordered, plain `Map`.** The log lives in ONE insertion-ordered\n * `Map<id, entry>` — insertion order IS append order IS id order, so `replay` and capacity\n * eviction both walk the map directly. NO database mirror — the log is process-local\n * transport mechanics, not durable state. `push` first drops every entry older than `ttl`\n * (lazy TTL — no background timer, the middleware's lazy-window idiom), appends, then evicts\n * the OLDEST entries until at most `capacity` remain; `replay` also runs the lazy TTL sweep\n * first, so a stale entry is never replayed.\n *\n * - **No transport coupling beyond the SSE seam.** It holds session state + the generic {@link\n * StreamInterface} handles `attach` was handed — never a raw socket, request, or response.\n * The middleware opens the stream (the spine seam) and registers it here; this class only\n * serializes a message onto the already-open streams.\n *\n * - **Injected clock.** `push` / `replay` accept an optional `now` (epoch ms), defaulting to\n * `Date.now()` — so a test drives TTL eviction with an elapsed clock rather than a real\n * timer.\n *\n * @example\n * ```ts\n * const session = new MCPSession(crypto.randomUUID())\n * session.attach(stream) // an open resumable GET-SSE stream\n * session.push({ jsonrpc: '2.0', method: 'notifications/message', params: { text: 'hi' } })\n * // → logged AND written to `stream` as an `id:`-tagged event; a reconnect replays it\n * ```\n */\nexport class MCPSession implements MCPSessionInterface {\n\treadonly #id: string\n\treadonly #events = new Map<string, EventStoreEntry>()\n\treadonly #streams = new Set<StreamInterface>()\n\treadonly #capacity: number\n\treadonly #ttl: number\n\t#counter = 0\n\n\tconstructor(id: string, options?: MCPSessionOptions) {\n\t\tthis.#id = id\n\t\tthis.#capacity = options?.capacity ?? DEFAULT_MCP_SESSION_CAPACITY\n\t\tthis.#ttl = options?.ttl ?? DEFAULT_MCP_SESSION_TTL\n\t}\n\n\tget id(): string {\n\t\treturn this.#id\n\t}\n\n\tattach(stream: StreamInterface): void {\n\t\tthis.#streams.add(stream)\n\t}\n\n\tdetach(stream: StreamInterface): void {\n\t\tthis.#streams.delete(stream)\n\t}\n\n\tpush(message: JSONRPCMessage, now = Date.now()): string {\n\t\t// Append to the log first (assigning the monotone event id), then fan the SAME id out to\n\t\t// every open stream — so a replayed event and a live one carry the identical id.\n\t\tconst id = this.#append(message, now)\n\t\tconst data = JSON.stringify(message)\n\t\tfor (const stream of this.#streams) stream.write({ id, data })\n\t\treturn id\n\t}\n\n\treplay(afterId: string, now = Date.now()): readonly EventStoreEntry[] {\n\t\tthis.#evict(now)\n\t\tconst out: EventStoreEntry[] = []\n\t\tlet found = false\n\t\tfor (const entry of this.#events.values()) {\n\t\t\t// Collect every entry STRICTLY AFTER `afterId`, in append order.\n\t\t\tif (found) out.push(entry)\n\t\t\telse if (entry.id === afterId) found = true\n\t\t}\n\t\t// An unknown / evicted `afterId` was never matched → `found` stays false → replay nothing\n\t\t// (the documented spec-sane choice; never re-deliver un-lost events).\n\t\treturn found ? out : []\n\t}\n\n\t// Append a message to the bounded replay log under a fresh monotone id, evicting stale +\n\t// over-capacity entries — the folded EventStore.append, now private to the session.\n\t#append(message: JSONRPCMessage, now: number): string {\n\t\t// Lazy TTL sweep BEFORE appending so an idle log shrinks as it is written.\n\t\tthis.#evict(now)\n\t\tthis.#counter += 1\n\t\tconst id = this.#counter.toString(36)\n\t\tthis.#events.set(id, { id, message, timestamp: now })\n\t\t// Capacity bound: drop the OLDEST entries (front of the insertion-ordered map) until at\n\t\t// most `capacity` remain — so the log is the most-recent `capacity` pushes.\n\t\twhile (this.#events.size > this.#capacity) {\n\t\t\tconst oldest = this.#events.keys().next().value\n\t\t\tif (oldest === undefined) break\n\t\t\tthis.#events.delete(oldest)\n\t\t}\n\t\treturn id\n\t}\n\n\t// Drop every entry older than the TTL. Entries are append-ordered (oldest first) and the\n\t// timestamp is monotone with insertion, so the stale run is a PREFIX — stop at the first live\n\t// entry. A non-positive ttl is treated as no expiry (nothing ever ages out by time).\n\t#evict(now: number): void {\n\t\tif (this.#ttl <= 0) return\n\t\tconst cutoff = now - this.#ttl\n\t\tfor (const [id, entry] of this.#events) {\n\t\t\tif (entry.timestamp <= cutoff) this.#events.delete(id)\n\t\t\telse break\n\t\t}\n\t}\n}\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { NodeWebSocketInterface } from '@orkestrel/websocket'\nimport { parseJSONRPCMessage } from '@src/core'\nimport { Emitter } from '@orkestrel/emitter'\n\n/**\n * The per-connection JSON-RPC-over-WebSocket SERVER bridge — wraps a\n * {@link NodeWebSocketInterface} (the RFC 6455 wire wrapper) as a\n * {@link MCPClientTransportInterface}, the bidirectional JSON-RPC message channel\n * `createWebSocketServer` pumps `mcp.dispatch` over and the egress mirror's\n * {@link import('./WebSocketClientTransport.js').WebSocketClientTransport} reuses.\n *\n * @remarks\n * - **Reuses `MCPClientTransportInterface`.** It IS the same generic carrier the HTTP\n * client transport implements — `emitter` (`message` / `close` / `error`), `start`,\n * `send`, `close` — so the WebSocket server and client both speak ONE transport contract,\n * no near-duplicate sibling interface. `session` is `undefined` (the stateless v1; a\n * session id is the deferred sessions tier). The name keeps the role explicit even though\n * the shape is shared.\n * - **Inbound (`message`).** `start()` subscribes to the socket's `message` event; each text\n * frame is `JSON.parse`d inside a try/catch and narrowed with `parseJSONRPCMessage` — a\n * well-formed {@link JSONRPCMessage} is re-emitted on this transport's `message` event (the\n * parsed envelope the {@link import('@orkestrel/mcp').MCPServerInterface} pump dispatches), while\n * a non-JSON or non-message frame is surfaced on `error` and DROPPED, never thrown. It\n * also bridges the socket's `close` → this transport's `close`, and the socket's `error`.\n * - **Outbound (`send`).** `send(message)` writes one text frame\n * (`nodeWs.send(JSON.stringify(message))`); the underlying wrapper no-ops a write on a\n * non-open socket, so a closed connection drops silently rather than throwing.\n * - **`close()`** removes the subscriptions `start()` installed on the socket, closes the\n * underlying socket (the RFC 6455 close handshake), and fires the transport's `close` event\n * (idempotent — a second `close`, or a socket-driven close, emits once). A frame that arrives\n * between that release and the peer's close echo reaches nothing: the socket-driven close path\n * releases the same way, so a closed transport is never subscribed to a live socket.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the emitter\n * isolates a listener throw (a buggy observer never corrupts the bridge). `error` is a\n * DOMAIN event (a transport-level fault), distinct from the emitter's listener-error channel.\n */\nexport class WebSocketServerTransport implements MCPClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #socket: NodeWebSocketInterface\n\t// Bound once, as fields, so `close` can remove exactly the subscriptions `start` installed:\n\t// an inline arrow is a new function on every call and can never be removed by reference.\n\treadonly #frame = (text: string): void => this.#receive(text)\n\treadonly #ending = (): void => this.#onClose()\n\treadonly #failure = (error: unknown): void => this.#emitter.emit('error', error)\n\t#started = false\n\t#closed = false\n\n\tconstructor(socket: NodeWebSocketInterface) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#socket = socket\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\t// The stateless v1 holds no session — a server-assigned id is the deferred tier.\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// A socket is bidirectional for its whole life: either side writes a frame whenever it\n\t\t// has one, with no request to attach it to.\n\t\treturn true\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// Arm the socket subscriptions once: a text frame becomes a `message`, the socket's\n\t\t// close / error bridge to this transport's events. Idempotent — a second `start` is a\n\t\t// no-op (the single MCPServer pump subscribes once).\n\t\tif (this.#started || this.#closed) return\n\t\tthis.#started = true\n\t\tthis.#socket.emitter.on('message', this.#frame)\n\t\tthis.#socket.emitter.on('close', this.#ending)\n\t\tthis.#socket.emitter.on('error', this.#failure)\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\t// The wrapper drops a write on a non-open socket, so a closed connection is a\n\t\t// silent no-op rather than a throw.\n\t\tthis.#socket.send(JSON.stringify(message))\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// Release BEFORE the close handshake: the peer has not answered yet, so a frame already\n\t\t// on the wire still decodes, and a subscription left behind would re-emit it on a\n\t\t// transport whose `close` has already fired.\n\t\tthis.#release()\n\t\tthis.#socket.close()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Decode one inbound text frame: `JSON.parse` → `parseJSONRPCMessage`. A well-formed\n\t// message re-emits on `message`; a malformed / non-message frame surfaces on `error` and\n\t// is dropped (the bridge never throws on adversarial wire input).\n\t#receive(text: string): void {\n\t\tlet parsed: unknown\n\t\ttry {\n\t\t\tparsed = JSON.parse(text)\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t\treturn\n\t\t}\n\t\tconst message = parseJSONRPCMessage(parsed)\n\t\tif (message === undefined) {\n\t\t\tthis.#emitter.emit('error', new Error('non-JSON-RPC WebSocket frame'))\n\t\t\treturn\n\t\t}\n\t\tthis.#emitter.emit('message', message)\n\t}\n\n\t// The socket closed (peer close frame, transport teardown) — fire this transport's `close`\n\t// once. No peer identity check is needed: the socket is constructor-fixed and `start()` is\n\t// idempotent, so no superseded socket can report a close against a replacement.\n\t#onClose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tthis.#release()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Hand the socket back exactly as it was found: the wrapper is owned by the ingress that\n\t// claimed the upgrade, so this transport removes its own subscriptions and touches\n\t// nothing else on it.\n\t#release(): void {\n\t\tthis.#socket.emitter.off('message', this.#frame)\n\t\tthis.#socket.emitter.off('close', this.#ending)\n\t\tthis.#socket.emitter.off('error', this.#failure)\n\t}\n}\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { NodeWebSocketInterface } from '@orkestrel/websocket'\nimport type { WebSocketClientTransportOptions } from '../types.js'\nimport type { ClientRequest, IncomingMessage } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport { randomBytes } from 'node:crypto'\nimport { request as httpRequest } from 'node:http'\nimport { request as httpsRequest } from 'node:https'\nimport { parseJSONRPCMessage } from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\nimport {\n\tcomputeWebSocketAccept,\n\tcreateNodeWebSocket,\n\tWEBSOCKET_VERSION,\n} from '@orkestrel/websocket'\nimport { MCP_WEBSOCKET_SUBPROTOCOL } from '../constants.js'\n\n/**\n * The WebSocket CLIENT transport for the Model Context Protocol — a\n * {@link MCPClientTransportInterface} that drives a REMOTE MCP server over a WebSocket, the\n * egress mirror of {@link import('./factories.js').createWebSocketServer} and the WebSocket\n * sibling of {@link import('./HTTPClientTransport.js').HTTPClientTransport}.\n *\n * @remarks\n * - **Persistent bidirectional channel (unlike the HTTP transport).** `start()` performs the\n * RFC 6455 client handshake: it opens a `node:http`(`s`) `GET` carrying `Connection: Upgrade`\n * / `Upgrade: websocket` / a random `Sec-WebSocket-Key` / `Sec-WebSocket-Version: 13` /\n * `Sec-WebSocket-Protocol: mcp` (plus any `options.headers`), awaits the client `'upgrade'`\n * event, and VALIDATES `Sec-WebSocket-Accept === computeWebSocketAccept(key)` (the D2 helper)\n * — a mismatch (or a non-`101` response, or a request error) REJECTS `start()` and the socket\n * is destroyed. On success it wraps the raw upgraded socket in `createNodeWebSocket({ socket,\n * head })` (CLIENT mode — no key → frames are MASKED per RFC 6455 §5.3) and bridges its\n * `message`.\n * - **The arriving socket is RE-ASKED for, never assumed.** `start()` suspends across that\n * connect and upgrade, so it re-checks the transport's state before installing anything: a\n * concurrent `start()` that already installed a socket, or a {@link close} that ended the\n * transport while the handshake was on the wire, both WIN — the socket that arrives late is\n * DESTROYED and never bound, so no orphan is left re-emitting frames at nobody. Both\n * `start()` calls still resolve; exactly one socket is ever bound.\n * - **Inbound (`message`).** Each decoded text frame is `JSON.parse`d (guarded) and narrowed\n * with `parseJSONRPCMessage` — a {@link JSONRPCMessage} re-emits on this transport's `message`\n * event (the reply the {@link import('@orkestrel/mcp').MCPClientInterface} correlates by `id`); a\n * non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`\n * / `error` bridge to this transport's events.\n * - **Outbound (`send`).** `send(message)` writes one masked text frame.\n * - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An\n * upgrade still on the wire is DESTROYED, so a `close()` during the handshake ends the\n * transport at once instead of waiting for a peer that may never answer — the suspended\n * `start()` resolves, because the close is the outcome its caller asked for.\n * - **URL scheme.** `options.url` accepts a `ws://` / `wss://` URL or an `http://` / `https://`\n * one; a `ws(s)` scheme is converted to `http(s)` for the underlying upgrade request (`wss`\n * → TLS through `node:https`). Either reaches the same endpoint.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); every emit\n * the emitter isolates a listener throw (a buggy observer never corrupts the transport);\n * `error` is a DOMAIN event (a transport-level fault).\n *\n * @example\n * ```ts\n * const transport = new WebSocketClientTransport({ url: 'ws://localhost:3000/mcp' })\n * const client = new MCPClient({ transport })\n * await client.connect() // start() handshakes, then the MCP initialize runs over WS frames\n * ```\n */\nexport class WebSocketClientTransport implements MCPClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #url: string\n\treadonly #headers: Readonly<Record<string, string>>\n\t// Bound once, as fields, so `close` can remove exactly the subscriptions `#bind` installed:\n\t// an inline arrow is a new function on every call and can never be removed by reference.\n\treadonly #frame = (text: string): void => this.#receive(text)\n\treadonly #ending = (): void => this.#onClose()\n\treadonly #failure = (error: unknown): void => this.#emitter.emit('error', error)\n\t#socket: NodeWebSocketInterface | undefined = undefined\n\t// The upgrade on the wire. Nothing else holds it, so a `close` during the\n\t// handshake can only cancel through this.\n\t#request: ClientRequest | undefined = undefined\n\t#closed = false\n\n\tconstructor(options: WebSocketClientTransportOptions) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#url = options.url\n\t\tthis.#headers = options.headers ?? {}\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// A socket is bidirectional for its whole life: either side writes a frame whenever it\n\t\t// has one, with no request to attach it to.\n\t\treturn true\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// Already connected — a second `connect()` short-circuits in the client, but guard here\n\t\t// too (idempotent open).\n\t\tif (this.#socket !== undefined) return\n\t\tthis.#closed = false\n\t\ttry {\n\t\t\tawait this.#connect(this.#httpURL(), randomBytes(16).toString('base64'))\n\t\t} finally {\n\t\t\t// Whatever the handshake did, it is no longer on the wire, so nothing is left for a\n\t\t\t// later `close` to cancel.\n\t\t\tthis.#request = undefined\n\t\t}\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\tconst socket = this.#socket\n\t\tif (socket === undefined) throw new Error('WebSocket transport is not connected')\n\t\tsocket.send(JSON.stringify(message))\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// The upgrade still on the wire is this transport's to cancel. Without it a close during\n\t\t// the handshake waits out the peer, and the socket that finally arrives is destroyed long\n\t\t// after the caller was told the transport had ended.\n\t\tthis.#request?.destroy()\n\t\tconst socket = this.#socket\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\tif (socket !== undefined) socket.close()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Run the RFC 6455 client handshake and bind the socket it produces. Split from `start` so\n\t// the retained request is cleared on every settlement path, including a rejection.\n\tasync #connect(url: URL, key: string): Promise<void> {\n\t\tconst secure = url.protocol === 'https:'\n\t\tconst send = secure ? httpsRequest : httpRequest\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst request = send({\n\t\t\t\thostname: url.hostname,\n\t\t\t\tport: url.port.length > 0 ? Number(url.port) : secure ? 443 : 80,\n\t\t\t\tpath: `${url.pathname}${url.search}`,\n\t\t\t\theaders: {\n\t\t\t\t\tConnection: 'Upgrade',\n\t\t\t\t\tUpgrade: 'websocket',\n\t\t\t\t\t'Sec-WebSocket-Key': key,\n\t\t\t\t\t'Sec-WebSocket-Version': WEBSOCKET_VERSION,\n\t\t\t\t\t'Sec-WebSocket-Protocol': MCP_WEBSOCKET_SUBPROTOCOL,\n\t\t\t\t\t...this.#headers,\n\t\t\t\t},\n\t\t\t})\n\t\t\tthis.#request = request\n\n\t\t\t// The server accepted the upgrade: validate the handshake accept, then wrap the\n\t\t\t// raw socket in a CLIENT-mode NodeWebSocket (masks its frames).\n\t\t\trequest.on('upgrade', (response: IncomingMessage, socket: Duplex, head: Buffer) => {\n\t\t\t\tconst accept = response.headers['sec-websocket-accept']\n\t\t\t\tif (!isString(accept) || accept !== computeWebSocketAccept(key)) {\n\t\t\t\t\tsocket.destroy()\n\t\t\t\t\treject(new Error('WebSocket handshake failed: Sec-WebSocket-Accept mismatch'))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// RE-ASK. Every state the guard at the top of `start()` read is stale by now: this\n\t\t\t\t// handshake suspended across a real TCP connect and HTTP upgrade, during which a\n\t\t\t\t// second `start()` may have installed its own socket or `close()` may have ended the\n\t\t\t\t// transport. Nobody wants this one, so DESTROY it rather than binding a second,\n\t\t\t\t// never-closed peer that keeps re-emitting frames at a transport that has moved on.\n\t\t\t\t// `start()` still resolves: the winner (or the close) is the outcome the caller asked\n\t\t\t\t// for, not a failure.\n\t\t\t\tif (this.#closed || this.#socket !== undefined) {\n\t\t\t\t\tsocket.destroy()\n\t\t\t\t\tresolve()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst ws = createNodeWebSocket({ socket, head })\n\t\t\t\tthis.#socket = ws\n\t\t\t\tthis.#bind(ws)\n\t\t\t\tresolve()\n\t\t\t})\n\n\t\t\t// A plain (non-101) response means the server declined the upgrade.\n\t\t\trequest.on('response', (response) => {\n\t\t\t\tresponse.resume()\n\t\t\t\treject(new Error(`WebSocket upgrade declined with status ${response.statusCode ?? 0}`))\n\t\t\t})\n\t\t\t// A connection-level failure (refused, DNS, reset) — or the reset that follows the\n\t\t\t// `close()` above destroying this request, which IS that close arriving rather than a\n\t\t\t// fault: the caller asked for the transport to end, and it has.\n\t\t\trequest.on('error', (error) => {\n\t\t\t\tif (this.#closed) {\n\t\t\t\t\tresolve()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treject(error instanceof Error ? error : new Error(String(error)))\n\t\t\t})\n\t\t\trequest.end()\n\t\t})\n\t}\n\n\t// Bridge the upgraded socket's events onto the transport: a text frame → `message`\n\t// (decoded + narrowed), the socket close → `close`, a socket fault → `error`.\n\t#bind(ws: NodeWebSocketInterface): void {\n\t\tws.emitter.on('message', this.#frame)\n\t\tws.emitter.on('close', this.#ending)\n\t\tws.emitter.on('error', this.#failure)\n\t}\n\n\t// Unsubscribe from the socket this transport holds. The socket itself belongs to\n\t// the peer connection, so nothing else on it is touched.\n\t#release(): void {\n\t\tconst socket = this.#socket\n\t\tif (socket === undefined) return\n\t\tsocket.emitter.off('message', this.#frame)\n\t\tsocket.emitter.off('close', this.#ending)\n\t\tsocket.emitter.off('error', this.#failure)\n\t}\n\n\t// Decode one inbound text frame: `JSON.parse` → `parseJSONRPCMessage`. A well-formed\n\t// message re-emits on `message`; a malformed / non-message frame surfaces on `error` and\n\t// is dropped (never throws on adversarial wire input).\n\t#receive(text: string): void {\n\t\tlet parsed: unknown\n\t\ttry {\n\t\t\tparsed = JSON.parse(text)\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t\treturn\n\t\t}\n\t\tconst message = parseJSONRPCMessage(parsed)\n\t\tif (message === undefined) {\n\t\t\tthis.#emitter.emit('error', new Error('non-JSON-RPC WebSocket frame'))\n\t\t\treturn\n\t\t}\n\t\tthis.#emitter.emit('message', message)\n\t}\n\n\t// The current socket closed underneath us — fire `close` once. Only the socket this\n\t// transport still holds can reach here: a superseded one was unsubscribed when it was\n\t// released, so its own later close reports to nobody and cannot clear the live socket.\n\t#onClose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Normalize `options.url` to the `http(s)` URL the underlying upgrade request uses: a\n\t// `ws://` → `http://`, a `wss://` → `https://`; an `http(s)://` URL passes through. Any\n\t// other scheme throws (a clear boundary error, not a silent mis-dial).\n\t#httpURL(): URL {\n\t\tconst url = new URL(this.#url)\n\t\tif (url.protocol === 'ws:') url.protocol = 'http:'\n\t\telse if (url.protocol === 'wss:') url.protocol = 'https:'\n\t\telse if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n\t\t\tthrow new Error(`unsupported WebSocket URL scheme '${url.protocol}'`)\n\t\t}\n\t\treturn url\n\t}\n}\n","import type { MCPClientTransportEventMap, JSONRPCMessage } from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { ProcessExit } from '@orkestrel/process'\nimport type { StdioClientTransportInterface, StdioClientTransportOptions } from '../types.js'\nimport { Process } from '@orkestrel/process/server'\nimport { PROCESS_GRACE } from '@orkestrel/process'\nimport { Emitter } from '@orkestrel/emitter'\nimport { DEFAULT_MCP_DELIVERY } from '../constants.js'\nimport { dispatchLines } from '../helpers.js'\n\n/**\n * The stdio CLIENT transport for the Model Context Protocol — a\n * {@link StdioClientTransportInterface} that drives a CHILD PROCESS MCP server over\n * newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link\n * import('./HTTPClientTransport.js').HTTPClientTransport} and {@link\n * import('./WebSocketClientTransport.js').WebSocketClientTransport}.\n *\n * @remarks\n * - **Composes `@orkestrel/process`.** `start()` builds one supervised\n * {@link import('@orkestrel/process/server').Process} with `writable: true`, so the child's\n * `stdin`/`stdout` are the JSON-RPC channel and its `stderr` is retained as bounded evidence\n * rather than parsed as protocol. The supervisor owns spawn, framing, and termination.\n * - **Inbound (`message`).** Standard output is drained eagerly through the supervisor's\n * `readline`-framed `lines` iterable, so a multi-byte UTF-8 sequence split across two reads is\n * decoded whole and a final line written without a trailing newline still arrives. Each framed\n * line is decoded and delivered through the shared {@link dispatchLines} helper — a well-formed\n * {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (never throws).\n * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line\n * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the\n * host reports the line handled rather than the moment the write is queued. The supervisor never\n * rejects — it answers `false` for a channel that was closed, destroyed, or ended, for a write\n * that failed, or for one that remained unconfirmed through `delivery`. A call made without a\n * live child rejects as not connected; a `false` answer from a live child rejects as unable to\n * deliver. The supervisor does not disclose which cause produced that answer.\n * - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once\n * (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes\n * `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of\n * its own to get its line pump back: the stream ends under the pump rather than throwing at it.\n * A line the supervisor had already framed behind the one being delivered is dropped rather than\n * emitted onto a transport whose teardown has begun. A `close()` issued while that teardown runs\n * joins it rather than opening a second one, so it resolves only after `close` has fired, and a\n * `start()` issued while it runs waits behind the same barrier, so lifetimes never overlap. A\n * descendant can retain an inherited stdout pipe after the child exits; the supervisor's `drain`\n * bound cuts that wait off, so this transport's `close()` settles within that bound rather than\n * on the descendant. The termination itself belongs to the host: a POSIX host signals the\n * child's own process group `SIGTERM`, waits the grace window, then `SIGKILL`s through the same\n * route, so the kill reaches grandchildren rather than orphaning them, while Windows ends the\n * tree with `taskkill /F /T`, which nothing in the child can intercept.\n * - **Evidence.** `evidence` reports that retained stderr tail off the HELD child — its live tail\n * while the child runs, and the value the supervisor froze at that child's terminal moment\n * afterwards. The reference is held past that moment and replaced only by the next `start()`,\n * which is what keeps a post-`close()` read stable without a private copy: the frozen value\n * never moves again, so a detached descendant writing to the inherited stderr after the cutoff\n * cannot grow it. See {@link StdioClientTransportInterface.evidence} for the readings and the\n * byte bound.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the\n * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level\n * fault, including the child spawn cause the supervisor surfaces and the notice that this\n * lifetime's `evidence` was cut off at the `drain` bound), distinct from the emitter's own\n * listener-error channel.\n *\n * @example\n * ```ts\n * const transport = new StdioClientTransport({ command: 'node', args: ['./server.js'] })\n * const client = new MCPClient({ transport })\n * await client.connect() // start() spawns the child, then the MCP initialize runs over stdio\n * ```\n */\nexport class StdioClientTransport implements StdioClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #command: string\n\treadonly #args: readonly string[]\n\treadonly #env: Readonly<Record<string, string>> | undefined\n\treadonly #delivery: number\n\t#process: Process | undefined = undefined\n\t#closing: Promise<void> | undefined = undefined\n\t#closed = false\n\n\tconstructor(options: StdioClientTransportOptions) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#command = options.command\n\t\tthis.#args = options.args ?? []\n\t\tthis.#env = options.env\n\t\tthis.#delivery = options.delivery ?? DEFAULT_MCP_DELIVERY\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// A spawned child's stdin stays writable for the process's life, so a client frame\n\t\t// reaches the peer at any moment — stdio is the transport MCP defines cancellation on.\n\t\treturn true\n\t}\n\n\tget evidence(): string | undefined {\n\t\t// The held child answers every reading: its live tail while it runs (`''` while it has\n\t\t// written nothing), and the value the supervisor FROZE at its terminal moment afterwards.\n\t\t// The frozen value never moves again, so the ended lifetime's tail stays readable off the\n\t\t// child itself and a detached descendant writing to the inherited stderr after the cutoff\n\t\t// cannot grow it. The reference is therefore held past the end of the lifetime and released\n\t\t// only when `start()` installs a replacement.\n\t\treturn this.#process?.evidence\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// A teardown still running owns the ending lifetime until it has reported `close`, so a\n\t\t// replacement WAITS on that barrier rather than racing it. This is what makes the ordering\n\t\t// rule total rather than usually right: `#closing` is assigned in the same synchronous turn\n\t\t// the `close()` or the exit that installed it runs in, and the call that installed it is the\n\t\t// only one that clears it, only after it has resolved. No child is therefore ever installed\n\t\t// while an older child's teardown is outstanding, so no interleaving can strand a `close`\n\t\t// listener on a tail the replacement already replaced.\n\t\t// The wait covers EVERY barrier this call meets, not only the first. A `close()` issued\n\t\t// while this one was resuming installs a NEWER barrier, and clearing that one would strand\n\t\t// the teardown it belongs to: a later `close()` would find no barrier, find this transport\n\t\t// already closed, and resolve through a no-op before the running teardown had reported\n\t\t// `close`. Waiting that newer barrier out keeps the same guard and leaves nothing behind —\n\t\t// a barrier still assigned when this call installs its child is one a later `close()`\n\t\t// resolves through as a no-op while that child is live.\n\t\tlet closing = this.#closing\n\t\twhile (closing !== undefined) {\n\t\t\tawait closing\n\t\t\tif (this.#closing === closing) {\n\t\t\t\tthis.#closing = undefined\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tclosing = this.#closing\n\t\t}\n\t\t// Already spawned and still open — a second `start()` (such as through `connect()`)\n\t\t// short-circuits (idempotent). An ENDED child stays held for its frozen tail, so the held\n\t\t// reference no longer answers whether a lifetime is open; this transport's own closed state\n\t\t// does, and a held ended child is exactly what a replacement replaces.\n\t\tif (this.#process !== undefined && !this.#closed) return\n\t\tthis.#closed = false\n\t\tconst child = new Process({\n\t\t\tcommand: {\n\t\t\t\tfile: this.#command,\n\t\t\t\targuments: [...this.#args],\n\t\t\t\t...(this.#env === undefined ? {} : { environment: this.#env }),\n\t\t\t},\n\t\t\tworkspace: process.cwd(),\n\t\t\tgrace: PROCESS_GRACE,\n\t\t\tdelivery: this.#delivery,\n\t\t\twritable: true,\n\t\t})\n\t\tthis.#process = child\n\t\tchild.emitter.on('error', (cause) => this.#emitter.emit('error', cause))\n\t\tvoid child.exit.then((exit) => this.#onExit(child, exit))\n\t\tvoid this.#pump(child)\n\t}\n\n\t/**\n\t * Sends one newline-delimited JSON-RPC message to the live child.\n\t *\n\t * @param message - The message to write to the child's `stdin`\n\t * @returns Resolves when the supervisor confirms the write\n\t * @throws Thrown with `stdio transport is not connected` when no live child is available before\n\t * the write\n\t * @throws Thrown with `stdio transport could not deliver the message` when a live child's write\n\t * resolves `false`\n\t */\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\t// A closed lifetime's child is still HELD for its tail, and a tail is not a channel: this\n\t\t// transport's own closed state is what says the channel is gone, so a write issued after a\n\t\t// `close()` or after the child's own exit reports not-connected here rather than depending\n\t\t// on the supervisor to answer for a channel it has already torn down.\n\t\tconst child = this.#closed ? undefined : this.#process\n\t\tif (child === undefined) throw new Error('stdio transport is not connected')\n\t\t// The supervisor's `send` never rejects: it ANSWERS `false` when the channel was closed,\n\t\t// destroyed, ended, the write failed, or the delivery bound elapsed. Awaiting that answer is\n\t\t// what keeps a dead peer from vanishing — an unawaited call resolves this `send` before the\n\t\t// line reaches the host. The answer does not disclose which cause produced it.\n\t\tconst delivered = await child.send(JSON.stringify(message))\n\t\tif (!delivered) throw new Error('stdio transport could not deliver the message')\n\t}\n\n\tasync close(): Promise<void> {\n\t\t// A CLOSED lifetime with no barrier assigned has already reached its terminal moment and\n\t\t// reported it, so there is nothing to tear down and nothing to join: return directly. Going\n\t\t// through `??=` here would assign the resolved promise an early-returning teardown produces\n\t\t// and leave that NO-OP barrier behind, which is not inert — a `start()` a later `close`\n\t\t// listener calls waits on every barrier it meets, so it would park on the microtask queue\n\t\t// and open its replacement after the emit rather than inside it, and the natural-exit\n\t\t// restart this transport documents would stop reaching the listeners after it.\n\t\tif (this.#closed && this.#closing === undefined) return\n\t\t// Concurrent calls share ONE teardown. A second `close()` that returned early would resolve\n\t\t// before this lifetime had reached its terminal moment, and a consumer reading `evidence`\n\t\t// off that resolution would find a tail still moving under it. A barrier assigned over a\n\t\t// closed lifetime is exactly that case — an explicit teardown still running, or the report\n\t\t// barrier a natural exit holds across its `error` — so those still join it here.\n\t\tthis.#closing ??= this.#teardown()\n\t\tawait this.#closing\n\t}\n\n\t// Run the supervisor's bounded teardown and report `close` once. That teardown resolves at the\n\t// child's terminal moment, so `evidence` is frozen and `lines` has ended by the time this\n\t// resumes. The child's own exit ends the lifetime the same way.\n\tasync #teardown(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tconst child = this.#process\n\t\tif (child !== undefined) {\n\t\t\t// The child stays HELD past this point. Its frozen tail is what `evidence` answers for\n\t\t\t// the ended lifetime, and only the next `start()` replaces the reference.\n\t\t\tawait child.destroy()\n\t\t\tthis.#report(await child.exit)\n\t\t}\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Drain the supervisor's newline-framed stdout lines, decoding + delivering every complete line\n\t// onto this transport's emitter. The stream ENDS at the child's terminal moment rather than\n\t// throwing there, so this loop needs no release of its own — the supervisor's own teardown is\n\t// what releases it. A teardown that has begun and a replacement that superseded this child each\n\t// stop the dispatch before that end: the closed state is what drops a line the supervisor had\n\t// already framed behind the one being delivered, and peer identity is what keeps a stale\n\t// iteration from emitting onto a live child.\n\tasync #pump(child: Process): Promise<void> {\n\t\tfor await (const line of child.lines) {\n\t\t\tif (this.#closed || this.#process !== child) return\n\t\t\tdispatchLines(this.#emitter, [line])\n\t\t}\n\t}\n\n\t// The current child process reached its terminal moment — report a cut-off tail, then fire this\n\t// transport's `close` once. A child an explicit close superseded reports its own exit after\n\t// `start()` has installed a replacement; peer identity keeps that old exit from reporting on a\n\t// tail no reader can reach any more or emitting a second close. A teardown already reported\n\t// this lifetime, so the closed state stops the second report rather than the first.\n\t#onExit(child: Process, exit: ProcessExit): void {\n\t\tif (this.#process !== child) return\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// The report below emits SYNCHRONOUSLY, so a listener on that fault channel runs before this\n\t\t// lifetime has said it ended, and a `start()` it calls there would install its replacement\n\t\t// first: the `close` that followed would then reach every listener over a child the reader\n\t\t// can no longer see. A barrier held across the report is what orders those two — the\n\t\t// `start()` parks on it and resumes on the microtask queue, after `close` has been delivered\n\t\t// — and it is the same barrier `close()` holds across an explicit teardown's own report. A\n\t\t// `close()` called from that same listener finds this barrier through `??=` instead of\n\t\t// opening a second teardown, and resolves against it as the no-op an ended lifetime makes\n\t\t// it, after `close` has fired.\n\t\tconst barrier = Promise.withResolvers<void>()\n\t\tthis.#closing ??= barrier.promise\n\t\tthis.#report(exit)\n\t\t// Release and clear before the emit, so a `start()` called from a `close` listener finds no\n\t\t// barrier and opens the next lifetime inside that emit — the restart this transport\n\t\t// documents on a natural exit. Clear only this lifetime's own barrier: a barrier that is not\n\t\t// this one belongs to a teardown still running, and discarding it would let a later\n\t\t// `close()` resolve through a no-op before that teardown had reported its own `close`.\n\t\tbarrier.resolve()\n\t\tif (this.#closing === barrier.promise) this.#closing = undefined\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// A terminal moment the `drain` bound cut off rather than the child's own stream close leaves\n\t// `evidence` holding the tail as of that cutoff, and later diagnostics may have existed. Say so\n\t// on the fault channel, or a consumer reads a cut-off tail as the child's whole output.\n\t#report(exit: ProcessExit): void {\n\t\tif (exit.drained) return\n\t\tthis.#emitter.emit(\n\t\t\t'error',\n\t\t\tnew Error(\n\t\t\t\t'stdio transport evidence may be incomplete: the child streams stayed open past the supervisor drain bound',\n\t\t\t),\n\t\t)\n\t}\n}\n","import type {\n\tMCPClientTransportEventMap,\n\tMCPClientTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport { Readable } from 'node:stream'\nimport { Emitter } from '@orkestrel/emitter'\nimport { dispatchLines, extractLines, writeLine } from '../helpers.js'\n\n/**\n * The stdio SERVER transport for the Model Context Protocol — wraps an injectable\n * readable/writable stream pair (`process.stdin`/`process.stdout` in production, a\n * test double in tests) as a {@link MCPClientTransportInterface}, the newline-delimited\n * JSON-RPC channel {@link import('../factories.js').createStdioServer} pumps\n * `mcp.dispatch` over, the stdio mirror of {@link\n * import('./WebSocketServerTransport.js').WebSocketServerTransport}.\n *\n * @remarks\n * - **Reuses `MCPClientTransportInterface`.** The same generic carrier the HTTP\n * and WebSocket server transports implement — `emitter` (`message` / `close` /\n * `error`), `start`, `send`, `close`. `session` is `undefined` (the stateless v1).\n * - **Inbound (`message`).** `start()` subscribes to `input`'s `data` event; each\n * chunk is folded through the shared {@link extractLines} line-framing helper\n * (buffering a partial trailing line across reads), and every complete line is\n * decoded and delivered through the shared {@link dispatchLines} helper — a\n * well-formed {@link JSONRPCMessage} re-emits on `message`, a malformed line\n * emits `error` (never throws). `input`'s `close` bridges to this\n * transport's `close`.\n * - **Outbound (`send`).** `send(message)` writes one newline-terminated\n * `JSON.stringify`d line to `output` and awaits the writable completion callback. The\n * callback is the backpressure boundary and its error rejects the send.\n * - **`close()`** removes this transport's input and output subscriptions, rejects every\n * pending send, and fires its `close`\n * event (idempotent). It pauses the input only when the caller was not already reading\n * it at `start` (`readableFlowing !== true`) AND no `data` listener remains once this\n * transport's own is removed — so a process holding `process.stdin` can exit, and a\n * caller's own flow is never stopped underneath it. The transport preserves flowing versus\n * non-flowing state and restores every caller-owned listener. A Node stream that had never been\n * read starts with `readableFlowing === null` and is left non-flowing (`false`), because Node\n * exposes no public operation that restores `null` after data consumption starts. Attaching a\n * later `data` listener does not resume that stream; the caller must call `resume()` before the\n * listener receives data. The injected streams are owned by the caller (typically\n * `process.stdin`/`process.stdout`), so the transport never destroys, ends, or blanket-clears\n * them.\n * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the\n * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level\n * fault), distinct from the emitter's own listener-error channel.\n */\nexport class StdioServerTransport implements MCPClientTransportInterface {\n\treadonly #emitter: Emitter<MCPClientTransportEventMap>\n\treadonly #input: NodeJS.ReadableStream\n\treadonly #output: NodeJS.WritableStream\n\treadonly #data = (chunk: Buffer | string): void => this.#receive(chunk.toString())\n\treadonly #ending = (): void => this.#onClose()\n\treadonly #failure = (error: Error): void => this.#emitter.emit('error', error)\n\treadonly #pending = new Set<PromiseWithResolvers<void>>()\n\t#buffer = ''\n\t#started = false\n\t#closed = false\n\t#flowing = false\n\n\tconstructor(input: NodeJS.ReadableStream, output: NodeJS.WritableStream) {\n\t\tthis.#emitter = new Emitter<MCPClientTransportEventMap>()\n\t\tthis.#input = input\n\t\tthis.#output = output\n\t}\n\n\tget emitter(): EmitterInterface<MCPClientTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\t// The stateless v1 holds no session — a server-assigned id is the deferred tier.\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// The output stream stays writable for the process's life, so a frame written at any\n\t\t// moment reaches the peer — stdio is the transport MCP defines cancellation on.\n\t\treturn true\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// Arm the stream subscriptions once: an input chunk decodes to `message`, the input's\n\t\t// close bridges to this transport's `close`. Idempotent — a second `start` is a no-op\n\t\t// (the single MCPServer pump subscribes once).\n\t\tif (this.#started || this.#closed) return\n\t\tthis.#started = true\n\t\t// The input's flow state BEFORE anything is attached. `readableFlowing` is `true` only\n\t\t// for a stream a caller already put in flowing mode, so a `true` here says the caller is\n\t\t// reading and this transport is a guest on a flow it did not start. `null` (untouched)\n\t\t// and `false` (explicitly paused) both say it is not. The declared `NodeJS.ReadableStream`\n\t\t// does not carry that state, so a stream that is not a node `Readable` reads as\n\t\t// not-flowing — the same answer an untouched one gives.\n\t\tthis.#flowing = this.#input instanceof Readable && this.#input.readableFlowing === true\n\t\tthis.#input.on('data', this.#data)\n\t\tthis.#input.on('close', this.#ending)\n\t\tthis.#input.on('error', this.#failure)\n\t\tthis.#output.on('error', this.#failure)\n\t}\n\n\t/**\n\t * Sends one newline-delimited JSON-RPC message through the caller-owned output stream.\n\t *\n\t * @remarks\n\t * The writable completion callback is the backpressure boundary. This method awaits that\n\t * callback rather than adding a `drain` listener. Closing the transport rejects every send\n\t * whose callback has not settled.\n\t *\n\t * @param message - The message to serialize and write\n\t * @returns Resolves when the output confirms the write\n\t * @throws Thrown with `stdio transport is not connected` after the transport closes\n\t * @throws Thrown with the output callback error or synchronous write failure\n\t */\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\tif (this.#closed) throw new Error('stdio transport is not connected')\n\t\tconst pending = Promise.withResolvers<void>()\n\t\tthis.#pending.add(pending)\n\t\ttry {\n\t\t\tawait Promise.race([writeLine(this.#output, `${JSON.stringify(message)}\\n`), pending.promise])\n\t\t} finally {\n\t\t\tthis.#pending.delete(pending)\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tthis.#release()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t// Buffer a raw input chunk through the shared line-framing helper, then decode + deliver\n\t// every complete line onto this transport's emitter (a partial trailing line carries\n\t// forward to the next chunk).\n\t#receive(chunk: string): void {\n\t\tconst { lines, remainder } = extractLines(this.#buffer, chunk)\n\t\tthis.#buffer = remainder\n\t\tdispatchLines(this.#emitter, lines)\n\t}\n\n\t// The input stream closed (EOF, peer teardown) — fire this transport's `close` once. No peer\n\t// identity check is needed: the input is constructor-fixed and `start()` is idempotent, so no\n\t// superseded stream can report a close against a replacement.\n\t#onClose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tthis.#release()\n\t\tthis.#emitter.emit('close')\n\t}\n\n\t#release(): void {\n\t\tthis.#input.removeListener('data', this.#data)\n\t\tthis.#input.removeListener('close', this.#ending)\n\t\tthis.#input.removeListener('error', this.#failure)\n\t\tthis.#output.removeListener('error', this.#failure)\n\t\tfor (const pending of this.#pending) {\n\t\t\tpending.reject(new Error('stdio transport is not connected'))\n\t\t}\n\t\tthis.#pending.clear()\n\t\t// Different questions, asked at the moments where each is answerable: the reading\n\t\t// taken at `start` says whether the caller was already reading, and the listener count\n\t\t// taken HERE — after this transport's own `data` handler is gone — says whether a reader\n\t\t// is left. Pause only when neither is true, so a process holding `process.stdin` can exit\n\t\t// and a caller's flow is never seized. A count alone would pause a stream the caller had\n\t\t// resumed; the start reading alone would starve a reader that arrived after `start`.\n\t\tif (!this.#flowing && this.#input.listenerCount('data') === 0) this.#input.pause()\n\t}\n}\n","import type {\n\tMCPClientTransportInterface,\n\tMCPContinuationInterface,\n\tMCPDispatcherInterface,\n} from '@src/core'\nimport type { RouteInput } from '@orkestrel/router'\nimport type { TokenSecret, UpgradeHandler } from '@orkestrel/server'\nimport type {\n\tHTTPClientTransportOptions,\n\tHTTPTransportOptions,\n\tStdioClientTransportInterface,\n\tStdioClientTransportOptions,\n\tStdioServerOptions,\n\tWebSocketClientTransportOptions,\n\tWebSocketServerOptions,\n} from './types.js'\nimport type { IncomingMessage } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport { bindServer } from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { signToken, verifyToken } from '@orkestrel/server'\nimport { createNodeWebSocket, WEBSOCKET_VERSION } from '@orkestrel/websocket'\nimport { DEFAULT_MCP_PATH, MCP_WEBSOCKET_SUBPROTOCOL } from './constants.js'\nimport { createMCPPostHandler } from './handlers.js'\nimport { bridgeMessageTransport, upgradeRequestPath } from './helpers.js'\nimport { HTTPClientTransport } from './transports/HTTPClientTransport.js'\nimport { StdioClientTransport } from './transports/StdioClientTransport.js'\nimport { StdioServerTransport } from './transports/StdioServerTransport.js'\nimport { WebSocketClientTransport } from './transports/WebSocketClientTransport.js'\nimport { WebSocketServerTransport } from './transports/WebSocketServerTransport.js'\n\n/**\n * Adapts the installed server token primitives to the host-neutral MCP continuation port.\n *\n * @param secret - Current signing secret or `[current, ...older]` rotation list\n * @returns A continuation port that seals and opens opaque canonical state strings\n */\nexport function createMCPContinuation(secret: TokenSecret): MCPContinuationInterface {\n\treturn {\n\t\tseal(value) {\n\t\t\treturn signToken(value, { secret })\n\t\t},\n\t\topen(value) {\n\t\t\treturn verifyToken(value, secret)\n\t\t},\n\t}\n}\n\n/**\n * Creates the MCP Streamable-HTTP transport routes — mounts a transport-agnostic\n * {@link MCPDispatcherInterface} (the `@orkestrel/mcp` dispatch boundary) on the fetch-standard router\n * spine, pumping each `POST` body through `mcp.dispatch`. Returns the {@link RouteInput}s to\n * hand to `router.add(...)`.\n *\n * @remarks\n * A SINGLE `POST {path}` route — `createMCPRoutes` is STATELESS. The handler reads its own\n * request body (its own JSON parse try/catch), so it works with or without a session\n * middleware mounted in front. It draws a sharp line between TRANSPORT-level and\n * DISPATCH-level outcomes:\n *\n * - A **transport** failure — a malformed JSON body, or a parsed value that is not a\n * JSON-RPC INVOCATION — is an HTTP `400` carrying a JSON-RPC error BODY (`-32700` Parse\n * error / `-32600` Invalid Request), with the `id` it could not read OMITTED.\n * - Modern protocol/method/name headers are validated against the body; a mismatch is\n * HTTP `400` + `-32020`. Headerless initialize is accepted, a live legacy session supplies\n * its pinned revision, and every other headerless request is rejected.\n * - Legacy dispatch errors stay IN-BAND at HTTP `200`; modern errors map to `400` for\n * `-32020` / `-32021` / `-32022` / `-32602`, `404` for `-32601`, and `200` otherwise.\n * - A **notification** (an invocation with no `id`, which `dispatch` resolves to\n * `undefined`) is a `202 Accepted` with no body.\n *\n * When `streaming` is enabled (the default) and the client `Accept`s `text/event-stream`,\n * the `200` reply is framed as a Streamable-HTTP SSE response (one `data:` event carrying\n * the JSON-RPC envelope, then the stream ends) through `@orkestrel/server`'s generic\n * {@link import('@orkestrel/server').openStream} seam; otherwise it is a plain JSON body.\n *\n * **Sessions are a SEPARATE, plug-and-play middleware.** `createMCPRoutes` mints / reads no\n * session id. To make the transport STATEFUL, mount {@link\n * import('./middlewares.js').createMCPSession} IN FRONT — it owns the same `path`, mints +\n * validates the `mcp-session-id`, and serves the resumable `GET {path}` + `DELETE {path}`,\n * leaving this route to dispatch the validated `POST`.\n *\n * This is MECHANISM, not policy: compose auth / rate-limiting (and the session middleware)\n * IN FRONT as ordinary middleware; the optional `origin` group carries the deployment's shared\n * allowlist or explicitly delegates validation to an upstream layer.\n *\n * @typeParam TState - The consumer's opaque per-request state type\n * @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over HTTP\n * @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}) and `streaming`\n * (default `true`), plus shared origin, keepalive, and synchronous caller-extraction options; see\n * {@link HTTPTransportOptions}\n * @returns The {@link RouteInput}s to register with the router\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createMCPRoutes } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * const routes = createMCPRoutes(createMCPLegacy(mcp)) // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createMCPRoutes<TState = unknown>(\n\tmcp: MCPDispatcherInterface,\n\toptions?: HTTPTransportOptions<TState>,\n): ReadonlyArray<RouteInput<string, TState>> {\n\tconst path = options?.path ?? DEFAULT_MCP_PATH\n\tconst post: RouteInput<string, TState> = {\n\t\tmethod: 'POST',\n\t\tpath,\n\t\tname: 'mcp',\n\t\thandler: createMCPPostHandler<TState>(mcp, options),\n\t}\n\treturn [post]\n}\n\n/**\n * Creates the HTTP CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}\n * — a {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server\n * over `fetch`. The egress mirror of {@link createMCPRoutes}.\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client sends is\n * `POST`ed to `options.url` with `content-type: application/json` and an `Accept` of\n * both `application/json` and `text/event-stream` (the server answers with EITHER — a\n * plain JSON envelope or a Streamable-HTTP SSE `data:` event, decoded with `@orkestrel/sse`),\n * and the reply is surfaced on the transport's `message` event for the client's id\n * correlation. Add `options.headers` (for example, an `Authorization` bearer) to reach a guarded\n * server. `start` / `close` hold no connection; against a STATEFUL server it captures the\n * `mcp-session-id` from `initialize` and echoes it on later requests. It also captures\n * the initialize result's `protocolVersion` and sends `mcp-protocol-version` alone on each\n * subsequent legacy request. Modern requests derive protocol and method headers directly\n * from the message, plus a name header only for `tools/call`.\n *\n * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged onto\n * every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`\n * (ms, applied with `AbortSignal.timeout`); see {@link HTTPClientTransportOptions}\n * @returns A working {@link MCPClientTransportInterface} over `fetch`\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createHTTPClientTransport } from '@orkestrel/mcp/server'\n *\n * const client = createMCPClient({\n * \ttransport: createHTTPClientTransport({ url: 'http://localhost:3000/mcp' }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createHTTPClientTransport(\n\toptions: HTTPClientTransportOptions,\n): MCPClientTransportInterface {\n\treturn new HTTPClientTransport(options)\n}\n\n/**\n * Creates the MCP WebSocket transport INGRESS — an {@link UpgradeHandler} that exposes a\n * transport-agnostic {@link MCPDispatcherInterface} over a WebSocket, the WebSocket mirror of\n * {@link createMCPRoutes}. Register it on the spine's upgrade seam.\n *\n * @remarks\n * It composes the lean RFC 6455 `@orkestrel/websocket` wrapper over `@orkestrel/server`'s\n * generic upgrade seam — the spine speaks no WebSocket, this handler does.\n *\n * - **Declines (returns `false`)** when the upgrade is not for it, so the spine fans the\n * socket to the next handler (or destroys an unclaimed one): the `Upgrade` header is not\n * `websocket`, the request path is not `options.path` (default {@link DEFAULT_MCP_PATH},\n * `'/mcp'`), the `Sec-WebSocket-Key` is absent, or the `Sec-WebSocket-Version` is not `13`.\n * A decline NEVER writes to the socket (it is not yet ours) — the spine owns the unclaimed\n * outcome.\n * - **Claims (returns `true`)** otherwise: it builds `createNodeWebSocket({ socket, key, head,\n * protocol })` (SERVER mode → writes the `101` handshake, selects the configured subprotocol\n * only when the client's offer contains it, and sends UNMASKED frames), wraps it in a\n * {@link WebSocketServerTransport}, and pipes it through the core {@link\n * import('@orkestrel/mcp').MCPTransportInterface} port through {@link\n * import('./helpers.js').bridgeMessageTransport} + {@link import('@orkestrel/mcp').bindServer}:\n * each inbound REQUEST runs through `mcp.dispatch`, and a defined response is written back\n * as a frame — a NOTIFICATION sends nothing, and a non-request message (a stray response) is\n * ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than\n * escaping the (async) message pump.\n * - **Closes on the spine's `stop`.** It holds every socket it claimed and, on `options.emitter`'s\n * `stop` event, closes each one with the RFC 6455 close handshake, so the spine's drain settles\n * at once and each client reads a clean goodbye. Node detaches an upgraded socket from the\n * connection set the spine's own close walks, so the claimant is the only thing that can end\n * it: an ingress that held its sockets open would cost `stop()` the whole `drain` budget and\n * then have the connection cut mid-protocol. A socket the peer already dropped is gone from\n * the set (its transport's `close` removes it), and closing a dead one is a no-op either way.\n *\n * It is MECHANISM, not policy: compose an auth guard IN FRONT by registering an upgrade\n * handler BEFORE this one — that handler can claim (decline + destroy) an unauthenticated\n * upgrade so it never reaches this pump.\n *\n * @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over WebSocket\n * @param options - The spine's `emitter` (REQUIRED — the `stop` event this ingress closes its\n * sockets on), plus optional `path` (default {@link DEFAULT_MCP_PATH}) and `subprotocol`\n * (default {@link MCP_WEBSOCKET_SUBPROTOCOL}); see {@link WebSocketServerOptions}\n * @returns An {@link UpgradeHandler} to register with the spine's `upgrade` seam\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createWebSocketServer } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * // Claims the MCP upgrade at ws://…/mcp:\n * server.upgrade(createWebSocketServer(createMCPLegacy(mcp), { emitter: server.emitter })) // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createWebSocketServer(\n\tmcp: MCPDispatcherInterface,\n\toptions: WebSocketServerOptions,\n): UpgradeHandler {\n\tconst path = options.path ?? DEFAULT_MCP_PATH\n\tconst subprotocol = options.subprotocol ?? MCP_WEBSOCKET_SUBPROTOCOL\n\t// The connections this handler owns, each with the detachment its binding returned\n\t// — a closure store, like the session middleware's. A transport leaves on its own `close`,\n\t// so the map holds exactly the LIVE connections and exactly the bindings still attached.\n\tconst live = new Map<WebSocketServerTransport, () => void>()\n\t// The spine is stopping: detach each binding, then say the RFC 6455 goodbye on every socket\n\t// still open. Nothing else can close them — node detaches an upgraded socket from the\n\t// connection set the spine's close walks — so without this the drain runs to its deadline and\n\t// the connection is cut mid-protocol.\n\toptions.emitter.on('stop', () => {\n\t\tfor (const [transport, unbind] of live) {\n\t\t\tunbind()\n\t\t\tvoid transport.close()\n\t\t}\n\t})\n\treturn (request: IncomingMessage, socket: Duplex, head: Buffer): boolean => {\n\t\t// DECLINE anything that is not our MCP WebSocket upgrade — the spine fans it onward or\n\t\t// destroys it. Never touch the socket on a decline (it is not ours yet).\n\t\tconst upgrade = request.headers['upgrade']\n\t\tif (!isString(upgrade) || upgrade.toLowerCase() !== 'websocket') return false\n\t\tif (upgradeRequestPath(request) !== path) return false\n\t\tconst key = request.headers['sec-websocket-key']\n\t\tif (!isString(key)) return false\n\t\tconst version = request.headers['sec-websocket-version']\n\t\tif (!isString(version) || version !== WEBSOCKET_VERSION) return false\n\n\t\t// CLAIM: the wrapper writes the `101` handshake (server mode) and the transport pipes\n\t\t// through the core port: bindServer dispatches each inbound request and writes back a\n\t\t// defined response (a notification sends nothing); a dispatch / send fault surfaces on\n\t\t// `mcp.emitter`'s `error` event.\n\t\tconst offer = request.headers['sec-websocket-protocol']\n\t\tconst protocol =\n\t\t\tisString(offer) && offer.split(',').some((candidate) => candidate.trim() === subprotocol)\n\t\t\t\t? subprotocol\n\t\t\t\t: undefined\n\t\tconst ws = createNodeWebSocket({\n\t\t\tsocket,\n\t\t\tkey,\n\t\t\thead,\n\t\t\t...(protocol === undefined ? {} : { protocol }),\n\t\t})\n\t\tconst transport = new WebSocketServerTransport(ws)\n\t\t// The binder's detachment is this handler's to hold: the connection it belongs to is one\n\t\t// this factory minted and nothing outside can reach, so a discarded `unbind` leaves the\n\t\t// binding attached to a transport that has ended with no way left to detach it.\n\t\tconst unbind = bindServer(mcp, bridgeMessageTransport(transport))\n\t\tlive.set(transport, unbind)\n\t\ttransport.emitter.on('close', () => {\n\t\t\tlive.delete(transport)\n\t\t\tunbind()\n\t\t})\n\t\tvoid transport.start()\n\t\treturn true\n\t}\n}\n\n/**\n * Creates the WebSocket CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}\n * — a {@link MCPClientTransportInterface} that drives a REMOTE MCP server over a WebSocket. The\n * egress mirror of {@link createWebSocketServer} and the WebSocket sibling of {@link\n * createHTTPClientTransport}.\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`) performs\n * the RFC 6455 client handshake against `options.url` (accepting a `ws://` / `wss://` or an\n * `http://` / `https://` URL — a `ws(s)` scheme is converted to `http(s)` for the underlying\n * upgrade request), validates the `Sec-WebSocket-Accept` (with `@orkestrel/websocket`'s\n * `computeWebSocketAccept`), and opens a persistent bidirectional frame channel; each JSON-RPC\n * message the client `send`s is written as one masked text frame, and each decoded reply is\n * surfaced on the transport's `message` event for the client's id correlation. Add\n * `options.headers` (for example, an `Authorization` bearer) to reach a guarded server.\n *\n * @param options - `url` (the remote WebSocket endpoint; REQUIRED) and optional `headers`\n * merged onto the upgrade request; see {@link WebSocketClientTransportOptions}\n * @returns A working {@link MCPClientTransportInterface} over a WebSocket\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createWebSocketClientTransport } from '@orkestrel/mcp/server'\n *\n * const client = createMCPClient({\n * \ttransport: createWebSocketClientTransport({ url: 'ws://localhost:3000/mcp' }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createWebSocketClientTransport(\n\toptions: WebSocketClientTransportOptions,\n): MCPClientTransportInterface {\n\treturn new WebSocketClientTransport(options)\n}\n\n/**\n * Creates the stdio CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}\n * — a {@link StdioClientTransportInterface} that spawns and drives a CHILD PROCESS MCP server\n * over newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link\n * createHTTPClientTransport} and {@link createWebSocketClientTransport}.\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)\n * spawns `options.command` with `options.args` and `options.env`, piping its\n * `stdin`/`stdout` for the JSON-RPC channel. The child's `stderr` is piped too, and\n * retained as a bounded tail this transport reports as `evidence` — the parent never\n * inherits it. Each JSON-RPC message the client `send`s is written as one\n * newline-terminated line to the child's `stdin`; each decoded reply line from the\n * child's `stdout` is surfaced on the transport's `message` event for the client's\n * id correlation. That write is bounded: a child that stays alive without ever reading\n * its `stdin` fills the pipe, and `options.delivery` is how long the unconfirmed write\n * waits before the `send` rejects. An omitted `delivery` selects {@link\n * import('./constants.js').DEFAULT_MCP_DELIVERY}; an explicit `0` removes the bound.\n *\n * @param options - `command` (the executable to spawn; REQUIRED), optional `args`,\n * optional `env`, and an optional `delivery` bound in milliseconds on an unconfirmed\n * `stdin` write; see {@link StdioClientTransportOptions}\n * @returns A working {@link StdioClientTransportInterface} over a child process's stdio,\n * whose `evidence` carries the supervised child's bounded stderr tail\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createStdioClientTransport } from '@orkestrel/mcp/server'\n *\n * const client = createMCPClient({\n * \ttransport: createStdioClientTransport({ command: 'node', args: ['./server.js'] }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createStdioClientTransport(\n\toptions: StdioClientTransportOptions,\n): StdioClientTransportInterface {\n\treturn new StdioClientTransport(options)\n}\n\n/**\n * Creates the MCP stdio transport INGRESS — pumps a transport-agnostic {@link\n * MCPDispatcherInterface} over newline-delimited JSON-RPC on `stdin`/`stdout` (or an\n * injected stream pair), the stdio mirror of {@link createWebSocketServer}.\n *\n * @remarks\n * Wraps `options.input` (default `process.stdin`) / `options.output` (default\n * `process.stdout`) in a {@link import('./transports/StdioServerTransport.js').StdioServerTransport}\n * and pipes it through the core {@link import('@orkestrel/mcp').MCPTransportInterface} port\n * through {@link import('./helpers.js').bridgeMessageTransport} + {@link\n * import('@orkestrel/mcp').bindServer}: each inbound REQUEST runs through `mcp.dispatch`, and\n * a defined response is written back as a newline-terminated line — a NOTIFICATION\n * writes nothing, and a non-request message is ignored. A `dispatch` / `send` fault\n * surfaces on `mcp.emitter`'s `error` event rather than escaping the (async) message\n * pump.\n *\n * @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over stdio\n * @param options - Optional injectable `input` / `output` streams; see\n * {@link StdioServerOptions}\n * @returns A `{ start(): void; stop(): void }` handle to arm / tear down the pump\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createStdioServer } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * // An MCP client now connects over this process's stdio:\n * createStdioServer(createMCPLegacy(mcp)).start() // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createStdioServer(\n\tmcp: MCPDispatcherInterface,\n\toptions?: StdioServerOptions,\n): { start(): void; stop(): void } {\n\tconst input = options?.input ?? process.stdin\n\tconst output = options?.output ?? process.stdout\n\tconst transport = new StdioServerTransport(input, output)\n\tconst unbind = bindServer(mcp, bridgeMessageTransport(transport))\n\treturn {\n\t\tstart(): void {\n\t\t\tvoid transport.start()\n\t\t},\n\t\tstop(): void {\n\t\t\tunbind()\n\t\t\tvoid transport.close()\n\t\t},\n\t}\n}\n","import type { JSONRPCMessage } from '@src/core'\nimport type { MiddlewareHandler } from '@orkestrel/server'\nimport type { MCPSessionEntry, MCPSessionOptions, MCPSessionState } from './types.js'\nimport {\n\tMCP_HEADER_MISMATCH,\n\tbuildJSONRPCError,\n\tisInitializeRequest,\n\tisModernRequest,\n\tparseJSONRPCMessage,\n} from '@src/core'\nimport { openStream } from '@orkestrel/server'\nimport {\n\tDEFAULT_MCP_PATH,\n\tMCP_PROTOCOL_VERSION_HEADER,\n\tMCP_SESSION_HEADER,\n\tSSE_BUFFERING_DISABLED,\n\tSSE_BUFFERING_HEADER,\n} from './constants.js'\nimport {\n\tallowsOrigin,\n\treadLastEventId,\n\treadSessionHeader,\n\trejectUnknownSession,\n} from './helpers.js'\nimport { inferHeaderIssue, inferLegacyVersion } from './inferers.js'\nimport { MCPSession } from './MCPSession.js'\nimport { HTTPDisconnect } from './transports/HTTPDisconnect.js'\n\n/**\n * Creates the native MCP session {@link MiddlewareHandler} — the plug-and-play stateful layer\n * that fronts a session-agnostic {@link import('./factories.js').createMCPRoutes}. Compose it\n * with `router.use(createMCPSession())` (or the equivalent middleware seam), mirroring any\n * other closure-scoped stateful middleware. Has NO dependency on `@orkestrel/middleware` — the\n * session store, mint-on-`initialize`, and resumable stream are all native to this package.\n *\n * @remarks\n * Owns a closure `Map<string, MCPSessionEntry>` keyed by session id, and a single request\n * `path` (default {@link DEFAULT_MCP_PATH}); a request to any other path passes straight\n * through (`next()`).\n *\n * A modern-shaped POST also passes straight through with `next()`, ignoring any session id.\n * The remaining behavior is the legacy session layer:\n *\n * - **`POST {path}`.** Buffers `const text = await request.text()` (so the downstream route\n * can re-read it from a freshly-built forwarded `Request`). Resolves a session through {@link\n * readSessionHeader}: a VALID id touches the entry and sets `context.state.session`; an\n * ABSENT / unknown id whose (guarded) body parses to an `initialize` request ({@link\n * isInitializeRequest}) MINTS a fresh {@link MCPSession} (`crypto.randomUUID()`, `capacity`)\n * and sets `context.state.session`; neither → {@link rejectUnknownSession} (`404`). The\n * minted entry pins the negotiated legacy revision, which is supplied to a later headerless\n * live-session request. It then\n * FORWARDS a fresh `Request` carrying the buffered `text` (`next(forwarded)`) — never the\n * already-consumed original — so the route re-reads the same body, and stamps the response\n * with {@link MCP_SESSION_HEADER}. The entry's `touched` instant is read AFTER that\n * downstream response, because it means the LAST ACCESS: a request slower than `ttl` would\n * otherwise store a session that is already expired, and the write-back RE-ASKS the store, so\n * a `DELETE` arriving while the request was suspended is not undone.\n * - **`GET {path}`.** Resolves the session the same way (no mint — only `initialize` mints);\n * an invalid / unknown id is the same `404`. A valid session opens the resumable\n * server→client stream through `@orkestrel/server`'s {@link import('@orkestrel/server').openStream}:\n * replays every event after the client's `Last-Event-ID` ({@link readLastEventId}) BEFORE\n * attaching the stream for live pushes, then attaches; cancellation of the streamed response\n * body composes with `request.signal` and detaches it. Long-lived — never `end()`ed here.\n * - **`DELETE {path}`.** Resolves the session; a valid id deletes it from the store and answers\n * `204`; an invalid / unknown id is the same `404`.\n *\n * It is MECHANISM, not policy, and ADDITIVE: omit it entirely for the stateless default\n * ({@link import('./factories.js').createMCPRoutes}'s only behavior). The `path` MUST match the\n * `createMCPRoutes` `path` it fronts. The WebSocket transport is inherently one session per\n * connection (the socket IS the session), so this middleware does not apply to it.\n *\n * @typeParam TState - The consumer's `TState`, which MUST extend {@link MCPSessionState} so\n * the resolved session can be threaded through `context.state.session`\n * @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}), `ttl` (idle-session\n * sweep window, ms — omit for sessions that live until an explicit `DELETE`), `capacity`\n * (the folded per-session replay-log bound), and `clock` (the deterministic epoch-ms clock;\n * defaults to `Date.now`), plus the shared `origin` validation options; see\n * {@link MCPSessionOptions}\n * @returns A {@link MiddlewareHandler} that mints / validates sessions + serves the resumable\n * `GET` / `DELETE`\n *\n * @example\n * ```ts\n * import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createMCPRoutes, createMCPSession } from '@orkestrel/mcp/server'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })\n * router.use(createMCPSession({ ttl: 60_000 })) // stateful: mint + validate + resumable GET / DELETE\n * // The route stays session-agnostic:\n * router.add(createMCPRoutes(createMCPLegacy(mcp))) // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createMCPSession<TState extends MCPSessionState>(\n\toptions?: MCPSessionOptions,\n): MiddlewareHandler<TState> {\n\tconst path = options?.path ?? DEFAULT_MCP_PATH\n\tconst capacity = options?.capacity\n\tconst ttl = options?.ttl\n\tconst clock = options?.clock ?? Date.now\n\tconst origin = options?.origin\n\tconst store = new Map<string, MCPSessionEntry>()\n\n\treturn async (request, context, next) => {\n\t\tif (context.url.pathname !== path) return next()\n\t\tif (!allowsOrigin(request, origin)) return new Response(null, { status: 403 })\n\t\tlet parsed: JSONRPCMessage | undefined\n\t\tlet text: string | undefined\n\t\tif (context.method === 'POST') {\n\t\t\ttry {\n\t\t\t\ttext = await request.text()\n\t\t\t\tparsed = parseJSONRPCMessage(JSON.parse(text))\n\t\t\t} catch {\n\t\t\t\tparsed = undefined\n\t\t\t}\n\t\t\tif (text !== undefined && parsed !== undefined && isModernRequest(parsed)) {\n\t\t\t\treturn next(\n\t\t\t\t\tnew Request(context.url, {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: request.headers,\n\t\t\t\t\t\tbody: text,\n\t\t\t\t\t\tsignal: request.signal,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (ttl !== undefined) {\n\t\t\tconst cutoff = clock() - ttl\n\t\t\tfor (const [id, entry] of store) {\n\t\t\t\tif (entry.touched <= cutoff) store.delete(id)\n\t\t\t}\n\t\t}\n\n\t\tif (context.method === 'DELETE') {\n\t\t\tconst id = readSessionHeader(request)\n\t\t\tif (id === undefined || !store.delete(id)) return rejectUnknownSession()\n\t\t\treturn new Response(null, { status: 204 })\n\t\t}\n\n\t\tlet entry: MCPSessionEntry | undefined\n\t\tconst id = readSessionHeader(request)\n\t\tif (id !== undefined) {\n\t\t\tconst current = store.get(id)\n\t\t\tif (current !== undefined) {\n\t\t\t\tentry = { session: current.session, touched: clock(), version: current.version }\n\t\t\t\tstore.set(id, entry)\n\t\t\t}\n\t\t}\n\n\t\tif (context.method === 'GET') {\n\t\t\tif (entry === undefined) return rejectUnknownSession()\n\t\t\tconst session = entry.session\n\t\t\tconst stream = openStream()\n\t\t\tconst disconnect = new HTTPDisconnect(request.signal, options?.keepalive)\n\t\t\tstream.response.headers.set(SSE_BUFFERING_HEADER, SSE_BUFFERING_DISABLED)\n\t\t\t// A comment write flushes the response headers immediately (the underlying node:http\n\t\t\t// response only sends headers on its first `write`/`end`) — without it a client's fetch\n\t\t\t// hangs waiting for headers until the first replay/push write, which may never come.\n\t\t\tstream.comment('open')\n\t\t\tconst lastEventId = readLastEventId(request)\n\t\t\tif (lastEventId !== undefined) {\n\t\t\t\t// Replay every event STRICTLY AFTER the client's last-seen id BEFORE attaching, so the\n\t\t\t\t// missed events arrive in order ahead of any live push.\n\t\t\t\tfor (const e of session.replay(lastEventId)) {\n\t\t\t\t\tstream.write({ id: e.id, data: JSON.stringify(e.message) })\n\t\t\t\t}\n\t\t\t}\n\t\t\tsession.attach(stream)\n\t\t\tif (disconnect.signal.aborted) session.detach(stream)\n\t\t\telse disconnect.signal.addEventListener('abort', () => session.detach(stream), { once: true })\n\t\t\treturn disconnect.bridge(stream)\n\t\t}\n\n\t\tif (context.method !== 'POST' || text === undefined) return next()\n\n\t\t// POST — only `initialize` mints a fresh session when no valid id is present.\n\t\tlet created: MCPSessionEntry | undefined\n\t\tif (entry === undefined) {\n\t\t\tif (parsed !== undefined && isInitializeRequest(parsed)) {\n\t\t\t\tconst session = new MCPSession(\n\t\t\t\t\tcrypto.randomUUID(),\n\t\t\t\t\tcapacity !== undefined ? { capacity } : {},\n\t\t\t\t)\n\t\t\t\tcreated = { session, touched: clock(), version: inferLegacyVersion(parsed) }\n\t\t\t\tentry = created\n\t\t\t} else {\n\t\t\t\treturn rejectUnknownSession()\n\t\t\t}\n\t\t}\n\t\tif (!Reflect.set(context.state, 'session', entry.session)) {\n\t\t\tthrow new Error('MCP session state is not writable')\n\t\t}\n\t\tconst headers = new Headers(request.headers)\n\t\tif (parsed === undefined || !isInitializeRequest(parsed)) {\n\t\t\tconst issue = inferHeaderIssue(request, entry.version)\n\t\t\tif (issue?.reason === 'missing') {\n\t\t\t\theaders.set(MCP_PROTOCOL_VERSION_HEADER, entry.version)\n\t\t\t} else if (issue !== undefined) {\n\t\t\t\tconst requestId = parsed !== undefined && 'method' in parsed ? parsed.id : undefined\n\t\t\t\treturn Response.json(buildJSONRPCError(requestId, MCP_HEADER_MISMATCH, issue.message), {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tconst forwarded = new Request(context.url, {\n\t\t\tmethod: 'POST',\n\t\t\theaders,\n\t\t\tbody: text,\n\t\t\tsignal: request.signal,\n\t\t})\n\t\tconst response = await next(forwarded)\n\t\tif (created !== undefined) {\n\t\t\tif (!response.ok) return response\n\t\t\t// `touched` is the instant of the LAST ACCESS, so it is read AFTER the suspension. The\n\t\t\t// mint stamp was taken before a whole `initialize` round trip that the middleware has no\n\t\t\t// bound on: a handshake slower than `ttl` used to insert a session that was already\n\t\t\t// expired, and the first request for the id this response advertises swept it away.\n\t\t\tstore.set(created.session.id, { ...created, touched: clock() })\n\t\t} else if (store.get(entry.session.id) === entry) {\n\t\t\t// The same correction on the resolved-existing path — plus a RE-ASK. `store.get` is\n\t\t\t// consulted again because a `DELETE` (or the sweep of a sibling request) may have removed\n\t\t\t// this entry while this request was suspended, and a blind write-back would resurrect a\n\t\t\t// session whose owner already ended it.\n\t\t\tstore.set(entry.session.id, { ...entry, touched: clock() })\n\t\t}\n\t\tresponse.headers.set(MCP_SESSION_HEADER, entry.session.id)\n\t\treturn response\n\t}\n}\n"],"mappings":"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"}