# `ConduitMcp.Client.Config`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/client/config.ex#L1)

The validated options of a `ConduitMcp.Client`.

`ConduitMcp.Client.child_spec/1` and `ConduitMcp.Client.start_link/1` run
`new!/1`, so a misspelled or invalid option raises an `ArgumentError`
naming the key when the client starts, never at the first request.

## Options

* `:name` (`t:term/0`) - Required. Identifies the client. Any term: it is a key in
  `ConduitMcp.Client.Registry`, never an atom-registered name, so
  `{MyApp.GitHub, tenant_id}` or `{__MODULE__, make_ref()}` work.
  Every `ConduitMcp.Client` function takes it.

* `:server_id` - The operator-assigned id of the server, stamped on every
  `ConduitMcp.Client.Tool`, `Resource` and `Prompt` this client
  returns. Defaults to `:name` when it is a string, else
  `inspect(name)`. The default value is `nil`.

* `:transport` - Required. `{module, opts}`: a module implementing
  `ConduitMcp.Client.Transport` and its options, for example
  `{ConduitMcp.Client.Transport.InProcess, server: MyServer}`.

* `:protocol` - Which protocol era to speak. `:auto` probes `server/discover`
  and falls back to `initialize` only on legacy evidence (see
  `ConduitMcp.Client.Negotiation.classify_probe/3`). `:modern`
  and `:legacy` skip the probe; a version string
  (`"2026-07-28"`, `"2025-11-25"`, `"2025-06-18"`) pins that
  exact version. The default value is `:auto`.

* `:probe_timeout` (`t:pos_integer/0`) - How long `protocol: :auto` waits for the `server/discover`
  probe, in milliseconds. Defaults by transport kind: 5 000 for
  `:http`, 3 000 otherwise.

* `:prior` - A previously observed era (for example a cached `era/1`).
  `:legacy` skips the probe and starts with `initialize`; if
  that is refused, the next attempt probes again. `:modern`
  changes nothing, since `server/discover` is the modern
  handshake. The default value is `nil`.

* `:handler` - A module implementing `ConduitMcp.Client.Handler`. Its callbacks
  decide the advertised client capabilities
  (`ConduitMcp.Client.Negotiation.client_capabilities/2`);
  without one the client advertises `{}`. The default value is `nil`.

* `:client_info` - The `Implementation` sent as `clientInfo`: a string-keyed map
  with at least `"name"` and `"version"`. Defaults to
  `%{"name" => "conduit_mcp", "version" => <library version>}`.

* `:request_timeout` (`t:pos_integer/0`) - Default deadline of each request, in milliseconds, and of the
  connect and handshake steps. A request's `:timeout` overrides
  it. The default value is `60000`.

* `:max_total_timeout` (`t:pos_integer/0`) - With `reset_timeout_on_progress: true`, progress notifications
  push a request's deadline forward, but never past this many
  milliseconds after it was sent. The default value is `300000`.

* `:max_in_flight` (`t:pos_integer/0`) - Requests sent and not yet answered. One more is refused at
  once with `%ConduitMcp.Client.Error{kind: :overloaded}`. The default value is `64`.

* `:max_queued` (`t:pos_integer/0`) - Requests waiting for the connection to become ready. One more
  is refused with `kind: :overloaded`. The default value is `128`.

* `:backoff` (function of arity 1) - `fn attempt -> milliseconds end`: the delay before reconnect
  attempt `attempt` (1, 2, …). Defaults to
  `ConduitMcp.Client.Backoff.full_jitter/1`. It runs in the
  connection process, so it must be pure and fast; one that
  raises or returns anything but a non-negative integer is
  replaced by `full_jitter/1` for that attempt, with a warning. The default value is `&ConduitMcp.Client.Backoff.full_jitter/1`.

* `:max_reconnects` - Consecutive failed reconnect attempts before giving up. The
  count resets once the connection has stayed ready for
  `:stable_after`. The default value is `:infinity`.

* `:on_give_up` - After `:max_reconnects`: `:stay` keeps the connection alive in
  the `:closed` state (so `status/1` explains why, and
  `reconnect/1` can retry); `:stop` exits so a supervisor
  restarts it. The default value is `:stay`.

* `:stable_after` (`t:pos_integer/0`) - Milliseconds the connection must stay ready before the
  reconnect attempt count resets. The default value is `30000`.

* `:health_check_interval` - When set, a ready connection checks the server every this many
  milliseconds (legacy `ping`, modern `server/discover`); two
  unanswered checks in a row restart the transport. The default value is `nil`.

* `:assigns` (`t:map/0`) - Host data passed to `ConduitMcp.Client.Handler` callbacks as
  `ctx.assigns` when a server request is not tied to a call
  (a call's own `:assigns` option wins for requests tied to it). The default value is `%{}`.

* `:handler_timeout` (`t:keyword/0`) - Milliseconds a legacy server-to-client request may run in its
  handler task before it is killed and answered `-32603`, per
  method. Modern input requests run in the caller and are bounded
  by the call. The default value is `[]`.

  * `:sampling` (`t:pos_integer/0`) - The default value is `60000`.

  * `:elicitation` (`t:pos_integer/0`) - The default value is `300000`.

  * `:roots` (`t:pos_integer/0`) - The default value is `60000`.

* `:max_server_requests` (`t:pos_integer/0`) - Legacy server-to-client requests handled at once; one more is
  answered `-32000` (busy) without running the handler. The default value is `8`.

* `:input_rate_limit` (`t:keyword/0`) - The per-server budget of input requests (sampling, elicitation,
  roots) in both eras. Over it, sampling is refused with the
  sampling `:rejection_code` and the others with `-32000`. The default value is `[]`.

  * `:per_second` (`t:pos_integer/0`) - The default value is `10`.

  * `:burst` (`t:pos_integer/0`) - The default value is `20`.

* `:log_rate_limit` (`t:keyword/0`) - The per-connection budget of `notifications/message` log entries
  from the server. Over it, entries are dropped before they reach
  subscribers or `handle_log/3`, and each drop emits
  `[:conduit_mcp, :client, :log, :dropped]`. The default value is `[]`.

  * `:per_second` (`t:pos_integer/0`) - The default value is `50`.

  * `:burst` (`t:pos_integer/0`) - The default value is `100`.

* `:sampling` (`t:keyword/0`) - Safety settings for `sampling/createMessage` (used only when the
  handler implements `handle_sampling/2`). `:max_tokens` clamps the
  server's `maxTokens`. `:context` declares `sampling.context`
  and lets `includeContext` through; otherwise it is coerced to
  `"none"`. `:tools` declares `sampling.tools`. `:allow_idle`
  accepts legacy sampling requests that arrive while no call is in
  flight (refused by default). `:max_tool_iterations` bounds a
  sampling tool loop: a request whose `messages` already hold
  more assistant `tool_use` turns than this is refused. Refusals
  use `:rejection_code`. The default value is `[]`.

  * `:max_tokens` (`t:pos_integer/0`) - The default value is `4096`.

  * `:context` (`t:boolean/0`) - The default value is `false`.

  * `:tools` (`t:boolean/0`) - The default value is `false`.

  * `:allow_idle` (`t:boolean/0`) - The default value is `false`.

  * `:max_tool_iterations` (`t:pos_integer/0`) - The default value is `5`.

  * `:rejection_code` (`t:integer/0`) - The default value is `-1`.

* `:elicitation` (`t:keyword/0`) - `:origin` - the server's origin (`"https://host[:port]"`) that a
  URL-mode elicitation URL is compared with, for transports that do
  not report one. The handler gets `origin_mismatch?: true` for any
  other origin, and when no origin is known. The default value is `[]`.

  * `:origin` - The default value is `nil`.

* `:roots` (`t:keyword/0`) - `:list_changed` - declare `roots.listChanged` to legacy servers
  (the host announces changes with
  `ConduitMcp.Client.notify_roots_changed/1`). Used only when the
  handler implements `list_roots/1`. The default value is `[]`.

  * `:list_changed` (`t:boolean/0`) - The default value is `true`.

* `:max_cache_ttl` (`t:pos_integer/0`) - The longest a cached list (`ConduitMcp.Client.Cache`) is served
  without asking the server again, whatever its `ttlMs` hint says. The default value is `3600000`.

* `:policy` - Authorizes tool calls (`ConduitMcp.Client.Policy`): the
  `ConduitMcp.Client.Policy.compile/1` options, a compiled
  policy, or `{:named, name}` to read the policy stored with
  `ConduitMcp.Client.Policy.update/2` at each call (a missing
  one denies). `nil` (the default) means no authorization: the
  client sends what it is asked to (R6). Setting it routes
  `call_tool/4` through `ConduitMcp.Client.Pipeline`. The default value is `nil`.

* `:on_ask` - The approver for calls the policy decides `:ask`:
  `fun.(%ConduitMcp.Client.ToolCall{}, ctx)` or `{Module,
  :function}`, returning `{:allow, :once | :always}`,
  `{:deny, reason}` or `{:deny, reason, :halt}` (see
  `ConduitMcp.Client.Pipeline`). Without one, `:ask` denies. The default value is `nil`.

* `:ask_timeout` (`t:pos_integer/0`) - Milliseconds `:on_ask` may take; after that the call is denied. The default value is `60000`.

* `:grant_ttl` (`t:pos_integer/0`) - How long an `{:allow, :always}` answer is remembered, in
  milliseconds (`ConduitMcp.Client.Grants`). The default value is `86400000`.

* `:approved_fingerprints` - The tool definitions the policy's `pin_tools` and
  `allow_new_tools` treat as approved: `:first_listing` records
  the fingerprints of the first full tool list the client reads;
  a map of tool name to `ConduitMcp.Client.Fingerprint` pins
  exactly those. The default value is `:first_listing`.

* `:hooks` - Pre-call, post-call, failure, listing, resource and prompt
  hooks; see `ConduitMcp.Client.Hooks`. The default value is `[]`.

* `:hook_timeout` (`t:pos_integer/0`) - Default milliseconds a hook may run before it fails (closed). The default value is `5000`.

* `:tools` - Which tools are visible at all: `[include: [...], exclude:
  [...]]` (`ConduitMcp.Client.ToolFilter`). Hidden tools are left
  out of every tool list and calling one fails with
  `kind: :not_found`. The default value is `nil`.

* `:validate` (`t:keyword/0`) - JSON Schema validation of call arguments (`inputSchema`) and
  of `structuredContent` (`outputSchema`); see
  `ConduitMcp.Client.SchemaValidator`. The default validator
  needs the optional `:jsv` dependency: asking for validation
  without it raises `ConduitMcp.OptionalDependencyError` here. The default value is `[]`.

  * `:arguments` (`t:boolean/0`) - The default value is `false`.

  * `:structured_content` (`t:boolean/0`) - The default value is `false`.

  * `:validator` (`t:atom/0`) - The default value is `ConduitMcp.Client.SchemaValidator.JSV`.

* `:trusted` (`t:boolean/0`) - Trust this server's tool annotations in policy annotation
  rules (PO-8) and its `instructions` in
  `ConduitMcp.Client.instructions_for_llm/1`. The default value is `false`.

* `:max_result_bytes_for_llm` (`t:pos_integer/0`) - The cap on a tool result shaped for a model (`shape: :llm`,
  see `ConduitMcp.Client.Pipeline`). The default value is `102400`.

* `:include_instructions` (`t:boolean/0`) - Whether `ConduitMcp.Client.instructions_for_llm/1` returns the
  server's `instructions` (PI-1). Off by default. The default value is `false`.

* `:clock` - `fn -> unix_milliseconds end` used for grant expiry (default
  the system clock); for tests. The default value is `nil`.

* `:telemetry` (`t:keyword/0`) - Telemetry settings. Client events carry allowlisted metadata
  only (see `ConduitMcp.Telemetry`); payload capture is the one
  opt-in exception. The default value is `[]`.

  * `:capture_payloads` - Opt-in capture of tool-call payloads in the
    `[:conduit_mcp, :client, :tool_call, ...]` span (TL-2). `false`
    (the default): the span's metadata has no `:arguments` or
    `:result` key at all. To enable it, pass `[redact: fun, max_bytes:
    n]`: `:redact` (**required**; `true` alone is rejected) is a
    one-arity function run in the calling process on the arguments
    and on the result before they reach any handler, and `:max_bytes`
    (default 4 096) caps the JSON size of what it returns. See
    "Payload capture" in `ConduitMcp.Telemetry`. The default value is `false`.

# `protocol`

```elixir
@type protocol() :: :auto | :modern | :legacy | String.t()
```

# `t`

```elixir
@type t() :: %ConduitMcp.Client.Config{
  assigns: map(),
  backoff: (pos_integer() -&gt; non_neg_integer()),
  capabilities: %{modern: map(), legacy: map()},
  capture_payloads:
    false | %{redact: (term() -&gt; term()), max_bytes: pos_integer()},
  client_info: %{required(String.t()) =&gt; term()},
  elicitation: keyword(),
  governance: struct(),
  handler: module() | nil,
  handler_timeout: keyword(),
  health_check_interval: pos_integer() | nil,
  input_rate_limit: keyword(),
  log_rate_limit: keyword(),
  max_cache_ttl: pos_integer(),
  max_in_flight: pos_integer(),
  max_queued: pos_integer(),
  max_reconnects: non_neg_integer() | :infinity,
  max_server_requests: pos_integer(),
  max_total_timeout: pos_integer(),
  name: term(),
  on_give_up: :stay | :stop,
  prior: :modern | :legacy | nil,
  probe_timeout: pos_integer(),
  protocol: protocol(),
  request_timeout: pos_integer(),
  roots: keyword(),
  sampling: keyword(),
  server_id: String.t(),
  stable_after: pos_integer(),
  transport: {module(), keyword()}
}
```

# `new!`

```elixir
@spec new!(keyword() | t()) :: t()
```

Validates `opts` and returns the config; raises `ArgumentError` naming the
offending key. A `%Config{}` is returned unchanged.

The governance options (`:policy` through `:clock` above) are compiled
into the `governance` field.

## Example

    iex> config =
    ...>   ConduitMcp.Client.Config.new!(
    ...>     name: :docs,
    ...>     transport: {ConduitMcp.Client.Transport.InProcess, server: ConduitMcp.TestServer}
    ...>   )
    iex> {config.protocol, config.probe_timeout, config.max_in_flight}
    {:auto, 3000, 64}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
