Anubis.Server.Context (anubis_mcp v2.2.0)

Copy Markdown

Read-only session and request context, set by the SDK before each callback.

The Session process builds a fresh Context before every user callback invocation. Mutations have no lasting effect — the Session always overwrites it.

For STDIO transport, headers is empty, remote_ip is nil, and auth is nil. For HTTP transport, headers are normalized to lowercase string keys.

Auth field

When OAuth 2.1 authorization is configured on the server, auth contains the normalized claims map extracted from the validated bearer token:

%{
  sub: "user-id",
  aud: "https://api.example.com",
  scope: "tools:read tools:write",
  scopes: ["tools:read", "tools:write"],
  exp: 1_234_567_890,
  iat: 1_234_567_800,
  client_id: "client-abc",
  raw_claims: %{}
}

auth is nil when no authorization is configured or the transport is STDIO.

Init meta field

init_meta carries the _meta map the client sent on its initialize request params (the MCP extension namespace), available to every callback including init/2. Empty map when the client sent none. Metadata sent under clientInfo._meta is preserved inside client_info itself.

Per-request fields

  • session_pid - the Session process serving this request. Use it to send notifications back to the client from any process, including a task spawned inside a tool callback. nil outside a session.
  • request_meta - the _meta map the client sent on this request's params. This is where progressToken and the io.modelcontextprotocol/tasks extension declaration live. Empty map when the client sent none.

Protocol era fields

protocol_version, protocol_module, client_capabilities and log_level describe the protocol the current request speaks.

Under the :legacy era they are settled once by the initialize handshake and hold for the session. Under the :stateless era every request carries its own, so they are rebuilt per request and never inherited — the specification forbids a server from inferring capabilities from earlier requests. client_info follows the same rule.

They are nil (and client_capabilities empty) before a legacy session has been initialized.

Multi round-trip fields

input_responses holds the answers a client sent to an Anubis.Server.InputRequired, keyed as the input requests were, and request_state the verified term a handler attached with Anubis.Server.InputRequired.state/2. They are set for the tools/call, prompts/get and resources/read request that carries them, and are empty and nil otherwise. request_digest is a SHA-256 of that request's parameters, less _meta, inputResponses and requestState: the signed state is bound to it, so a retry must repeat the original request.

Summary

Types

auth_claims()

@type auth_claims() :: %{
  sub: String.t() | nil,
  aud: String.t() | [String.t()] | nil,
  scope: String.t() | nil,
  scopes: [String.t()],
  exp: integer() | nil,
  iat: integer() | nil,
  client_id: String.t() | nil,
  raw_claims: map()
}

t()

@type t() :: %Anubis.Server.Context{
  auth: auth_claims() | nil,
  client_capabilities: map(),
  client_info: map() | nil,
  headers: %{required(String.t()) => String.t()},
  init_meta: map(),
  input_responses: %{required(String.t()) => map()},
  log_level: String.t() | nil,
  protocol_module: module() | nil,
  protocol_version: String.t() | nil,
  remote_ip: :inet.ip_address() | nil,
  request_digest: binary() | nil,
  request_meta: map(),
  request_state: term(),
  session_id: String.t() | nil,
  session_pid: pid() | nil
}