# ExMCP Transport Guide
ExMCP supports stdio, streamable HTTP/SSE, BEAM-local, and test transports.
## Overview
| Transport | Identifier | Best For |
|-----------|------------|----------|
| stdio | `:stdio` | Official MCP subprocess transport |
| Streamable HTTP/SSE | `:http` | Remote servers and Phoenix apps |
| BEAM-local | `:beam` | Local Elixir client/server pairs |
| Test | `:test` | In-memory tests |
## stdio
The stdio transport spawns an external MCP server process and exchanges
newline-delimited JSON-RPC.
```elixir
{:ok, client} =
ExMCP.Client.start_link(
transport: :stdio,
command: ["node", "server.js"],
cd: "/path/to/project",
env: [{"NODE_ENV", "production"}]
)
```
Supported options:
- `:command` - executable plus arguments as a list.
- `:cd` - subprocess working directory.
- `:env` - environment variables as `{"KEY", "VALUE"}` tuples.
- `:timeout` - client operation timeout.
## Streamable HTTP/SSE
The HTTP transport sends JSON-RPC over HTTP POST. With `use_sse: true`, it also
uses SSE for server-to-client streaming.
```elixir
{:ok, client} =
ExMCP.Client.start_link(
transport: :http,
url: "https://api.example.com/mcp",
use_sse: true,
headers: [{"Authorization", "Bearer #{token}"}],
request_timeout: 30_000,
stream_handshake_timeout: 15_000,
stream_idle_timeout: 60_000
)
```
Supported client options include:
- `:url` - base URL or full MCP endpoint URL.
- `:endpoint` - endpoint path when it is not included in `url`.
- `:headers` - additional request headers.
- `:use_sse` - enable SSE response stream, defaults to `true`.
- `:session_id` - resume an existing streamable HTTP session.
- `:protocol_version` - requested MCP protocol version.
- `:timeout` - connect timeout.
- `:request_timeout` - single request timeout.
- `:stream_handshake_timeout` - wait for SSE stream startup.
- `:stream_idle_timeout` - allowed SSE idle time.
- `:max_retry_delay` - cap for SSE reconnect delay.
- `:security` - client-side security validation configuration.
- `:auth` / `:auth_provider` - OAuth/auth provider integration.
### Phoenix/Plug Server
```elixir
scope "/mcp" do
pipe_through [:api, :mcp_auth]
forward "/", ExMCP.HttpPlug,
handler: MyApp.MCPServer,
server_info: %{name: "my-app", version: "1.0.0"},
sse_enabled: true,
cors_enabled: true
end
```
Put HTTP concerns in Plug pipelines before `ExMCP.HttpPlug`: authentication,
request signing, rate limiting, CORS/origin decisions, and DNS rebinding checks.
## BEAM-Local
The BEAM-local transport carries MCP-shaped maps/lists as Elixir terms between
local processes. It does not JSON encode/decode in the transport, but it still
uses MCP initialize, request IDs, capabilities, and handler callbacks.
```elixir
{:ok, server} = MyServer.start_link(transport: :beam) # DSL modules provide start_link/1; for raw handlers use HandlerServer or ExMCP.start_server
{:ok, client} =
ExMCP.Client.start_link(
transport: :beam,
server: server
)
```
Supported options:
- server side: `transport: :beam` on a DSL/handler server.
- client side: `transport: :beam`, `server: pid`, optional `timeout`.
**Tip:** For a fast local verification of these BEAM + DSL + Client patterns (no re-installs), run `mix examples.getting_started` from the project root after `mix compile`.
BEAM-local does not provide service discovery or distributed registry behavior.
If you need a pool or registry of server processes, keep that in your
application supervision layer and pass the selected server PID to the client.
## Test Transport
Use `:test` for in-memory tests where both endpoints are in the same process
tree:
```elixir
{:ok, server} =
ExMCP.Server.HandlerServer.start_link(
transport: :test,
handler: MyServer
)
{:ok, client} =
ExMCP.Client.start_link(
transport: :test,
server: server
)
```
## Reliability
Client retries:
```elixir
ExMCP.Client.start_link(
transport: :http,
url: "https://api.example.com/mcp",
retry_policy: [max_attempts: 3, initial_delay: 100, max_delay: 2_000]
)
```
Transport wrapper:
```elixir
ExMCP.Client.start_link(
transport: :http,
url: "https://api.example.com/mcp",
reliability: [
circuit_breaker: [failure_threshold: 5, reset_timeout: 30_000],
health_check: [check_interval: 60_000]
]
)
```
## Telemetry
Transports emit connection and message telemetry. BEAM-local events use the
generic transport event names with `metadata.transport == :beam`:
- `[:ex_mcp, :transport, :connection, :opened]`
- `[:ex_mcp, :transport, :message, :sent]`
- `[:ex_mcp, :transport, :message, :received]`
## Selection Guide
- Use `:stdio` for official subprocess MCP servers.
- Use `:http` for network boundaries and Phoenix integrations.
- Use `:beam` for trusted local Elixir processes.
- Use `:test` for tests.