Snodo.Transport.StreamableHTTP.Server (snodo v0.4.1)

Copy Markdown View Source

Small dependency-free HTTP/1.1 listener for Snodo.Transport.StreamableHTTP.

It is intentionally a binding, not a web framework. Each accepted connection carries one request and closes after one response. Header admission happens in the connection process; admitted MCP work runs through the reusable bounded Snodo.Server.Executor. A peer disconnect while work is pending cancels that execution and tears down its reply owner.

:request_timeout bounds queue wait plus execution after HTTP admission; the transport's default ceiling is 30 seconds, including with an application-owned executor. Progress does not extend the deadline. Explicit :infinity disables this ceiling. Body reads and socket writes have their own transport bounds.

Connection and stream bounds, each a positive integer:

  • :max_connections - the most connections open at once, default 1,024. A connection accepted at the limit is closed without being read.
  • :head_timeout - milliseconds from accept until the request head must be complete, default 10,000. :read_timeout (default 5,000) still bounds each read; this deadline bounds the whole head, so a client that sends it a byte at a time is closed when the deadline passes.
  • :request_gate_timeout - milliseconds allowed for a request gate to check the head, default 10,000. A timed-out gate gets a 504 response.
  • :body_timeout - milliseconds from the end of the head, or from gate admission when a gate is configured, until the request body must be read, default 10,000. Each body read returns what has arrived and waits at most :read_timeout; this deadline bounds the whole body.
  • :max_subscriptions - the most subscriptions/listen streams open at once, default 256. The executor holds one count per listener and returns a slot when the connection serving that stream exits. A stream over the limit is closed at its source and the request gets 503.
  • :drain_timeout - milliseconds the listener waits on shutdown for open connections to finish, default 5,000.

Shutdown

The listener drains when it terminates, including when its supervisor stops it:

  1. The listening socket closes, so new connections are refused.
  2. A connection whose request head or body is still arriving gets 503 and closes. A request read in full before the drain began is admitted and runs to its response.
  3. Each open subscriptions/listen stream gets its successful completion response, its source is closed with reason :shutdown, and the connection closes.
  4. Connections still open when :drain_timeout passes are killed, which cancels their executor work and closes their subscription sources.
  5. An executor the listener started is stopped. An application-owned executor keeps running.

child_spec/1 sets the child's :shutdown to :drain_timeout plus 5,000 ms, the default worker shutdown, so the supervisor does not kill the listener during the drain. A child spec that overrides :shutdown must keep it above :drain_timeout. A listener that is killed does not drain.

Applications that already run Plug, Bandit, or Cowboy can translate their request into Snodo.Transport.StreamableHTTP.Request and use the pure adapter directly instead of starting this listener.

The listener has no built-in authentication. Set :request_gate to a {module, options} implementing Snodo.Transport.StreamableHTTP.RequestGate to serve discovery routes and authenticate requests before reading their bodies. A verified identity returned by the gate is available to Snodo.Authorization. snodo_oauth provides an OAuth resource-server gate.

The listener serves plaintext HTTP. It warns when bound outside loopback, including when a request gate is configured. Bind it privately behind a TLS reverse proxy, or run Snodo.Transport.Plug in an HTTP server with TLS.

Summary

Functions

Returns the bound IP, actual port, and configured MCP endpoint path.

Returns a worker child spec whose :shutdown exceeds :drain_timeout by 5,000 ms. An invalid :drain_timeout is rejected when the listener starts.

Returns a URL for a listener bound to a local IPv4 or IPv6 address.

Functions

address(server)

Returns the bound IP, actual port, and configured MCP endpoint path.

child_spec(init_arg)

Returns a worker child spec whose :shutdown exceeds :drain_timeout by 5,000 ms. An invalid :drain_timeout is rejected when the listener starts.

url(server)

@spec url(GenServer.server()) :: String.t()

Returns a URL for a listener bound to a local IPv4 or IPv6 address.