Plug-compatible Streamable HTTP boundary for modern and legacy MCP.
Every protected MCP leg authenticates through AttestoMCP.Plug.ProtectResource
before package-owned request decoding or registry dispatch. A host parser
placed earlier in the endpoint runs before this Plug and must apply its own
input limit. Metadata discovery is the one intentionally public route
defined by RFC 9728. Hosts may select
request-scoped tool streams with stream_tools or stream_all_tools; both
options are validated during init/1.
:auth accepts either a keyword list or a zero-arity remote callback/MFA
returning one. Resolver-backed authentication is evaluated for every
applicable MCP or metadata request, so runtime authorization configuration
is not captured by router compilation. The trusted resolver may supply the
canonical resource or origin at runtime; it is checked against the configured
Plug path and reused for metadata and audience verification. A resolver may
not replace the boundary's canonical assign keys, and failures fail the
request closed.
A host may keep that canonical resource pinned for RFC 9728 metadata while
accepting tokens for an explicit set of alternate gateway identifiers by
supplying Attesto core's :trusted_audiences inside :auth. The list
replaces the single-resource audience policy, so it must include the
canonical resource when direct clients should remain accepted.
:principal_binding may derive a bounded stable identity from the complete
authenticated principal without replacing the value handlers receive.
:client_ip may derive the canonical IPv4 or IPv6 tuple used by both
authenticated and failed-authentication rate-limit buckets. It accepts a
one-argument function or MFA receiving the Plug.Conn; when omitted,
conn.remote_ip is used verbatim. Explicit callback failures and non-IP
returns fail closed and emit neutral client_ip/exception telemetry.
:authorize is a separate mount-level business-policy callback. It receives
the authenticated base context once per protected HTTP leg; only literal
true permits the request, and denial is a neutral 403 before POST body
reading or protocol dispatch.
Summary
Types
Supported Plug boundary options.
Per-method opt-ins to definition-based HTTP authorization.
An explicit definition-based authorization mode for one MCP method.
Functions
Authenticates and serves one HTTP request through the MCP boundary.
Initializes a Plug state for an already-supervised server and pinned auth boundary.
Types
Supported Plug boundary options.
@type plug_option() :: {:server, pid() | atom()} | {:path, String.t()} | {:auth, keyword() | auth_options_resolver()} | {:scope_map, map()} | {:scope_policy, scope_policy()} | {:default_scopes, [String.t()]} | {:scopes_supported, [String.t()]} | {:context_builder, (Plug.Conn.t() -> map()) | {module(), atom()} | {module(), atom(), [term()]}} | {:principal_binding, (term() -> term()) | {module(), atom()} | {module(), atom(), [term()]}} | {:client_ip, (Plug.Conn.t() -> :inet.ip_address()) | {module(), atom()} | {module(), atom(), [term()]}} | {:authorize, (map() -> boolean()) | {module(), atom()} | {module(), atom(), [term()]}} | {:subscription_scopes, [String.t()]} | {:max_body_bytes, pos_integer()} | {:max_message_bytes, pos_integer()} | {:allow_http, boolean()} | {:allow_dynamic_origin, boolean()} | {:origin, String.t()} | {:base_url, String.t()} | {:resource, String.t()} | {:resource_audience, String.t()} | {:stream_keepalive_ms, pos_integer()} | {:legacy_keepalive_ms, pos_integer()} | {:subscription_timeout, pos_integer()} | {:stream_queue_size, pos_integer()} | {:subscription_queue_size, pos_integer()} | {:max_queue, pos_integer()} | {:stream_all_tools, boolean()} | {:stream_tools, [String.t()]}
@type scope_policy() :: %{optional(String.t()) => scope_policy_mode()}
Per-method opt-ins to definition-based HTTP authorization.
@type scope_policy_mode() :: :visible_definitions | :selected_definition
An explicit definition-based authorization mode for one MCP method.
Functions
@spec call(Plug.Conn.t(), map()) :: Plug.Conn.t()
Authenticates and serves one HTTP request through the MCP boundary.
@spec init([plug_option()]) :: map()
Initializes a Plug state for an already-supervised server and pinned auth boundary.