# `ConduitMcp.Client.Transport.Stdio.Framer`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/client/transport/stdio/framer.ex#L1)

Splits a stdio byte stream into newline-delimited MCP messages.

The stdio transport reads its port in `:stream` mode, so chunks arrive in
arbitrary splits; this pure state machine turns them back into messages.

    iex> framer = ConduitMcp.Client.Transport.Stdio.Framer.new()
    iex> {:ok, [], framer} = ConduitMcp.Client.Transport.Stdio.Framer.feed(framer, "{\"a\":")
    iex> {:ok, messages, _framer} = ConduitMcp.Client.Transport.Stdio.Framer.feed(framer, "1}\r\n{}\n")
    iex> messages
    ["{\"a\":1}", "{}"]

## Rules

  * A message ends at `\n`. One trailing `\r` is stripped, so CRLF output
    (Windows servers) frames the same as LF.
  * Blank lines (empty after the `\r` strip) are skipped.
  * Bytes are scanned once: the held partial line is never rescanned, only
    each new chunk is searched for newlines.
  * Bytes after the last newline wait for the next chunk; at end of stream
    they are an incomplete message and are dropped by the caller.

## Oversized messages and resync

A message longer than `:max_message_bytes` (default 8 MiB, `\r` and `\n`
excluded) is detected as soon as the held bytes pass the cap, without
buffering more. The framer then **discards every byte up to and including
the next `\n`** and resumes framing after it, so one oversized message
costs that message only.

`feed/2` reports a discard by returning
`{:error, :message_too_large, messages, state}`: the whole chunk was still
consumed, `messages` holds every complete message in it (before and after
the discarded one), and `state` is ready for the next chunk. A caller that
treats oversize as fatal closes the connection; a lenient caller keeps
feeding.

# `t`

```elixir
@opaque t()
```

# `buffered_bytes`

```elixir
@spec buffered_bytes(t()) :: non_neg_integer()
```

Bytes held for an incomplete message (0 while discarding).

# `feed`

```elixir
@spec feed(t(), binary()) ::
  {:ok, [binary()], t()} | {:error, :message_too_large, [binary()], t()}
```

Feeds the next chunk; returns the complete messages it finished, in order.

# `new`

```elixir
@spec new(keyword()) :: t()
```

A fresh framer.

## Options

  * `:max_message_bytes` - positive integer, default 8 MiB.

---

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