# `Snodo.Client.HTTP`
[🔗](https://github.com/joshrotenberg/snodo/blob/v0.1.0/lib/snodo/client/http.ex#L1)

Streamable HTTP transport for `Snodo.Client.connect({:http, url}, opts)`.

Each request is one `POST` through OTP's `:httpc`, so no connection or
process outlives it. The request headers come from the protocol dialect's
`transport_policy/1`, the same declaration the server admits requests
against: the accepted and request media types, and every mirrored header
(for `2026-07-28`, `MCP-Protocol-Version`, `Mcp-Method`, and `Mcp-Name`) read
from the request body. A mirrored value that is not plain printable ASCII is
sent in the `=?base64?...?=` form when the policy allows it.

The response may be `application/json` or `text/event-stream`. From an event
stream the transport returns the response whose ID matches the request and
drops notifications such as progress. A JSON-RPC error body is returned
whatever the HTTP status, so `Snodo.Client` decodes it as `{:error,
%Snodo.Error{}}`. Anything else is a -32000 transport error with the status and
body in `cause`. A timeout closes the connection, which the server treats as
cancellation.

Options:

  * `:headers` - extra request headers as `{name, value}` string pairs, for
    example `[{"authorization", "Bearer " <> token}]`.
  * `:ssl` - `:ssl` client options for `https` URLs. The default verifies the
    peer against `:public_key.cacerts_get/0` and checks the host name.
  * `:connect_timeout` - milliseconds to establish the connection. Defaults
    to the request timeout.

# `state`

```elixir
@type state() :: %{
  url: charlist(),
  headers: [{charlist(), charlist()}],
  ssl: keyword(),
  connect_timeout: timeout() | nil
}
```

# `encode_sentinel`

```elixir
@spec encode_sentinel(String.t()) :: String.t()
```

Encodes a mirrored header value with the base64 sentinel when it is not
plain printable ASCII, or when the plain value could be mistaken for one.

---

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