Expand description
The stdio transport’s byte layer.
§What this is
Three adapters and a shared error type, and none of them knows what a
JSON-RPC message is. StdioSource turns a byte stream into whole frames,
StdioSink turns documents into framed bytes, and Diagnostics is the
side channel that must never be confused for either. The framing itself is
crate::domain::FrameDecoder and crate::domain::encode_frame — this
module supplies the stream, the ceiling, and the errors, and delegates the
rest, so the two halves of the transport cannot disagree about where a
frame ends or how long one may be.
§The ceiling is one value, not two
StdioConfig is handed to both halves. That is the point: a limit applied
only to reads protects the server from a hostile peer but not from a
handler that answers with more than the peer will read, and a limit applied
only to writes is the same argument with the roles swapped. One
MaxFrameSize, both directions, so a reply is refused with a typed error
instead of being written as a line the peer’s own decoder has to treat as a
malformed message.
§Why the transport is generic over its stream
StdioSource<R> and StdioSink<W> are generic rather than hard-wired to
a process’s stdin and stdout. The transport boundary is where a
tokio::process::Child becomes a byte stream, and everything below that line
is identical whether the bytes arrive from a child, a pipe, a socket, or a
test. Naming the concrete stream here would make every one of those cases a
second implementation, and the framing logic — the part with the security
properties — would be duplicated along with it.
§Where the conversation is
The three adapters above are a byte layer and stay one. The loop that reads
a frame, routes it and answers it is serve::ServeLoop, and the reply
document it writes is reply. Both sit in this module because they are
still stdio: the loop’s central rule is that this transport’s stdout carries
MCP messages and nothing else, and that rule is about this process’s
streams rather than about MCP.
§What is deliberately not here
No spawning and no lifecycle. The loop runs against whatever byte streams it is given, and the process management that gives it a child’s stdin and stdout in the first place is a separate concern. A single unterminated line has no framing error either, because the framing layer cannot know whether the peer is mid-message or has gone away.
Re-exports§
pub use crate::domain::jsonrpc::reply::ReplyError;
Structs§
- Diagnostics
- A writer bound to one stream, safe to share.
- Diagnostics
Writer - A held borrow of a
Diagnosticschannel, usable as anAsyncWrite. - Serve
Loop - Reads a peer’s frames, routes them, and writes the answers back.
- Serve
Report - The handler’s view of the peer’s id.
- Stdio
Config - The settings both halves of the transport are built from.
- Stdio
Sink - Writes framed documents to the peer, and to nothing else.
- Stdio
Source - A
FrameSourceover anyAsyncRead.
Enums§
- Serve
Error - Why the session ended when it was not the peer ending it.
- Stdio
Error - Why the stdio transport could not carry a frame.