ConduitMcp.Application (ConduitMCP v0.11.0)

Copy Markdown View Source

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).