# `ConduitMcp.Application`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/application.ex#L1)

OTP application for ConduitMCP.

Request handling itself is stateless — each HTTP request runs in its own
Bandit process — but ConduitMCP is **not** a library without processes. It
starts a small supervision tree whose job is to own the long-lived ETS
tables that must outlive short-lived request processes, plus the janitors
that keep them bounded:

  * `ConduitMcp.Cancellation.Owner` — always started.
  * `ConduitMcp.Cancellation.InFlightOwner` — always started; owns the
    in-flight table (one row per request being handled) that gates which
    cancellations are recorded.
  * `ConduitMcp.Cancellation.Janitor` — always started; sweeps cancellation
    rows whose request never cleared them, and in-flight rows whose process
    died without untracking. Disable with
    `config :conduit_mcp, :cancellation_janitor, false`.
  * `ConduitMcp.Session.EtsStore.Owner` — always started.
  * `ConduitMcp.Transport.SSE.Owner` — always started; owns the slot table
    (one row per live stream) behind `Transport.SSE`'s `:max_connections`.
  * `ConduitMcp.Transport.SSEStream.Owner` — always started; owns the slot
    table (one row per open stream) behind the HTTP transports'
    `:max_streams` cap on streamed POST responses.
  * `ConduitMcp.Subscriptions` — always started; a `Registry` (duplicate
    keys) of the `subscriptions/listen` streams served by this node, keyed
    by the notification topics they deliver. The registry's own processes
    only clean up after streams that die; publishing and registering go
    straight to its ETS tables from the calling process.
  * `ConduitMcp.Subscriptions.Admission` — always started; the live
    `subscriptions/listen` streams of each principal behind
    `:max_listens_per_principal`. Unlike the table owners it is called:
    admitting a stream (once per listen) counts, supersedes and records in
    one serialized step. It runs no user code.
  * `ConduitMcp.Session.Janitor` — always started against
    `ConduitMcp.Session.EtsStore`, under the name
    `ConduitMcp.Session.Janitor.Default` so it cannot collide with a janitor
    you start yourself. Disable with
    `config :conduit_mcp, :session_janitor, false`, or tune it with
    `config :conduit_mcp, :session_janitor, ttl: ..., interval: ...`.
  * `ConduitMcp.Tasks.EtsStore.Owner` — started only when the default
    in-memory tasks store is in use (skipped for a custom `:tasks_store`).
  * `ConduitMcp.Ext.Tasks.EtsStore.Owner` and the Ext.Tasks janitor (a
    `ConduitMcp.Session.Janitor` named `ConduitMcp.Ext.Tasks.Janitor`) —
    only when `config :conduit_mcp, :ext_tasks` enables the experimental
    tasks extension; the owner only for the default store, the janitor
    unless `janitor: false`. See `ConduitMcp.Ext.Tasks`.
  * `ConduitMcp.OAuth.KeyProvider.JWKS.Owner` — started when the JWKS key
    provider is compiled in (i.e. when `Req` is available).

Every table above is `:public`; the owners create the table and then idle.
Reads and writes go directly to ETS from the calling process — the owners
are not `handle_call` gateways and must not become ones.
(`ConduitMcp.Subscriptions.Admission` owns no table; see its moduledoc for
why a process answering calls is right there.)

It also starts `ConduitMcp.Client.Registry`, a passive unique `Registry`
(partitioned by scheduler) in which every `ConduitMcp.Client` registers
its processes under tuple keys of its `:name`. The application starts no
client processes: you supervise `{ConduitMcp.Client, opts}` yourself.

`start/2` also seeds the validation config into `:persistent_term` for O(1)
reads on every request; when `config :conduit_mcp, :mrtr_keys` is set,
validates that keyring with `ConduitMcp.MRTR.State.validate_keys!/1` so a
malformed key fails at boot rather than on the first MRTR request; and
validates `:ext_tasks` (`ConduitMcp.Ext.Tasks.validate_config!/1`) and the
`:cache_ttl_ms` / `:cache_scope` defaults
(`ConduitMcp.CacheHints.validate_config!/0`).

---

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