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

The validated options of a `ConduitMcp.Client.Hub`.

`ConduitMcp.Client.Hub.child_spec/1` and `ConduitMcp.Client.Hub.start_link/1`
run `new!/1`, so a missing `:policy`, a duplicate or invalid server id, or a
bad client option raises an `ArgumentError` naming it when the hub starts.

## Options

* `:name` (`t:term/0`) - Required. Any term naming the hub; every `ConduitMcp.Client.Hub`
  function addresses the hub by it.

* `:policy` - Required. **Required** (R6, PO-2): the `ConduitMcp.Client.Policy` every
  server's calls are decided by - `ConduitMcp.Client.Policy.compile/1`
  options, a compiled policy, `{:named, name}`, or `:allow_all`
  (`ConduitMcp.Client.Policy.allow_all/0`), which must be spelled
  out. Rules match `{server_id, tool}` (PO-3), never the
  exposed name.

* `:servers` - `[{server_id, client_opts}]` (or a keyword list with atom ids),
  started in this order. Ids follow
  `ConduitMcp.Client.Naming.validate_server_id/1` and must be
  unique. `client_opts` are `ConduitMcp.Client` options except
  `:name` and `:server_id`, which the hub sets; governance
  options there override the hub's (see the moduledoc of
  `ConduitMcp.Client.Hub`). The default value is `[]`.

* `:on_ask` - The approver for `:ask` decisions (the client's `:on_ask`).

* `:ask_timeout` (`t:pos_integer/0`) - The client's `:ask_timeout`.

* `:grant_ttl` (`t:pos_integer/0`) - The client's `:grant_ttl`.

* `:hooks` - Hooks (`ConduitMcp.Client.Hooks`) for every server, as a keyword
  list; they run before a server's own `:hooks` of the same event.

* `:hook_timeout` (`t:pos_integer/0`) - The client's `:hook_timeout`.

* `:tools` - A `ConduitMcp.Client.ToolFilter` for every server. A server's own
  `:tools` adds its `:exclude` patterns to these and, when it has
  any, replaces the `:include` patterns.

* `:validate` (`t:keyword/0`) - The client's `:validate`.

* `:max_result_bytes_for_llm` (`t:pos_integer/0`) - The client's `:max_result_bytes_for_llm`.

* `:clock` - The client's `:clock` (tests).

* `:telemetry` - The client's `:telemetry` (payload capture) for every server; a
  server's own `:telemetry` replaces it.

* `:prefix` (`t:String.t/0`) - Prepended to every exposed tool name (`ConduitMcp.Client.Naming`),
  `[A-Za-z0-9_-]`, counted in the 64-byte limit. The default value is `""`.

* `:restart_backoff` (function of arity 1) - `fn attempt -> milliseconds end`: the wait before starting a
  failed server again, `attempt` counting consecutive failures
  from 1 (a server that ran for a minute starts over at 1).
  Default: full jitter from 1 s, doubling, capped at 30 s
  (`ConduitMcp.Client.Backoff.full_jitter/2` with `base: 1_000,
  cap: 30_000`; ST-8).

* `:max_stdio_servers` - The most servers whose transport is a local process
  (`kind/0` `:stdio`, such as `ConduitMcp.Client.Transport.Stdio`)
  the hub runs at once (ST-8). More in `servers:` raises when the
  hub starts; `add_server/3` beyond it returns `{:error,
  %ConduitMcp.Client.Error{kind: :overloaded, reason:
  :max_stdio_servers}}`. `:infinity` lifts the cap. The default value is `16`.

* `:refetch_interval` (`t:non_neg_integer/0`) - The least time in milliseconds between two tool-list fetches the
  hub asks one server for (HB-4). The default value is `1000`.

* `:breaker_threshold` (`t:pos_integer/0`) - Consecutive failed tool-list fetches after which a server's
  cached tools are no longer offered, until a fetch succeeds (HB-4). The default value is `3`.

# `t`

```elixir
@type t() :: %ConduitMcp.Client.Hub.Config{
  breaker_threshold: pos_integer(),
  governance: keyword(),
  max_stdio_servers: non_neg_integer() | :infinity,
  name: term(),
  prefix: String.t(),
  refetch_interval: non_neg_integer(),
  restart_backoff: (pos_integer() -&gt; non_neg_integer()),
  servers: [{String.t(), ConduitMcp.Client.Config.t()}]
}
```

# `new!`

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

Validates `opts`; raises `ArgumentError` naming the offending option,
server id or client option. A `%Config{}` is returned unchanged.

# `server_config!`

```elixir
@spec server_config!(t(), String.t(), keyword()) :: ConduitMcp.Client.Config.t()
```

The `ConduitMcp.Client` config of server `server_id` of the hub: the hub's
governance options overridden by `opts` (hooks and filters combined), named
`{:hub_server, hub, server_id}`, with the hub's token store
(`token_store/1`) when its OAuth options name no `:store`. Raises
`ArgumentError` on a bad option.

# `token_store`

```elixir
@spec token_store(term()) :: ConduitMcp.Client.OAuth.TokenStore.store()
```

The in-memory `ConduitMcp.Client.OAuth.TokenStore.ETS` that hub `name`
starts and gives every server whose OAuth options name no `:store`.

---

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