Wymcp.Plugs.Auth (Wymcp v0.8.3)

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.