Snodo.Client.HTTP (snodo v0.3.0)

Copy Markdown View Source

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

Each request is one HTTP/1.1 POST on its own :gen_tcp or :ssl connection, closed before the request returns, 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. A header value that contains CR, LF, or NUL is refused with a -32000 transport error.

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.

The response is read by this module rather than :httpc, which reads the body of any status other than 200 and 206 in full before returning it. :max_response_bytes is checked as the response arrives, whatever its status: a Content-Length over the limit is refused before the body is read, a chunked body is refused at the first chunk that would pass it, and a body that ends when the connection closes is refused at the read that passes it. An event stream is one body, so the limit applies to the whole stream, notifications included. The status line and headers are held to the same limit. Over the limit the connection is closed and the request returns a -32000 transport error with cause: {:max_response_bytes, limit}.

Options:

  • :headers - extra request headers as {name, value} string pairs, for example [{"authorization", "Bearer " <> token}]. The transport owns host, content-type, content-length, transfer-encoding, and connection, and refuses them here.
  • :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.
  • :max_response_bytes - the largest response to accept, default 16 MiB, the stdio client's line limit.

Summary

Functions

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.

Types

state()

@type state() :: %{
  scheme: String.t(),
  host: charlist(),
  port: :inet.port_number(),
  authority: String.t(),
  target: String.t(),
  socket_options: [:gen_tcp.connect_option()],
  headers: [{String.t(), String.t()}],
  ssl: keyword(),
  connect_timeout: timeout() | nil,
  max_response_bytes: pos_integer()
}

Functions

encode_sentinel(value)

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