AttestoMCP.Anubis (AttestoMCP v2.1.0)

Copy Markdown View Source

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).

Summary

Functions

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

Project a verified context using an optional custom assign key.

Functions

put_auth(frame)

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(frame, opts)

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.