barrel_mcp_http_engine (barrel_mcp v2.0.2)
View SourceTransport-neutral MCP HTTP engine.
Holds the protocol logic for both the simple HTTP transport and the Streamable HTTP transport (POST/GET/DELETE/OPTIONS, SSE, sessions, CORS, Origin validation, authentication, the OAuth protected-resource-metadata endpoint and async tool calls) WITHOUT any dependency on a concrete HTTP server.
A binding (the built-in barrel_mcp_http_listener h1/h2 server, or an external adapter such as Livery's) reads the request line and body, then calls handle/6 with:
Method— the request method binary (<<"POST">>…).Path— the request target (query string allowed; it is stripped here).Headers— a[{binary(), binary()}]proplist. Lookups are case-insensitive.Body— the full request body (<<>>when none).Responder— a map of I/O closures (see below).Config— the engine configuration (see theconfig()type).
The Responder abstracts response delivery so the engine never touches a socket:
#{reply => fun((Status, Headers, Body) -> ok),
stream_start => fun((Status, Headers) -> ok),
stream_chunk => fun((iodata()) -> ok | {error, term()}),
stream_end => fun(() -> ok)}Headers passed to the closures is a [{binary(), binary()}] proplist (lowercase names). A streaming (SSE) response is stream_start then repeated stream_chunk then stream_end.
handle/6 runs in the calling (per-request) process. For a long-lived GET SSE stream it blocks in a receive loop until the session is terminated or the binding signals a client disconnect by sending the calling process the message mcp_disconnect.
Summary
Types
-type responder() :: #{reply := fun((non_neg_integer(), [{binary(), binary()}], iodata()) -> ok), stream_start := fun((non_neg_integer(), [{binary(), binary()}]) -> ok), stream_chunk := fun((iodata()) -> ok | {error, term()}), stream_end := fun(() -> ok)}.