# `Phantom.Plug`
[🔗](https://github.com/dbernheisel/phantom_mcp/blob/main/lib/phantom/plug.ex#L1)

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

<!-- tabs-open -->

### Phoenix

```elixir
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
end
```

### Plug.Router

```elixir
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
      ]
end
```

<!-- tabs-close -->

Here are the defaults:

```elixir
[
  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:

```json
{
  "mcpServers": {
    "my_app": {
      "type": "http",
      "url": "http://localhost:4000/mcp"
    }
  }
}
```

> #### Avoid `mcp-remote` and `mcp-proxy` {: .warning}
>
> 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`

# `opts`

```elixir
@type opts() :: [
  router: module(),
  origins: [String.t()] | :all | mfa(),
  validate_origin: boolean(),
  session_timeout: pos_integer(),
  max_request_size: pos_integer()
]
```

# `www_authenticate`

```elixir
@type www_authenticate() :: %{
  :method =&gt; String.t(),
  optional(String.t() | atom()) =&gt; atom() | String.t()
}
```

# `call`

# `init`

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)

# `www_authenticate`

```elixir
@spec www_authenticate(map()) :: String.t()
```

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

---

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