# `AttestoMCP.Anubis`
[🔗](https://github.com/XukuLLC/attesto_mcp/blob/v2.1.0/lib/attesto_mcp/anubis.ex#L2)

Bridge a verified resource-server identity into the Anubis MCP frame.

`AttestoMCP.Plug.Authenticate` (or `AttestoPhoenix.Plug.Authenticate`)
verifies the access token and assigns a protocol-shaped context onto the
conn under `:attesto_context`. The Anubis HTTP transport copies
`conn.assigns` into `frame.assigns`, so that context is reachable as
`frame.assigns[:attesto_context]` inside the server.

Anubis's own authorization machinery - the `tools/list` visibility filter,
`Frame.scopes/1`, `Frame.has_scope?/2`, any per-tool scope gate - reads the
identity from `frame.context.auth` (an `Anubis.Server.Context` field), which
is `nil` until a host populates it. `put_auth/1` performs that one
projection: `frame.assigns[:attesto_context]` -> `frame.context.auth`, in the
auth-claims map shape `Anubis.Server.Context` documents.

The bridge does not add application authorization policy such as role,
tenant, or tool-visibility rules. It records the first verified issuer,
subject, and OAuth client that reaches a host callback and rejects a
different identity on a later callback in the same live session.

## Host wiring

Call it in `init/2` so initialization records the owner, then at the top of
every request callback that uses the identity or session state:

    def init(_client_info, frame) do
      {:ok, AttestoMCP.Anubis.put_auth(frame)}
    end

    def handle_request(request, frame) do
      frame = AttestoMCP.Anubis.put_auth(frame)
      # ... Anubis authorization now sees the verified identity ...
    end

The module is compile-guarded on `Anubis.Server.Frame`: a resource server
that does not depend on `anubis_mcp` never compiles it (mirroring how
attesto core gates its optional `Plug` surface on `Plug.Conn`).

# `put_auth`

```elixir
@spec put_auth(Anubis.Server.Frame.t()) :: Anubis.Server.Frame.t()
```

Project the verified `:attesto_context` into `frame.context.auth`.

Idempotent and non-destructive:

  * if `frame.context.auth` is already set, that value is never overwritten
    (a host or an earlier call owns it), while the verified context is still
    checked against the session owner;
  * if `frame.assigns[:attesto_context]` is absent (unauthenticated request,
    STDIO transport, or the auth plug did not run), the frame is returned
    unchanged.

Otherwise `frame.context.auth` is set to the `Anubis.Server.Context`
auth-claims map projected from the context.

The first verified context also records a session-owner marker as described by
`put_auth/2`.

# `put_auth`

```elixir
@spec put_auth(
  Anubis.Server.Frame.t(),
  keyword()
) :: Anubis.Server.Frame.t()
```

Project a verified context using an optional custom assign key.

`:context_key` must be an atom and defaults to `:attesto_context`, matching
`AttestoMCP.Plug.Authenticate`. Both the atom key used by live HTTP requests
and its string form produced by a JSON session-store round-trip are read.

The first verified identity seen by a host callback is recorded in the live
frame. A later callback carrying a different issuer, subject, or OAuth client
raises `SessionOwnerMismatchError`. Anubis reports that exception as a failed
request or session rather than an OAuth 401/403. Anubis handles initialization,
GET/SSE, DELETE, and persisted-session recovery before callbacks, and its 2.0
HTTP recovery path does not restore saved frame assigns. This guard therefore
protects only callback-visible state in the same live session. Hosts requiring
complete session isolation should use `attesto_mcp_server`.

---

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