Anubis.Protocol.Registry (anubis_mcp v2.2.0)

Copy Markdown

Registry for MCP protocol version modules.

Maps version strings to their implementing modules, supports version negotiation, and provides the central dispatch point for version-specific protocol logic.

Eras

Registered versions group into the eras defined by Anubis.Protocol.Behaviour — :legacy versions negotiate a session with the initialize handshake, :stateless versions carry their metadata on every request. The grouping is derived from each module's Anubis.Protocol.Behaviour.era/0 callback, so registering a version is still a single entry in @versions.

Because the two eras open a connection in structurally different ways, they negotiate separately: negotiate/1 and negotiate/2 serve the initialize handshake and therefore only ever resolve to a :legacy version.

Usage

iex> Anubis.Protocol.Registry.get("2025-11-25")
{:ok, Anubis.Protocol.V2025_11_25}

iex> Anubis.Protocol.Registry.supported_versions()
["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26"]

iex> Anubis.Protocol.Registry.versions_for_era(:stateless)
["2026-07-28"]

iex> Anubis.Protocol.Registry.negotiate("2025-03-26")
{:ok, "2025-03-26", Anubis.Protocol.V2025_03_26}

Summary

Functions

Returns the era a version belongs to.

Returns the fallback protocol version for compatibility.

Get the protocol module for a given version string.

Returns the features supported by a given version.

Returns the module for the latest supported protocol version.

Returns the module for the latest supported version of an era, or nil when the era has no registered version.

Returns the latest protocol version reachable through the initialize handshake.

Returns the latest supported version of an era, or nil when none is registered.

List the versions that negotiate a session with the initialize handshake.

Negotiate the best version given a client's requested version.

Negotiate version between client and server supported version lists.

Returns the progress notification params schema for a given version.

List the versions that carry their metadata on every request.

Check if a version string is supported.

List all supported versions in preference order (newest first).

Checks if a feature is supported by a protocol version.

List the versions belonging to an era, in preference order (newest first).

Types

era()

version()

@type version() :: String.t()

Functions

era(version)

@spec era(version()) :: {:ok, era()} | :error

Returns the era a version belongs to.

Examples

iex> Anubis.Protocol.Registry.era("2026-07-28")
{:ok, :stateless}

iex> Anubis.Protocol.Registry.era("unknown")
:error

fallback_version()

@spec fallback_version() :: version()

Returns the fallback protocol version for compatibility.

get(version)

@spec get(version()) :: {:ok, module()} | :error

Get the protocol module for a given version string.

Examples

iex> Anubis.Protocol.Registry.get("2025-06-18")
{:ok, Anubis.Protocol.V2025_06_18}

iex> Anubis.Protocol.Registry.get("unknown")
:error

get_features(version)

@spec get_features(version()) :: {:ok, [atom()]} | :error

Returns the features supported by a given version.

Delegates to the version module's supported_features/0 callback.

latest_module()

@spec latest_module() :: module()

Returns the module for the latest supported protocol version.

latest_module(era)

@spec latest_module(era()) :: module() | nil

Returns the module for the latest supported version of an era, or nil when the era has no registered version.

Examples

iex> Anubis.Protocol.Registry.latest_module(:stateless)
Anubis.Protocol.V2026_07_28

latest_version()

@spec latest_version() :: version()

Returns the latest protocol version reachable through the initialize handshake.

Stateless versions are not handshake-negotiable; use latest_version/1 to ask for the newest version of a specific era.

latest_version(era)

@spec latest_version(era()) :: version() | nil

Returns the latest supported version of an era, or nil when none is registered.

Examples

iex> Anubis.Protocol.Registry.latest_version(:stateless)
"2026-07-28"

legacy_versions()

@spec legacy_versions() :: [version()]

List the versions that negotiate a session with the initialize handshake.

Examples

iex> Anubis.Protocol.Registry.legacy_versions()
["2025-11-25", "2025-06-18", "2025-03-26"]

negotiate(client_version)

@spec negotiate(version()) ::
  {:ok, version(), module()} | {:error, :unsupported_version, [version()]}

Negotiate the best version given a client's requested version.

MCP spec: the server picks the version, the client proposes one. If we support the requested version, use it. Otherwise, return an error with the list of supported versions.

This serves the initialize handshake, so it only resolves to :legacy versions — a stateless version requested here is treated as unsupported.

Examples

iex> Anubis.Protocol.Registry.negotiate("2025-11-25")
{:ok, "2025-11-25", Anubis.Protocol.V2025_11_25}

iex> Anubis.Protocol.Registry.negotiate("9999-01-01")
{:error, :unsupported_version, ["2025-11-25", "2025-06-18", "2025-03-26"]}

iex> Anubis.Protocol.Registry.negotiate("2026-07-28")
{:error, :unsupported_version, ["2025-11-25", "2025-06-18", "2025-03-26"]}

negotiate(client_version, server_versions)

@spec negotiate(version(), [version()]) :: {:ok, version(), module()} | :error

Negotiate version between client and server supported version lists.

Used when the server has a restricted set of supported versions. Returns the client's preference when the server offers it, otherwise the newest legacy version the server offers, regardless of the order they were given in.

Non-legacy and unregistered entries are ignored, and a server list that offers no legacy version returns :error rather than silently substituting another version.

Unlike negotiate/1, which is an admission gate and rejects anything it cannot match exactly, this is the initialize fallback: a client proposing a version the server does not offer — including a :stateless one — is answered with the newest legacy version available, as the legacy lifecycle requires ("otherwise, the server MUST respond with another protocol version it supports"). The client then accepts that version or disconnects.

Examples

iex> Anubis.Protocol.Registry.negotiate("2025-03-26", ["2025-11-25", "2025-03-26"])
{:ok, "2025-03-26", Anubis.Protocol.V2025_03_26}

iex> Anubis.Protocol.Registry.negotiate("2025-06-18", ["2025-11-25", "2025-03-26"])
{:ok, "2025-11-25", Anubis.Protocol.V2025_11_25}

iex> Anubis.Protocol.Registry.negotiate("2025-06-18", ["2025-03-26", "2025-11-25"])
{:ok, "2025-11-25", Anubis.Protocol.V2025_11_25}

iex> Anubis.Protocol.Registry.negotiate("2026-07-28", ["2025-11-25"])
{:ok, "2025-11-25", Anubis.Protocol.V2025_11_25}

iex> Anubis.Protocol.Registry.negotiate("2026-07-28", ["2026-07-28"])
:error

progress_params_schema(version)

@spec progress_params_schema(version()) :: {:ok, map()} | :error

Returns the progress notification params schema for a given version.

Delegates to the version module's progress_params_schema/0 callback.

stateless_versions()

@spec stateless_versions() :: [version()]

List the versions that carry their metadata on every request.

Examples

iex> Anubis.Protocol.Registry.stateless_versions()
["2026-07-28"]

supported?(version)

@spec supported?(version()) :: boolean()

Check if a version string is supported.

supported_versions()

@spec supported_versions() :: [version()]

List all supported versions in preference order (newest first).

supports_feature?(version, feature)

@spec supports_feature?(version(), atom()) :: boolean()

Checks if a feature is supported by a protocol version.

versions_for_era(era)

@spec versions_for_era(era()) :: [version()]

List the versions belonging to an era, in preference order (newest first).

Examples

iex> Anubis.Protocol.Registry.versions_for_era(:stateless)
["2026-07-28"]