# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-mcp-server-reject-a-tool-call-that-its-alias-table-would-otherwise-resolve.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-mcp-server-reject-a-tool-call-that-its-alias-table-would-otherwise-resolve.warrant.json
# Markdown SHA-256:   bd17090688fe86b0c312785fb80e2c8f83d97d24b741fdd712de08c5020039e8
# Code AST SHA-256:   3f9b316aa093c363804114669d9036ea048c3b2b9fa37e9eac99e2a582478509
# Verified Classes:   AliasSchemaMismatch, AliasEntry
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Callable, Mapping, Any


class AliasSchemaMismatch(RuntimeError):
    """Raised when an alias resolves but the negotiated schema version
    cannot satisfy the target handler's argument contract."""


@dataclass(frozen=True)
class AliasEntry:
    target: str
    serves_versions: frozenset[int]
    downconverter: Callable[[Mapping[str, Any]], Mapping[str, Any]] | None = None


ALIASES: dict[str, AliasEntry] = {
    "search_v1": AliasEntry(
        target="search",
        serves_versions=frozenset({1}),
        downconverter=None,  # no v1->v2 argument converter registered
    ),
}


def dispatch(tool_name: str, arguments: Mapping[str, Any], session_schema_version: int) -> str:
    entry = ALIASES.get(tool_name)
    if entry is None:
        return tool_name  # not an alias; normal resolution path

    if session_schema_version not in entry.serves_versions:
        raise AliasSchemaMismatch(
            f"alias {tool_name!r} does not serve schema version "
            f"{session_schema_version}; serves {sorted(entry.serves_versions)}"
        )

    if entry.downconverter is None and session_schema_version < max(entry.serves_versions):
        # Alias claims to serve this version but cannot reshape arguments
        # to the target's current contract. Refuse rather than let the
        # handler apply an implicit default.
        raise AliasSchemaMismatch(
            f"alias {tool_name!r} has no argument down-converter for "
            f"schema version {session_schema_version}"
        )

    if entry.downconverter is not None:
        arguments = entry.downconverter(arguments)

    return entry.target


# Example: v1 client calling deprecated name trips the gate.
try:
    dispatch("search_v1", {"query": "foo"}, session_schema_version=1)
except AliasSchemaMismatch as exc:
    print(f"rejected at boundary: {exc}")
