# `AttestoMCP.Telemetry`
[🔗](https://github.com/XukuLLC/attesto_mcp/blob/v2.2.3/lib/attesto_mcp/telemetry.ex#L1)

`:telemetry` events for the MCP authentication and scope boundaries.

Every `AttestoMCP.Plug.Authenticate` call, and so every
`AttestoMCP.Plug.ProtectResource` authentication, is wrapped in a span, as
is every `AttestoMCP.Plug.RequireScopes` decision. A `:stop` event names the
outcome with one atom from a bounded set, so an operator can count how
often clients fail to authenticate and why without reading request logs.

## Events

### `[:attesto_mcp, :authenticate, :start | :stop | :exception]`

`:start` measurements: `:system_time` and `:monotonic_time`. `:stop`
measurements: `:duration` (native time units) and `:monotonic_time`. `:stop`
metadata:

  * `:outcome` - one of the outcomes below.
  * `:status` - the HTTP status of a refusal (401, 403, or 503); `nil` when
    the request was authenticated.
  * `:binding` - `:bearer`, `:dpop`, or `:mtls` for an authenticated
    request; `nil` for a refusal.

`:exception` carries `:duration` and the metadata `:kind` only; the
exception is re-raised afterwards. Every event carries a
`:telemetry_span_context` reference shared by the span's events.

### `[:attesto_mcp, :require_scopes, :start | :stop | :exception]`

The same measurements. `:stop` metadata is `:outcome` (`:ok`,
`:insufficient_scope`, or `:unauthenticated`) and `:status`.

## Outcomes

  * `:ok` - authenticated, or every required scope is granted.
  * `:missing_credentials` - no access token, or a malformed
    `Authorization` header.
  * `:invalid_token` - the token failed verification for a reason not
    listed separately: signature, format, or claims.
  * `:token_expired` and `:token_not_yet_valid` - the token's validity
    window excludes the current time.
  * `:invalid_audience` and `:invalid_issuer` - the token names another
    resource or another issuer.
  * `:sender_constraint_mismatch` - the token is bound to a DPoP key or a
    client certificate that the request did not present, presented a
    different one, or presented one the token is not bound to.
  * `:invalid_dpop_proof` - the DPoP proof failed verification.
  * `:dpop_replay` - the DPoP proof's `jti` had already been seen.
  * `:dpop_nonce_required` - the client must retry with the issued
    `DPoP-Nonce` (RFC 9449 §8).
  * `:step_up_required` - the token does not meet the route's RFC 9470
    step-up requirement.
  * `:policy_rejected` - the host's `:principal` callback refused the
    verified claims.
  * `:insufficient_scope` - the token lacks a required scope.
  * `:unauthenticated` - a scope check ran on a connection no
    authentication boundary had verified.
  * `:verifier_unavailable` - the verifier's dependency (JWKS, introspection)
    could not be reached, so the request was answered 503.
  * `:other` - a refusal outside this set.

## What metadata contains

Outcomes, statuses, and bindings only. Tokens, DPoP proofs, claims,
principals, granted or required scopes, `error_description` text, callback
return values, and exception reasons never enter telemetry metadata.

# `outcome`

```elixir
@type outcome() ::
  :ok
  | :missing_credentials
  | :invalid_token
  | :token_expired
  | :token_not_yet_valid
  | :invalid_audience
  | :invalid_issuer
  | :sender_constraint_mismatch
  | :invalid_dpop_proof
  | :dpop_replay
  | :dpop_nonce_required
  | :step_up_required
  | :policy_rejected
  | :insufficient_scope
  | :unauthenticated
  | :verifier_unavailable
  | :other
```

A bounded authentication or authorization outcome.

# `outcome`

```elixir
@spec outcome(Plug.Conn.t()) :: outcome() | nil
```

The outcome recorded on a connection by the last `AttestoMCP` plug that
authenticated or refused it, or `nil` when none has run.

It is stored under `conn.private[:attesto_mcp_auth_outcome]`, which a host
or a downstream package may read directly.

---

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