barrel_mcp_ctx (barrel_mcp v3.0.0)

View Source

Per-request context.

MCP 2026-07-28 made the protocol stateless: a request carries its own protocol version, client capabilities and identity in _meta instead of inheriting them from an initialize handshake. This module builds one context per request from the decoded JSON-RPC envelope and answers questions about it, so nothing else in the library has to reach into _meta by hand.

Every request belongs to one of two eras:

  • modern: params._meta carries io.modelcontextprotocol/protocolVersion. Stateless, no session.
  • legacy: everything else, including every initialize. Version and capabilities live on the session and are supplied by the transport through Extra.

Building a context never fails, so a malformed request still yields something the caller can inspect. Use validate/1 to check that a modern request carries the _meta fields the spec requires.

Summary

Functions

The authenticated principal, as returned by the auth provider.

Capabilities the client declared for this request.

The client's self-reported name and version, or undefined. Self-reported and unverified: use it for display and logging, never for security decisions.

The elicitation modes this client declared. "An empty capabilities object is equivalent to declaring support for form mode only" (2026-07-28/client/elicitation.mdx:67).

Which era this request belongs to.

Build the context for one decoded JSON-RPC request or notification. Never fails.

One MRTR response by key, raw as it arrived.

Every response the client supplied on an MRTR retry, keyed by the identifiers the server assigned in inputRequests.

Whether this request uses per-request metadata (2026-07-28+).

Minimum log level the server should emit for this request, or undefined. When it is undefined the server must emit no notifications/message at all for this request.

The raw params._meta map, for callers that need a key this module does not expose (progress tokens, trace context, extensions).

The stable identity behind the credential, as barrel_mcp_auth:authenticate/3 derived it.

Protocol version for this request. undefined for a legacy request whose session has not recorded one yet.

The opaque MRTR state the client echoed back, still signed. Callers verify it through barrel_mcp_request_state rather than reading it here.

Session this request belongs to, or undefined. Always undefined for a modern request: the era has no sessions.

Whether the transport can hold a response stream open.

Whether the client declared a core capability, e.g. elicitation, sampling or roots. A server must not ask for something the client did not declare.

Whether the client declared an extension, by identifier (e.g. ?MCP_EXT_TASKS).

Check that a modern request carries the _meta fields the spec marks required, and that the optional ones it does carry are usable. A failure is malformed params and the caller must reject it with ?JSONRPC_INVALID_PARAMS (HTTP 400).

Check the stated revision on its own, before anything is judged against it. A peer naming a revision we do not speak may legitimately carry a _meta shape we would misjudge, so the version error has to win; but a version that is absent or not a string is a _meta problem, and answering "unsupported version: undefined" names the wrong one.

Types

ctx/0

-type ctx() ::
          #{era := era(),
            protocol_version := binary() | undefined,
            client_info := map() | undefined,
            client_capabilities := map(),
            log_level := binary() | undefined,
            input_responses := map(),
            request_state := binary() | undefined,
            session_id := binary() | undefined,
            auth_info := term(),
            streaming := boolean(),
            meta := map()}.

era/0

-type era() :: modern | legacy.

extra/0

-type extra() ::
          #{session_id => binary() | undefined,
            auth_info => term(),
            streaming => boolean(),
            protocol_version => binary() | undefined,
            client_capabilities => map(),
            transport_version => binary() | undefined}.

Functions

auth_info(_)

-spec auth_info(ctx()) -> term().

The authenticated principal, as returned by the auth provider.

client_capabilities(_)

-spec client_capabilities(ctx()) -> map().

Capabilities the client declared for this request.

client_info(_)

-spec client_info(ctx()) -> map() | undefined.

The client's self-reported name and version, or undefined. Self-reported and unverified: use it for display and logging, never for security decisions.

elicitation_modes(Ctx)

-spec elicitation_modes(ctx()) -> [binary()].

The elicitation modes this client declared. "An empty capabilities object is equivalent to declaring support for form mode only" (2026-07-28/client/elicitation.mdx:67).

era(_)

-spec era(ctx()) -> era().

Which era this request belongs to.

from_request(Request)

-spec from_request(map()) -> ctx().

Equivalent to from_request(Request, #{}).

from_request(Request, Extra)

-spec from_request(map(), extra()) -> ctx().

Build the context for one decoded JSON-RPC request or notification. Never fails.

input_response(Ctx, Key)

-spec input_response(ctx(), binary()) -> {ok, map()} | none.

One MRTR response by key, raw as it arrived.

input_responses(_)

-spec input_responses(ctx()) -> map().

Every response the client supplied on an MRTR retry, keyed by the identifiers the server assigned in inputRequests.

is_modern(_)

-spec is_modern(ctx()) -> boolean().

Whether this request uses per-request metadata (2026-07-28+).

log_level(_)

-spec log_level(ctx()) -> binary() | undefined.

Minimum log level the server should emit for this request, or undefined. When it is undefined the server must emit no notifications/message at all for this request.

meta(_)

-spec meta(ctx()) -> map().

The raw params._meta map, for callers that need a key this module does not expose (progress tokens, trace context, extensions).

principal(Ctx)

-spec principal(ctx()) -> term().

The stable identity behind the credential, as barrel_mcp_auth:authenticate/3 derived it.

This is what owns a task, a sealed request state or an elicitation, never the whole auth_info() map: that carries exp and jti, so a refreshed token would read as a different caller and orphan whatever was started under the old one.

anonymous when the request carried no credential at all.

protocol_version(_)

-spec protocol_version(ctx()) -> binary() | undefined.

Protocol version for this request. undefined for a legacy request whose session has not recorded one yet.

request_state(_)

-spec request_state(ctx()) -> binary() | undefined.

The opaque MRTR state the client echoed back, still signed. Callers verify it through barrel_mcp_request_state rather than reading it here.

session_id(_)

-spec session_id(ctx()) -> binary() | undefined.

Session this request belongs to, or undefined. Always undefined for a modern request: the era has no sessions.

streaming(_)

-spec streaming(ctx()) -> boolean().

Whether the transport can hold a response stream open.

false unless the transport says otherwise, because most cannot: stdio has one output channel and the plain HTTP transport answers once. Only the Streamable HTTP engine sets it. A method that needs a long-lived response is method-not-found without it, rather than something the transport is handed and cannot serve.

supports(Ctx, Feature)

-spec supports(ctx(), atom() | binary()) -> boolean().

Whether the client declared a core capability, e.g. elicitation, sampling or roots. A server must not ask for something the client did not declare.

supports_extension(Ctx, Id)

-spec supports_extension(ctx(), binary()) -> boolean().

Whether the client declared an extension, by identifier (e.g. ?MCP_EXT_TASKS).

validate(_)

-spec validate(ctx()) -> ok | {error, {missing_meta, binary()}} | {error, {invalid_meta, binary()}}.

Check that a modern request carries the _meta fields the spec marks required, and that the optional ones it does carry are usable. A failure is malformed params and the caller must reject it with ?JSONRPC_INVALID_PARAMS (HTTP 400).

Legacy requests carry no such requirement and always pass.

validate_version(_)

-spec validate_version(ctx()) ->
                          ok | {error, {missing_meta, binary()}} | {error, {invalid_meta, binary()}}.

Check the stated revision on its own, before anything is judged against it. A peer naming a revision we do not speak may legitimately carry a _meta shape we would misjudge, so the version error has to win; but a version that is absent or not a string is a _meta problem, and answering "unsupported version: undefined" names the wrong one.