# `Snodo.Transport.Stdio`
[🔗](https://github.com/joshrotenberg/snodo/blob/v0.2.1/lib/snodo/transport/stdio.ex#L1)

Concurrent newline-delimited JSON-RPC transport.

A small coordinator owns framing and serializes complete stdout writes. Work
is admitted through the optional, transport-neutral `Snodo.Server.Executor`, so
the coordinator never awaits a handler and execution policy is reusable by
other transports.

`:write_timeout` is a positive millisecond limit, defaulting to 5,000. A single
linked, monitored helper performs each write while the coordinator waits
boundedly; there is no writer queue. Cancellation and EOF handling can wait
up to this limit during a blocked write. A timeout or output error is terminal:
the transport cancels its work and closes subscriptions, without attempting
further writes. A timed-out device may already have accepted some bytes, so
the transport never retries. Supplied I/O devices are never stopped or killed.

`:max_line_bytes` limits one input message, defaulting to 2,000,000 bytes to
match the HTTP listener's `:max_body_bytes`. A longer line is answered with
-32600 and a null id without being decoded, and the next line is served
normally. The VM's I/O server buffers each line in full before returning it,
so this bounds decoding and dispatch, not that read buffer.

An initialize-era client (2025-11-25 or 2025-06-18, when the runtime enables
those dialects) negotiates its version once with `initialize`. The transport
records the version from a successful result and gives every later message
on the connection an `mcp-protocol-version` request header, the header HTTP
requests carry, so dialect selection and validation are shared with HTTP. A
2026-07-28 client does not send `initialize`, and its messages carry no such
header.

# `state`

```elixir
@type state() :: %{
  runtime: Snodo.Server.Runtime.t(),
  input: IO.device(),
  output: IO.device(),
  writer: {IO.device(), pos_integer()},
  executor: pid(),
  executor_monitor: reference() | nil,
  serve_owner_monitor: reference() | nil,
  owns_executor?: boolean(),
  request_timeout: Snodo.Server.Executor.execution_timeout() | :default,
  reader: pid() | nil,
  connection_ref: reference(),
  executions_by_id: map(),
  executions_by_ref: map(),
  subscriptions_by_id: map(),
  subscriptions_by_worker: map(),
  max_subscriptions: pos_integer(),
  max_line_bytes: pos_integer(),
  negotiated_version: String.t() | nil,
  eof?: boolean()
}
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `serve`

```elixir
@spec serve(Snodo.Server.Runtime.t(), keyword()) :: :ok | {:error, term()}
```

Runs the stdio transport until EOF and all admitted requests finish.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
