ExMCP.ClientConfig (ex_mcp v0.9.0)

View Source

Configuration builder for ExMCP clients.

This module provides a fluent interface for building client configurations, ensuring type safety and validation at compile time.

Usage

config = ExMCP.ClientConfig.new()
         |> ExMCP.ClientConfig.put_transport(:http)
         |> ExMCP.ClientConfig.put_url("https://api.example.com")
         |> ExMCP.ClientConfig.put_transport_options(
           timeout: 30_000,
           pool_size: 10
         )

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

Transport Configuration

stdio Transport

config = ExMCP.ClientConfig.new()
         |> ExMCP.ClientConfig.put_transport(:stdio)
         |> ExMCP.ClientConfig.put_command(["python", "server.py"])
         |> ExMCP.ClientConfig.put_args(["--verbose"])
         |> ExMCP.ClientConfig.put_env([{"DEBUG", "1"}])

HTTP Transport

config = ExMCP.ClientConfig.new()
         |> ExMCP.ClientConfig.put_transport(:http)
         |> ExMCP.ClientConfig.put_url("https://api.example.com")
         |> ExMCP.ClientConfig.put_headers([{"Authorization", "Bearer token"}])
         |> ExMCP.ClientConfig.put_transport_options(
           use_sse: true,
           timeout: 10_000
         )

Advanced Options

config = ExMCP.ClientConfig.new()
         |> ExMCP.ClientConfig.put_name(:my_client)
         |> ExMCP.ClientConfig.put_handler(MyClientHandler)
         |> ExMCP.ClientConfig.put_retry_policy(
           max_attempts: 3,
           backoff: :exponential
         )

Basic Usage

# Create a simple HTTP configuration
config = ExMCP.ClientConfig.new(:http, url: "http://localhost:8080")
{:ok, client} = ExMCP.connect(config)

Advanced Usage

# Create a production configuration with full settings
config = ExMCP.ClientConfig.new(:production)
|> ExMCP.ClientConfig.put_transport(:http, url: "https://api.example.com")
|> ExMCP.ClientConfig.put_retry_policy(max_attempts: 5, base_interval: 1000)
|> ExMCP.ClientConfig.put_timeout(connect: 10_000, request: 30_000)
|> ExMCP.ClientConfig.put_auth(:bearer, token: "...")

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

Configuration Profiles

Built-in profiles:

  • :development - For local development with debugging
  • :test - For testing with fast timeouts
  • :production - For production with robust retry policies
  • :http - HTTP transport with sensible defaults
  • :stdio - Stdio transport configuration
  • :native - Native BEAM transport configuration

Summary

Functions

Timeout configuration for ExMCP operations.

Adds a fallback transport configuration.

Gets the list of all transport configurations (primary + fallbacks).

Creates a new client configuration.

Configures authentication settings.

Sets client information.

Adds custom configuration options.

Configures observability settings.

Configures connection pooling settings.

Configures retry policy settings.

Configures timeout settings.

Configures the transport settings.

Converts the configuration to a keyword list suitable for client APIs.

Validates the configuration and returns errors if any.

Types

auth_config()

@type auth_config() :: %{
  type: auth_type(),
  token: String.t() | nil,
  username: String.t() | nil,
  password: String.t() | nil,
  headers: %{required(String.t()) => String.t()},
  refresh_token: String.t() | nil,
  client_id: String.t() | nil,
  client_secret: String.t() | nil,
  custom_handler: {module(), atom(), [any()]} | nil
}

auth_type()

@type auth_type() :: :none | :bearer | :basic | :oauth | :custom

log_level()

@type log_level() :: :debug | :info | :warn | :error | :none

observability_config()

@type observability_config() :: %{
  logging: %{
    enabled: boolean(),
    level: log_level(),
    format: :text | :json,
    include_request_id: boolean(),
    include_metadata: boolean()
  },
  telemetry: %{
    enabled: boolean(),
    prefix: [atom()],
    include_system_metrics: boolean()
  },
  tracing: %{
    enabled: boolean(),
    sampler: atom() | {module(), atom(), [any()]},
    propagators: [atom()]
  }
}

pool_config()

@type pool_config() :: %{
  enabled: boolean(),
  size: pos_integer(),
  max_overflow: non_neg_integer(),
  checkout_timeout: pos_integer(),
  idle_timeout: pos_integer()
}

profile()

@type profile() :: atom()

retry_policy()

@type retry_policy() :: %{
  enabled: boolean(),
  max_attempts: pos_integer(),
  base_interval: pos_integer(),
  max_interval: pos_integer(),
  backoff_type: :linear | :exponential | :fixed,
  jitter: boolean()
}

t()

@type t() :: %ExMCP.ClientConfig{
  auth: auth_config(),
  client_info: %{name: String.t(), version: String.t(), user_agent: String.t()},
  custom_options: %{required(atom()) => any()},
  fallback_transports: [transport_config()],
  observability: observability_config(),
  pool: pool_config(),
  profile: profile() | nil,
  retry_policy: retry_policy(),
  timeouts: timeout_config(),
  transport: transport_config()
}

timeout_config()

@type timeout_config() :: %{
  total: pos_integer(),
  connect: pos_integer(),
  request: pos_integer(),
  stream: %{handshake: pos_integer(), idle: pos_integer()},
  pool: %{checkout: pos_integer(), idle: pos_integer()}
}

transport_config()

@type transport_config() :: %{
  type: transport_type(),
  url: String.t() | nil,
  command: String.t() | [String.t()] | nil,
  host: String.t(),
  port: integer(),
  path: String.t(),
  ssl: boolean(),
  headers: %{required(String.t()) => String.t()},
  options: keyword()
}

transport_type()

@type transport_type() :: :http | :stdio | :sse | :native | :beam

Functions

%ExMCP.ClientConfig{}

(struct)

Timeout configuration for ExMCP operations.

  • total: Maximum time for an entire logical operation, including all retries (ms)
  • connect: Time to establish initial TCP/TLS connection (ms)
  • request: Time for a single request-response cycle (ms)
  • stream: Timeouts specific to persistent streams like SSE
    • handshake: Time to wait for stream handshake after connection (ms)
    • idle: Time a stream can be idle before being considered dead (ms)
  • pool: Connection pool related timeouts
    • checkout: Time to wait for a connection from the pool (ms)
    • idle: Time a connection can sit idle in the pool (ms)

add_fallback(config, transport_type, opts \\ [])

@spec add_fallback(t(), transport_type(), keyword()) :: t()

Adds a fallback transport configuration.

Examples

config = ExMCP.ClientConfig.new(:http, url: "http://primary:8080")
|> ExMCP.ClientConfig.add_fallback(:http, url: "http://backup:8080")
|> ExMCP.ClientConfig.add_fallback(:stdio, command: "local-server")

get_all_transports(config)

@spec get_all_transports(t()) :: [transport_config()]

Gets the list of all transport configurations (primary + fallbacks).

Examples

transports = ExMCP.ClientConfig.get_all_transports(config)
# Try connecting to each transport in order

new()

@spec new() :: t()

Creates a new client configuration.

Examples

# Use a predefined profile
config = ExMCP.ClientConfig.new(:production)

# Create HTTP configuration with custom options
config = ExMCP.ClientConfig.new(:http, url: "http://localhost:8080")

# Create stdio configuration
config = ExMCP.ClientConfig.new(:stdio, command: ["python", "server.py"])

# Start with empty configuration
config = ExMCP.ClientConfig.new()

new(profile_or_transport)

@spec new(profile() | transport_type()) :: t()

new(transport_type, opts)

@spec new(
  transport_type(),
  keyword()
) :: t()

put_auth(config, auth_type, opts \\ [])

@spec put_auth(t(), auth_type(), keyword()) :: t()

Configures authentication settings.

Examples

# Bearer token
config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_auth(:bearer, token: "your-token")

# Basic auth
config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_auth(:basic, username: "user", password: "pass")

# Custom headers
config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_auth(:custom, headers: %{"X-API-Key" => "key"})

put_client_info(config, opts)

@spec put_client_info(
  t(),
  keyword()
) :: t()

Sets client information.

Examples

config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_client_info(
  name: "MyApp MCP Client",
  version: "1.0.0"
)

put_custom(config, key, value)

@spec put_custom(t(), atom(), any()) :: t()

Adds custom configuration options.

Examples

config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_custom(:my_option, "value")
|> ExMCP.ClientConfig.put_custom(:another_option, %{key: "value"})

put_observability(config, opts)

@spec put_observability(
  t(),
  keyword()
) :: t()

Configures observability settings.

Examples

config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_observability(
  logging: [enabled: true, level: :info],
  telemetry: [enabled: true, prefix: [:my_app, :mcp]]
)

put_pool(config, opts)

@spec put_pool(
  t(),
  keyword()
) :: t()

Configures connection pooling settings.

Examples

config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_pool(
  enabled: true,
  size: 10,
  max_overflow: 5
)

put_retry_policy(config, opts)

@spec put_retry_policy(
  t(),
  keyword()
) :: t()

Configures retry policy settings.

Examples

config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_retry_policy(
  max_attempts: 5,
  base_interval: 1000,
  backoff_type: :exponential
)

put_timeout(config, opts)

@spec put_timeout(
  t(),
  keyword()
) :: t()

Configures timeout settings.

Examples

# Basic timeouts
config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_timeout(
  total: 120_000,
  connect: 10_000,
  request: 30_000
)

# Advanced timeouts with stream and pool settings
config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_timeout(
  total: 120_000,
  connect: 10_000,
  request: 30_000,
  stream: [handshake: 15_000, idle: 60_000],
  pool: [checkout: 5_000, idle: 300_000]
)

Timeout Types

  • total: Maximum time for entire operation including retries (ms)
  • connect: Time to establish TCP/TLS connection (ms)
  • request: Time for single request-response cycle (ms)
  • stream.handshake: Time to wait for SSE handshake completion (ms)
  • stream.idle: Time stream can be idle before considered dead (ms)
  • pool.checkout: Time to wait for connection from pool (ms)
  • pool.idle: Time connection can idle in pool before cleanup (ms)

put_transport(config, transport_type, opts \\ [])

@spec put_transport(t(), transport_type(), keyword()) :: t()

Configures the transport settings.

Examples

config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_transport(:http, url: "http://localhost:8080")

config = ExMCP.ClientConfig.new()
|> ExMCP.ClientConfig.put_transport(:stdio, command: ["python", "server.py"])

to_client_opts(config)

@spec to_client_opts(t()) :: keyword()

Converts the configuration to a keyword list suitable for client APIs.

Examples

config = ExMCP.ClientConfig.new(:http, url: "http://localhost:8080")
opts = ExMCP.ClientConfig.to_client_opts(config)
{:ok, client} = ExMCP.Client.start_link(opts)

validate(config)

@spec validate(t()) :: :ok | {:error, [String.t()]}

Validates the configuration and returns errors if any.

Examples

case ExMCP.ClientConfig.validate(config) do
  :ok -> {:ok, client} = ExMCP.connect(config)
  {:error, errors} -> handle_config_errors(errors)
end