# `Anubis.Protocol.Registry`

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 `c: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}

# `era`

```elixir
@type era() :: Anubis.Protocol.Behaviour.era()
```

# `version`

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

# `era`

```elixir
@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`

```elixir
@spec fallback_version() :: version()
```

Returns the fallback protocol version for compatibility.

# `get`

```elixir
@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`

```elixir
@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`

```elixir
@spec latest_module() :: module()
```

Returns the module for the latest supported protocol version.

# `latest_module`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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?`

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

Check if a version string is supported.

# `supported_versions`

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

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

# `supports_feature?`

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

Checks if a feature is supported by a protocol version.

# `versions_for_era`

```elixir
@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"]

---

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