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.
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.