Main Plug implementation for MCP HTTP transport with SSE support.
This module provides a complete MCP server implementation with:
- JSON-RPC 2.0 message handling
- Server-Sent Events (SSE) streaming
- CORS handling and security features
- Session management integration
- Origin validation
Phoenix
defmodule MyAppWeb.Router do
use MyAppWeb, :router
# ...
pipeline :mcp do
plug :accepts, ["json", "sse"]
plug Plug.Parsers,
parsers: [{:json, length: 1_000_000}],
pass: ["application/json"],
json_decoder: JSON
end
scope "/mcp" do
pipe_through :mcp
forward "/", Phantom.Plug,
router: MyApp.MCPRouter,
pubsub: MyApp.PubSub
end
endPlug.Router
defmodule MyAppWeb.Router do
use Plug.Router
plug :match
plug Plug.Parsers,
parsers: [{:json, length: 1_000_000}],
pass: ["application/json"],
json_decoder: JSON
plug :dispatch
forward "/mcp",
to: Phantom.Plug,
init_opts: [
router: MyApp.MCP.Router,
pubsub: MyApp.PubSub
]
endHere are the defaults:
[
pubsub: nil,
origins: ["http://localhost:4000"],
validate_origin: true,
session_timeout: 30000,
max_request_size: 1048576
]Local testing
Most MCP clients support Streamable HTTP natively. Configure them to connect directly to your Phantom server:
{
"mcpServers": {
"my_app": {
"type": "http",
"url": "http://localhost:4000/mcp"
}
}
}Avoid mcp-remote and mcp-proxy
Third-party proxies like mcp-remote and mcp-proxy can break MCP
features such as elicitation, resource subscriptions, and session
management. Connect directly over HTTP when possible.
If your client only supports stdio, use Phantom.Stdio instead of
proxying HTTP through a stdio wrapper.
For a direct stdio transport without HTTP, see Phantom.Stdio.
Telemetry
Telemetry is provided with these events:
[:phantom, :plug, :request, :connect]with meta:~w[session router conn opts]a[:phantom, :plug, :request, :disconnect]with meta:~w[session router conn]a[:phantom, :plug, :request, :terminate]with meta:~w[session router conn]a[:phantom, :plug, :request, :exception]with meta:~w[session router conn stacktrace request exception]a
Summary
Functions
Callback implementation for Plug.call/2.
Initializes the plug with the given options.
Construct a WWW-Authenticate header as defined by RFC 9728 from the map.
Types
@type opts() :: [ router: module(), origins: [String.t()] | :all | mfa(), validate_origin: boolean(), session_timeout: pos_integer(), max_request_size: pos_integer() ]
Functions
Callback implementation for Plug.call/2.
Initializes the plug with the given options.
Options
:router- The MCP router module (required):origins- List of allowed origins or:all(default: localhost):validate_origin- Whether to validate Origin header (default: true):session_timeout- Session timeout in milliseconds (default: 30s):max_request_size- Maximum request size in bytes (default: 1MB)
Construct a WWW-Authenticate header as defined by RFC 9728 from the map.
This requires the map to contain a :method to indicate acceptable authentication
methods, typically "Bearer", and then rest of the attributes will be serialized
into the header as key=value.
For example,
iex> Phantom.Plug.www_authenticate(%{
...> method: "Bearer",
...> resource_metadata: "https://myapp.com/.well-known/oauth-protected-resource",
...> max_age: 42000
...> })
~s|Bearer max_age="42000", resource_metadata="https://myapp.com/.well-known/oauth-protected-resource"|https://datatracker.ietf.org/doc/html/rfc9728#name-use-of-www-authenticate-for