# `GenMCP.Transport.StreamableHTTP`
[🔗](https://github.com/lud/gen_mcp/blob/main/lib/gen_mcp/transport/streamable_http.ex#L29)

HTTP plug implementing the MCP Streamable HTTP transport for the `2026-07-28`
protocol.

This is the entry point of an MCP server over HTTP. It is a `Plug.Router`, so
you mount it in a Plug or Phoenix router and it answers Model Context Protocol
requests at that path. A client POSTs a JSON-RPC message, and the transport
replies with either a single JSON response or a Server-Sent Events stream,
depending on what the server returns.

The transport is stateless: each request is validated, run by a fresh server
process, and answered on its own. The server that handles the decoded request
is `GenMCP.Suite` by default,
so the smallest useful mount needs no `:server` option and just lists what to
serve:

    forward "/mcp", GenMCP.Transport.StreamableHTTP,
      server_name: "My App",
      server_version: "1.0.0",
      tools: [MyApp.AddTool]

`:server_name` and `:server_version` are what the Suite reports to clients
and are required; every example below carries them for that reason.

To run a custom `GenMCP` implementation instead of the Suite, pass it as
`:server`:

    forward "/mcp", GenMCP.Transport.StreamableHTTP, server: MyApp.Server

### Options

Options are read at three layers. The transport keeps the HTTP connection
options for itself and passes everything else to the server, which takes its own
wrapper options and forwards the rest to the server implementation.

#### HTTP connection options

* `:assigns` (`t:map/0`) - A map of assigns to define to the channel passed to tools, resources, etc. The default value is `%{}`.

* `:copy_assigns` (list of `t:atom/0`) - A list of assigns keys that will be copied from the conn to the channel. Those will overwrite the assigns from the `:assigns` option above. The default value is `[]`.

* `:allowed_origins` - Origin allowlist for DNS-rebinding protection. A request carrying an `Origin` header not in the list is rejected with 403 Forbidden. Requests without an `Origin` header (non-browser clients) are always accepted. Use `:any` to disable the check (e.g. behind a gateway that already validates origins). The default value is `[]`.

#### MCP server options

`:server` selects the `GenMCP` implementation that handles the decoded request.
See `GenMCP` for the `{module, arg}` form.

* `:server` - The `GenMCP` behaviour server implemetation that will handle MCP messages. If a simple atom, it will receive all other options given to the session. The default value is `GenMCP.Suite`.

#### Server implementation options

Every option not listed above is forwarded to the server implementation. The
default server, `GenMCP.Suite`, accepts:

* `:server_name` (`t:String.t/0`) - Required.

* `:server_version` (`t:String.t/0`) - Required.

* `:server_title` - The default value is `nil`.

* `:tools` - The list of `GenMCP.Suite.Tool` implementations that will be available in the server. List items can be either module names, `{module, arg}` tuples or descriptor maps. The default value is `[]`.

* `:resources` - The list of `GenMCP.Suite.ResourceRepo` implementations to serve resources from. List items can be either module names, `{module, arg}` tuples or descriptor maps. The default value is `[]`.

* `:prompts` - A list of `GenMCP.Suite.PromptRepo` implementations to generate prompts with. List items can be either module names, `{module, arg}` tuples or descriptor maps. The default value is `[]`.

* `:extensions` - A list `Extension` implementations to add more tools, resource repositories and prompt repositories. List items can be either module names, `{module, arg}` tuples or descriptor maps. The default value is `[]`.

* `:subscription_handler` - A handler for subscription requests. Either a module name, a `{module, arg}` tuple or a descriptor map. The default value is `nil`.

* `:send_server_info` (`t:boolean/0`) - Includes the server's `io.modelcontextprotocol/serverInfo` metadata (built from `:server_name`, `:server_version`, and `:server_title`) in the `_meta` of every result, as the spec recommends. The default value is `true`.

### Passing request data to handlers

Handlers read per-request context from the `GenMCP.Mux.Channel` they are given.
Use `:assigns` and `:copy_assigns` to put your own data there. `:assigns` holds
static values set on every channel. `:copy_assigns` lists `conn` assign keys to
copy from the connection onto the channel, which is how an upstream
authentication plug hands the authenticated identity to your tools: the plug
puts `:current_user` on the conn, and you copy it across.

    pipeline :mcp_auth do
      plug MyAppWeb.AuthPlug
    end

    scope "/mcp" do
      pipe_through :mcp_auth

      forward "/", GenMCP.Transport.StreamableHTTP,
        server_name: "My App",
        server_version: "1.0.0",
        tools: [MyApp.AddTool],
        copy_assigns: [:current_user]
    end

A copied conn assign overwrites a static `:assigns` entry of the same key.

### DNS-rebinding protection

A browser-based client sends an `Origin` header. The transport rejects a
request whose `Origin` is not in `:allowed_origins` with `403 Forbidden`, which
stops a malicious page from rebinding DNS to reach a local MCP server. A request
with no `Origin` header (a non-browser client) is always accepted. Set
`allowed_origins: :any` to disable the check, for example behind a gateway that
already validates the origin.

    forward "/mcp", GenMCP.Transport.StreamableHTTP,
      server_name: "My App",
      server_version: "1.0.0",
      tools: [MyApp.AddTool],
      allowed_origins: ["https://app.example.com"]

### Multiple endpoints in one router

On Phoenix 1.8 and later you can mount the transport directly as many times as
you need. Each `forward` keeps its own options, so different endpoints can serve
different tools, resources, or origins:

    scope "/mcp" do
      forward "/files", GenMCP.Transport.StreamableHTTP,
        server_name: "My App (files)",
        server_version: "1.0.0",
        tools: [MyApp.FileTool]

      forward "/admin", GenMCP.Transport.StreamableHTTP,
        server_name: "My App (admin)",
        server_version: "1.0.0",
        tools: [MyApp.AdminTool]
    end

Phoenix resolves a forwarded plug to a path by module, so reverse route lookup
(path helpers and `~p` verified routes) for a module mounted at several paths
returns only the first one. This affects URL generation, not request dispatch.
If you generate URLs to these endpoints, give each its own module with
`defplug/1` so each resolves to its own path.

#### Phoenix before 1.8

Older Phoenix routers refuse to forward the same module more than once and raise
at compile time:

    ** (ArgumentError) GenMCP.Transport.StreamableHTTP has already been
    forwarded to. A module can only be forwarded a single time.

Give each endpoint its own module with `defplug/1`, then forward to those
modules instead of to the transport:

    require GenMCP.Transport.StreamableHTTP, as: StreamableHTTP

    StreamableHTTP.defplug(MyAppWeb.FilesMcp)
    StreamableHTTP.defplug(MyAppWeb.AdminMcp)

    scope "/mcp" do
      forward "/files", MyAppWeb.FilesMcp,
        server_name: "My App (files)",
        server_version: "1.0.0",
        tools: [MyApp.FileTools]

      forward "/admin", MyAppWeb.AdminMcp,
        server_name: "My App (admin)",
        server_version: "1.0.0",
        tools: [MyApp.AdminTools]
    end

# `call`

# `defplug`
*macro* 

Defines a named plug module that delegates to `GenMCP.Transport.StreamableHTTP`.

Use this to mount more than one MCP endpoint on a router that allows a module to
be forwarded only once. Phoenix before 1.8 is the common case: forwarding
`GenMCP.Transport.StreamableHTTP` at two paths raises `ArgumentError` with the
message "has already been forwarded to. A module can only be forwarded a single
time". The generated module is a distinct plug that delegates both `init/1` and
`call/2` to the transport, so you forward to it exactly like the transport
itself and each endpoint is its own module. On Phoenix 1.8 and later you can
forward to `GenMCP.Transport.StreamableHTTP` directly instead, even for several
endpoints.

This is a macro, so `require` (or alias and require) the transport before
calling it, and give a literal module name:

    require GenMCP.Transport.StreamableHTTP, as: StreamableHTTP

    StreamableHTTP.defplug(MyAppWeb.McpPlug)

    # then, in the router, mount the generated module
    forward "/mcp", MyAppWeb.McpPlug,
      server_name: "My App",
      server_version: "1.0.0",
      tools: [MyApp.AddTool]

# `init`

Initializes the plug, returning the prepared transport configuration.

This is the `c:Plug.init/1` callback. It validates the transport's own options
(`:allowed_origins`, `:assigns`, `:copy_assigns`) and keeps every other option
aside as the server options handed to the server on each request. The returned
value is the opaque configuration later passed to `call/2`; you do not build or
read it yourself.

---

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