Files
Max Isbey 1b74b06753 Tighten comments and docstrings repo-wide
Cut comment and docstring volume roughly in half across src, tests,
examples, and docs_src: removed comments that restate the adjacent code,
leftover development narration, section banners, and self-evident
Args/Returns blocks, and compressed the remaining docstrings to a
Google-style summary line plus only the detail that earns its place.

Kept (and tightened) the load-bearing content: Raises sections,
deprecation and version-availability notes, spec/RFC/issue references,
why-comments for non-obvious decisions, and all coverage pragmas. The
generated mcp_types.v* wire modules are untouched.
2026-06-29 15:10:27 +00:00

162 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Discovery + parametrization for the example-stories matrix.
Expands each story in `examples/stories/manifest.toml` across (server_variant ×
transport × era). Story modules import as real packages (`mcp-example-stories`
installs `stories` editable), so pyright red-lines every story on a signature change.
The HTTP-ASGI leg reuses the interaction suite's in-process bridge until a `stories._shared.bridge` exists.
"""
from __future__ import annotations
import importlib
import sys
from collections.abc import AsyncIterator
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import httpx
import pytest
import stories
from mcp_types.version import LATEST_MODERN_VERSION
from starlette.applications import Starlette
from stories._harness import AuthBuilder, TargetFactory
from stories._hosting import asgi_from
from mcp.client.streamable_http import streamable_http_client
from tests.interaction.transports._bridge import StreamingASGITransport
if sys.version_info >= (3, 11): # pragma: lax no cover
import tomllib
else: # pragma: lax no cover
import tomli as tomllib
STORIES_DIR = Path(stories.__file__).parent
BASE_URL = "http://127.0.0.1:8000"
MANIFEST = tomllib.loads((STORIES_DIR / "manifest.toml").read_text())
DEFAULTS: dict[str, Any] = MANIFEST["defaults"]
STORIES: dict[str, dict[str, Any]] = MANIFEST["story"]
_ERA_TO_MODE = {"modern": LATEST_MODERN_VERSION, "legacy": "legacy", "in-body": "auto"}
"""`Client` rejects handshake-era version strings, so `legacy` maps to `mode='legacy'`, not
`LATEST_HANDSHAKE_VERSION`; `in-body` legs pin modes inside `main`, so they get an explicit `mode="auto"`."""
def story_cfg(name: str) -> dict[str, Any]:
return DEFAULTS | STORIES.get(name, {})
def _expand_era(era: str) -> tuple[str, ...]:
if era == "dual":
return ("modern", "legacy")
if era == "dual-in-body":
return ("in-body",)
return (era,)
@dataclass(frozen=True)
class Leg:
story: str
server_variant: str
transport: str
era: str
@property
def id(self) -> str:
return "-".join((self.story, self.server_variant, self.transport, self.era))
@property
def mode(self) -> str:
"""The explicit `mode=` this leg passes to the story's `main`."""
return _ERA_TO_MODE[self.era]
def _legs() -> list[tuple[Leg, dict[str, Any]]]:
out: list[tuple[Leg, dict[str, Any]]] = []
for name in STORIES:
cfg = story_cfg(name)
variants = ["server"] + (["server_lowlevel"] if cfg["lowlevel"] else [])
out.extend(
(Leg(name, variant, transport, era), cfg)
for variant in variants
for transport in cfg["transports"]
for era in _expand_era(cfg["era"])
)
return out
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
if "leg" not in metafunc.fixturenames:
return
params: list[Any] = []
for leg, cfg in _legs():
marks: list[pytest.MarkDecorator] = []
if f"{leg.transport}:{leg.era}" in cfg["xfail"]:
marks.append(pytest.mark.xfail(strict=True, reason="manifest xfail")) # pragma: lax no cover
params.append(pytest.param(leg, marks=marks, id=leg.id))
metafunc.parametrize("leg", params)
@pytest.fixture
def cfg(leg: Leg) -> dict[str, Any]:
return story_cfg(leg.story)
@pytest.fixture
def server_module(leg: Leg) -> Any:
return importlib.import_module(f"stories.{leg.story}.{leg.server_variant}")
@pytest.fixture
def client_module(leg: Leg) -> Any:
return importlib.import_module(f"stories.{leg.story}.client")
@dataclass
class Hosted:
"""One server/app instance hosted for the leg's whole duration.
`targets` yields a fresh connection target against that single instance per call
(state carries across connections); `http` is the shared `httpx.AsyncClient` on
the same ASGI app, or None on the in-memory leg.
"""
targets: TargetFactory
http: httpx.AsyncClient | None
@pytest.fixture
async def hosted(
leg: Leg, cfg: dict[str, Any], server_module: Any, client_module: Any, monkeypatch: pytest.MonkeyPatch
) -> AsyncIterator[Hosted]:
"""Host the leg's server/app once for the whole test.
The story's `main` owns `Client(target, mode=...)`; this fixture only decides the
target. Auth stories thread an `httpx.Auth` onto the bridge client via a
module-level `build_auth(http)` export.
"""
for key, value in cfg["env"].items():
monkeypatch.setenv(key, value)
path = cfg["mcp_path"]
if leg.transport == "in-memory":
server = server_module.build_server()
yield Hosted(lambda: server, None)
return
# http-asgi: the app's own lifespan brings the session manager up, and the in-process
# bridge never fires ASGI lifespan events itself, so enter it explicitly.
if cfg["server_export"] == "app":
app: Starlette = server_module.build_app()
else:
app = asgi_from(server_module.build_server(), path=path)
build_auth: AuthBuilder | None = getattr(client_module, "build_auth", None)
async with (
app.router.lifespan_context(app),
httpx.AsyncClient(transport=StreamingASGITransport(app), base_url=BASE_URL) as http_client,
):
if build_auth is not None:
http_client.auth = build_auth(http_client)
yield Hosted(lambda: streamable_http_client(f"{BASE_URL}{path}", http_client=http_client), http_client)