MCP.OAuth2.Client (fnord v0.8.71)

View Source

Pure OAuth2 + PKCE client implementation for MCP servers.

Unlike OIDC libraries (like oidcc), this works with OAuth2 Authorization Server discovery (RFC 8414) at /.well-known/oauth-authorization-server, not just OpenID Connect discovery at /.well-known/openid-configuration.

Implements:

  • Authorization Code flow with PKCE (RFC 7636)
  • Token refresh (RFC 6749)
  • OAuth2 server metadata discovery (RFC 8414)

Security:

  • PKCE is always required (S256 challenge method)
  • Tokens are never logged
  • Uses secure random generation for state and verifier

Summary

Functions

Handle OAuth2 callback and exchange authorization code for tokens.

Refresh an expired access token using the refresh token.

Start OAuth2 authorization flow with PKCE.

Types

config()

@type config() :: %{
  :discovery_url => String.t(),
  :client_id => String.t(),
  optional(:client_secret) => String.t(),
  redirect_uri: String.t(),
  scopes: [String.t()]
}

tokens()

@type tokens() :: %{
  access_token: String.t(),
  token_type: String.t(),
  expires_at: non_neg_integer(),
  refresh_token: String.t() | nil,
  scope: String.t() | nil
}

Functions

handle_callback(cfg, params, expected_state, code_verifier)

@spec handle_callback(config(), map(), String.t(), String.t()) ::
  {:ok, tokens()} | {:error, term()}

Handle OAuth2 callback and exchange authorization code for tokens.

Validates state, extracts code, exchanges for tokens with PKCE verifier.

Returns: {:ok, tokens} with normalized token map

refresh_token(cfg, refresh_token)

@spec refresh_token(config(), String.t()) :: {:ok, tokens()} | {:error, term()}

Refresh an expired access token using the refresh token.

Returns: {:ok, tokens} with new access token and possibly new refresh token

start_flow(cfg)

@spec start_flow(config()) ::
  {:ok, %{auth_url: String.t(), state: String.t(), code_verifier: String.t()}}
  | {:error, term()}

Start OAuth2 authorization flow with PKCE.

Fetches server metadata, generates PKCE parameters, and builds authorization URL.

Returns: {:ok, %{auth_url: String.t(), state: String.t(), code_verifier: String.t()}}