ExMCP Configuration Guide

View Source

This guide covers the supported configuration surfaces for ExMCP 1.0.

Dependency

def deps do
  [
    {:ex_mcp, "~> 1.0.0-rc.1"}
  ]
end

Protocol Version

ExMCP supports:

  • 2024-11-05
  • 2025-03-26
  • 2025-06-18
  • 2025-11-25

The latest supported version is returned by ExMCP.protocol_version/0.

config :ex_mcp,
  protocol_version: "2025-11-25"

Validate versions with the public negotiator:

ExMCP.Protocol.VersionNegotiator.valid_version?("2025-11-25")

Client Configuration

You can pass options directly to ExMCP.Client.start_link/1:

{:ok, client} =
  ExMCP.Client.start_link(
    transport: :http,
    url: "https://api.example.com/mcp",
    use_sse: true,
    request_timeout: 30_000
  )

Or build a reusable config with ExMCP.ClientConfig:

config =
  ExMCP.ClientConfig.new(:production)
  |> ExMCP.ClientConfig.put_transport(:http, url: "https://api.example.com/mcp")
  |> ExMCP.ClientConfig.put_auth(:bearer, token: System.fetch_env!("MCP_TOKEN"))
  |> ExMCP.ClientConfig.put_retry_policy(max_attempts: 3, base_interval: 500)

{:ok, client} = ExMCP.connect(config)

stdio

{:ok, client} =
  ExMCP.Client.start_link(
    transport: :stdio,
    command: ["node", "server.js"],
    cd: "/path/to/project",
    env: [{"NODE_ENV", "production"}],
    timeout: 30_000
  )

Supported options:

  • :command
  • :cd
  • :env
  • :timeout

HTTP/SSE

{: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,
    max_retry_delay: 60_000
  )

Supported options include:

  • :url
  • :endpoint
  • :headers
  • :use_sse
  • :session_id
  • :protocol_version
  • :timeout
  • :request_timeout
  • :stream_handshake_timeout
  • :stream_idle_timeout
  • :max_retry_delay
  • :security
  • :auth
  • :auth_provider

BEAM-Local

{:ok, server} = MyServer.start_link(transport: :beam)  # works when using DSL; otherwise use HandlerServer.start_link(handler: MyServer, ...)

{:ok, client} =
  ExMCP.Client.start_link(
    transport: :beam,
    server: server,
    timeout: 5_000
  )

transport: :beam is local to the current VM and requires a server PID. Keep any pooling, service discovery, or process selection in your application layer.

Server Configuration

Servers (DSL or raw handlers) can be started with:

MyServer.start_link(transport: :beam)                    # DSL modules get this
MyServer.start_link(transport: :stdio)
MyServer.start_link(transport: :http, port: 4000, sse_enabled: true)

# For a raw handler module (no DSL):
ExMCP.Server.HandlerServer.start_link(handler: MyHandler, transport: :beam)
# or the convenience:
ExMCP.start_server(handler: MyHandler, transport: :stdio)

Phoenix/Plug applications usually mount ExMCP.HttpPlug:

forward "/mcp", ExMCP.HttpPlug,
  handler: MyApp.MCPServer,
  server_info: %{name: "my-app", version: "1.0.0"},
  sse_enabled: true,
  cors_enabled: true

Resilience

Retries:

  ExMCP.Client.start_link(
    transport: :http,
    url: "https://api.example.com/mcp",
  retry_policy: [max_attempts: 3, initial_delay: 100, max_delay: 2_000]
)

Circuit breaker and health checks:

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]
  ]
)

Logging

For stdio servers, stdout must contain only JSON-RPC messages. ExMCP configures stdio logging when stdio mode starts. Send ad hoc diagnostics to stderr:

IO.puts(:stderr, "debug")

For HTTP and BEAM-local development:

Logger.configure(level: :debug)

Security

HTTP clients can use headers:

ExMCP.Client.start_link(
  transport: :http,
  url: "https://api.example.com/mcp",
  headers: [{"Authorization", "Bearer #{token}"}]
)

For server-side HTTP concerns, compose Plug/Phoenix pipelines before ExMCP.HttpPlug.