Source code for aiohttp_tiny_mcp.protocol.selection
"""AdapterSet: selects exactly one Adapter per request (docs/reference/selection.md)."""
from __future__ import annotations
from collections.abc import Sequence
from typing import TypeVar
from aiohttp_tiny_mcp.adapter import Adapter
from aiohttp_tiny_mcp.core import Failure, FailureKind, Preamble, Rejected
from .v2024_11_05 import Adapter2024_11_05
from .v2025_03_26 import Adapter2025_03_26
from .v2025_06_18 import Adapter2025_06_18
from .v2025_11_25 import Adapter2025_11_25
from .v2026_07_28 import Adapter2026_07_28
ASSUMED_VERSION = "2025-03-26"
STABLE_REVISION = "2025-11-25"
T = TypeVar("T", bound="AdapterSet")
[docs]
class AdapterSet:
def __init__(self, adapters: Sequence[Adapter], *, fallback: Adapter | None = None) -> None:
self.adapters = tuple(adapters)
self.by_version: dict[str, Adapter] = {}
for adapter in self.adapters:
self.by_version[adapter.version] = adapter
for old in adapter.supersedes:
self.by_version[old] = adapter
self.fallback_adapter = fallback or self.adapters[0]
for adapter in self.adapters:
adapter.bind(self.versions)
@classmethod
def default(cls: type[T]) -> T:
return cls(
[
Adapter2026_07_28(),
Adapter2025_11_25(),
Adapter2025_06_18(),
Adapter2025_03_26(),
Adapter2024_11_05(),
]
)
@property
def versions(self) -> tuple[str, ...]:
return tuple(sorted({a.version for a in self.adapters}, reverse=True))
def select(self, pre: Preamble, session_version: str | None = None) -> Adapter:
if pre.parse_error:
return self.fallback()
if pre.meta_version is not None and not isinstance(pre.meta_version, str):
raise Rejected(
Failure(FailureKind.MALFORMED, "protocol version in _meta must be a string")
)
if pre.query_version is not None:
return self.resolve_version(pre.query_version)
if (
pre.header_version is not None
and pre.meta_version is not None
and pre.header_version != pre.meta_version
):
raise Rejected(
Failure(FailureKind.HEADER_MISMATCH, "MCP-Protocol-Version does not match _meta")
)
if pre.meta_version is not None:
return self.resolve_version(pre.meta_version)
if pre.header_version is not None:
return self.resolve_version(pre.header_version)
if pre.method == "initialize":
return self.by_version.get(STABLE_REVISION, self.fallback())
if session_version is not None:
return self.resolve_version(session_version)
return self.by_version.get(ASSUMED_VERSION, self.fallback())
def resolve_version(self, version: str) -> Adapter:
adapter = self.by_version.get(version)
if adapter is None:
raise Rejected(
Failure(
FailureKind.UNSUPPORTED_VERSION,
f"unsupported protocol version: {version}",
data={"supported": list(self.versions), "requested": version},
)
)
return adapter
def fallback(self) -> Adapter:
return self.fallback_adapter