# stdio _(no submodules)_ ## Purpose `StdioTransport` frames JSON-RPC messages over a newline-delimited JSON byte stream — the MCP spec default for sub-process child servers. The transport is universal: it accepts an injectable `readable: AsyncIterable` and a `writable: UniversalWritable`, so callers wire `Readable.toWeb(process.stdin)`, `Bun.stdin.stream()`, browser `ReadableStream`, or an in-memory queue without the package depending on any Node-only module. ## Features - Newline-delimited JSON framing — one message per `\n`-terminated line. - Injectable streams: no `node:fs` / `node:process` / `node:stream` imports. - Streaming parser tolerates partial chunks via a buffer + `TextDecoder({ stream: true })`. - Malformed lines are skipped silently; framing recovers on the next newline. - Inherits `closed` guard, listener registry, and idempotent close from `BaseTransport`. ## Usage: Basic Example ```ts import { createStdioTransport } from "@suaveplan/mcp/transport/stdio"; import type { JsonRpcMessage } from "@suaveplan/mcp"; const sentLines: string[] = []; const inbox = new ReadableStream({ start() { /* … */ } }); const transport = createStdioTransport({ readable: inbox, writable: { async write(chunk) { sentLines.push(new TextDecoder().decode(chunk)); }, async close() {}, }, }); transport.onMessage((m: JsonRpcMessage) => console.log("rx", m)); await transport.send({ jsonrpc: "2.0", id: 1, method: "ping" }); ``` ## Usage: Advanced Example ```ts import { createStdioTransport } from "@suaveplan/mcp"; // Bridge to a child process spawned by your test harness. async function bridgeProcess(child: { stdout: AsyncIterable; stdin: WritableStream }) { const writer = child.stdin.getWriter(); return createStdioTransport({ readable: child.stdout, writable: { async write(chunk) { await writer.write(chunk); }, async close() { await writer.close(); }, }, }); } ``` ## API Reference - `interface StdioTransportOptions` — `{ readable: AsyncIterable; writable: UniversalWritable }`. - `class StdioTransport extends BaseTransport` — constructor takes `StdioTransportOptions`; starts reading asynchronously. - `createStdioTransport(options): StdioTransport` — convenience factory mirroring the documented public API. ## Implementation Notes The reader loop maintains an internal text buffer; each chunk is decoded with `TextDecoder({ stream: true })` so multi-byte sequences split across reads still decode cleanly. After each newline the line is `trim`-ed and `JSON.parse`-d; failures or schema rejections are dropped because line framing makes the next message independent. Send framing writes `JSON.stringify(message) + "\n"` — `JSON.stringify` always escapes control characters in strings, so a literal newline in payload text becomes `\\n` and never breaks the framing. `closeInternal` flips a `stopped` flag the reader checks on each iteration and then closes the writable side; calling close while a long read is pending lets the reader exit on its next `for await` step.