Wymcp. Plugs. Auth
(Wymcp v0.8.7)
View Source
Authentication plug for MCP requests.
Reads the auth module from the mount module's options
(conn.assigns[:wymcp][:auth])
and calls its Wymcp.Auth.authenticate/1 callback. When no auth
module is configured, defaults to Wymcp.Auth.Noop (pass-through).
On authentication failure, returns HTTP 401 with a WWW-Authenticate
challenge as the MCP specification requires. By default the challenge is
bare Bearer; consumers may append RFC 6750 auth-params (an RFC 9728
resource_metadata pointer, a scope hint) via the router's
:www_authenticate option — see Wymcp.Router. If rendering the configured
params raises (e.g. a misconfigured MFA), the challenge degrades to bare
Bearer for that request and the error is logged naming the option — the
401 contract survives misconfiguration. The response body is a JSON-RPC
error with code -32600 (Invalid Request).
Observability
A 401 from either branch is a rejection:
it carries the rejection mark and emits [:wymcp, :wire, :reject] like
every other rejection wymcp sends, with reason :unauthenticated when the
auth module returned {:error, reason} and :auth_error when it raised.
The consumer's reason — a string, or an atom the wire renders as its
string — rides that event as message.
The raise branch emits a second event, [:wymcp, :auth, :error] (see
Wymcp.Telemetry): the rejection records that the client got a 401, the
fault records that the consumer's module is broken, and the two are
orthogonal. Its metadata names the module, the exception class and
message, the body's method and message_id, and crash_reason — the
exception and its stacktrace, which a handler cannot rebuild from the
strings.
The telemetry message_id is read straight from the body and deliberately
differs from the id the 401's envelope carries: the wire answer applies
Wymcp.Response.rejection_id/1, which is nil on every non-request
message, while an operator diagnosing a rejection wants the id the client
actually sent. That is why request_field/2 survives at the two
observability sites and nowhere else.
Neither branch writes a Logger line of its own: Wymcp.Telemetry.Logger
renders both events, and a consumer wanting different lines writes its own
handler rather than living with wymcp's.