ExMCP.Authorization.Provider behaviour (ex_mcp v0.9.2)

View Source

Behaviour for pluggable OAuth/auth providers in MCP transports.

Instead of embedding OAuth logic directly in the HTTP transport, a Provider encapsulates token storage, retrieval, and the authentication flow. The transport delegates all auth concerns to the provider.

Built-in Providers

Custom Providers

Implement the behaviour to integrate with your own auth system:

defmodule MyAuthProvider do
  @behaviour ExMCP.Authorization.Provider

  @impl true
  def init(config) do
    {:ok, %{api_key: config[:api_key]}}
  end

  @impl true
  def get_token(state) do
    {:ok, state.api_key, state}
  end

  @impl true
  def handle_unauthorized(_www_auth, _scopes, state) do
    {:error, :unauthorized, state}
  end

  @impl true
  def handle_forbidden(_www_auth, _scopes, state) do
    {:error, :forbidden, state}
  end
end

Usage with Transport

{:ok, client} = ExMCP.Client.start_link(
  transport: :http,
  url: "https://api.example.com/mcp",
  auth_provider: {MyAuthProvider, api_key: "sk-..."}
)

Summary

Callbacks

Get the current access token, if available.

Handle a 403 Forbidden response (typically scope step-up).

Handle a 401 Unauthorized response from the server.

Initialize the provider with configuration.

Types

config()

@type config() :: map() | keyword()

state()

@type state() :: any()

Callbacks

get_token(state)

@callback get_token(state()) :: {:ok, String.t() | nil, state()} | {:error, term()}

Get the current access token, if available.

Returns {:ok, token, new_state} if a valid token is available, or {:ok, nil, state} if no token is available yet (pre-auth).

handle_forbidden(www_authenticate, scopes, state)

@callback handle_forbidden(
  www_authenticate :: String.t() | nil,
  scopes :: [String.t()],
  state()
) :: {:ok, String.t(), state()} | {:error, term(), state()}

Handle a 403 Forbidden response (typically scope step-up).

Similar to handle_unauthorized/3 but for scope-related rejections. The provider should attempt re-authorization with broader scopes.

Returns {:ok, token, new_state} on success, {:error, reason, new_state} on failure.

handle_unauthorized(www_authenticate, scopes, state)

@callback handle_unauthorized(
  www_authenticate :: String.t() | nil,
  scopes :: [String.t()],
  state()
) :: {:ok, String.t(), state()} | {:error, term(), state()}

Handle a 401 Unauthorized response from the server.

The provider should attempt to authenticate (e.g., run OAuth flow, refresh token, etc.) and return a new token.

  • www_authenticate — the WWW-Authenticate header value (may be nil)
  • scopes — extracted scopes from the challenge (may be empty)

Returns {:ok, token, new_state} on success, {:error, reason, new_state} on failure.

init(config)

@callback init(config()) :: {:ok, state()} | {:error, term()}

Initialize the provider with configuration.

Called once when the transport is created. Returns initial provider state.