serveStdio

Drive an McpServer over a newline-delimited JSON-RPC channel using the shared full-duplex DuplexChannel.

readLine returns the next line (without its terminator), or null at end-of-input. writeLine emits one line (a terminator is added by the caller's sink). Blank input lines are ignored. This is transport-pure — runStdio wires it to the process's real stdin/stdout via async pipes.

The MCP stdio transport is bidirectional and permits the server to write any valid MCP message to stdout at any time, not only direct request replies. The channel's read loop demultiplexes inbound lines:

- a *request* is dispatched in its OWN cooperative vibe task, so several tool handlers can be in flight concurrently and a handler that blocks on a server->client request (ctx.sample/ctx.elicit) or polls ctx.isCancelled does not stall the read loop. Notifications the handler emits (notifications/message, notifications/progress) and the request's reply are written through channel.send (serialized against other writers); - a *notification* (e.g. notifications/cancelled, notifications/initialized) is handled inline; an inbound notifications/cancelled flips the matching in-flight request's CancellationToken concurrently with its running handler task, which then observes ctx.isCancelled() and has its response suppressed (basic/utilities/cancellation, draft Transport-Specific Cancellation over stdio); - a draft subscriptions/listen request is served on the single channel (its acknowledgement and subsequent change notifications go through channel.send).

Requires a running vibe event loop; serveStdio runs the read loop on the CURRENT task and blocks until end-of-input.

@safe
void
serveStdio
(,
string delegate
()
@safe
readLine
,
void delegate
(
string
)
@safe
writeLine
)