Files
modelcontextprotocol--pytho…/tests/examples/test_story_shape.py
T
wehub-resource-sync 49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:10:27 +08:00

123 lines
6.1 KiB
Python

"""AST shape-check: stories keep the SDK construction visible and the harness contained.
The python analogue of typescript-sdk's eslint import-allowlist over its examples,
strictly stronger: it also asserts each ``main`` constructs ``Client(...)`` itself —
the regression the harness inversion exists to prevent.
"""
from __future__ import annotations
import ast
from pathlib import Path
import pytest
from tests.examples.conftest import STORIES, STORIES_DIR, story_cfg
_HARNESS_ALLOWLIST = frozenset({"run_client", "target_from_args", "Target", "TargetFactory"})
"""The only ``stories._harness`` names a ``client.py`` may use. ``AuthBuilder`` is
additionally allowed in a ``client.py`` that defines ``build_auth`` (the auth seam
``run_client`` and the conftest both look up by name)."""
_MCPSERVER_TIER = ("mcp.server.mcpserver", "mcp.server.MCPServer")
"""Both spellings of the high-level tier: the ``mcpserver`` module and its ``mcp.server`` re-export."""
_LOWLEVEL_STORIES = [name for name in sorted(STORIES) if story_cfg(name)["lowlevel"]]
def _parse(path: Path) -> ast.Module:
"""Parse ``path`` into an AST module."""
return ast.parse(path.read_text(), filename=str(path))
def _resolve(node: ast.ImportFrom, package: str) -> str:
"""The absolute module path ``node`` imports from, resolving a relative import against ``package``."""
parents = package.split(".")[: -(node.level - 1) or None] if node.level else []
return ".".join([*parents, *([node.module] if node.module else [])])
def _module_paths(tree: ast.Module, package: str) -> set[str]:
"""Every dotted module path the file (a module in ``package``) references — imports, with relative
ones resolved to absolute, plus attribute chains rooted at an import-bound name (``import mcp.shared``
+ ``mcp.shared._memory.f()``), so a reach-in is caught however it is spelled."""
paths: set[str] = set()
bound: dict[str, str] = {}
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
paths.add(alias.name)
local = alias.asname or alias.name.partition(".")[0]
bound[local] = alias.name if alias.asname else local
elif isinstance(node, ast.ImportFrom):
module = _resolve(node, package)
for alias in node.names:
paths.add(f"{module}.{alias.name}")
bound[alias.asname or alias.name] = f"{module}.{alias.name}"
for node in ast.walk(tree):
attrs: list[str] = []
expr: ast.AST = node
while isinstance(expr, ast.Attribute):
attrs.append(expr.attr)
expr = expr.value
if attrs and isinstance(expr, ast.Name) and expr.id in bound:
paths.add(".".join([bound[expr.id], *reversed(attrs)]))
return paths
def _is_private_mcp(path: str) -> bool:
"""True when ``path`` crosses a ``_``-private segment inside the ``mcp`` package."""
head, *rest = path.split(".")
return head == "mcp" and any(part.startswith("_") for part in rest)
def _is_story_module(path: str) -> bool:
"""True for ``stories.<story>...`` — a story package, not a ``stories._*`` scaffold."""
head, _, rest = path.partition(".")
return head == "stories" and bool(rest) and not rest.startswith("_")
@pytest.mark.parametrize("name", sorted(STORIES))
def test_main_constructs_client_inline(name: str) -> None:
"""``main``'s body contains a literal ``Client(...)`` call; the construction is never hidden in a helper."""
tree = _parse(STORIES_DIR / name / "client.py")
mains = [n for n in tree.body if isinstance(n, ast.AsyncFunctionDef) and n.name == "main"]
assert mains, f"{name}/client.py defines no top-level async `main`"
calls = {n.func.id for n in ast.walk(mains[0]) if isinstance(n, ast.Call) and isinstance(n.func, ast.Name)}
assert "Client" in calls, f"{name}/client.py: main() never calls Client(...) itself"
@pytest.mark.parametrize("name", sorted(STORIES))
def test_client_harness_imports_within_allowlist(name: str) -> None:
"""``client.py`` takes nothing from ``stories._harness`` beyond the allowlist, bounding the harness surface."""
tree = _parse(STORIES_DIR / name / "client.py")
defines_build_auth = any(isinstance(n, ast.FunctionDef) and n.name == "build_auth" for n in tree.body)
allowed = _HARNESS_ALLOWLIST | {"AuthBuilder"} if defines_build_auth else _HARNESS_ALLOWLIST
paths = _module_paths(tree, package=f"stories.{name}")
used = {p.removeprefix("stories._harness.").partition(".")[0] for p in paths if p.startswith("stories._harness.")}
assert used <= allowed, f"{name}/client.py uses {sorted(used - allowed)} from stories._harness"
@pytest.mark.parametrize("name", sorted(STORIES))
def test_story_files_import_no_private_mcp_module(name: str) -> None:
"""No file in a story directory references a ``_``-private ``mcp.*`` module."""
for path in sorted((STORIES_DIR / name).glob("*.py")):
private = sorted(p for p in _module_paths(_parse(path), package=f"stories.{name}") if _is_private_mcp(p))
assert not private, f"{path.relative_to(STORIES_DIR)} reaches into private mcp module(s): {private}"
@pytest.mark.parametrize("name", _LOWLEVEL_STORIES)
def test_server_lowlevel_imports_no_mcpserver_tier(name: str) -> None:
"""``server_lowlevel.py`` stays on the lowlevel tier; it never references ``MCPServer`` or its module."""
paths = _module_paths(_parse(STORIES_DIR / name / "server_lowlevel.py"), package=f"stories.{name}")
high = sorted(p for p in paths if any(f"{p}.".startswith(f"{tier}.") for tier in _MCPSERVER_TIER))
assert not high, f"{name}/server_lowlevel.py references the MCPServer tier: {high}"
@pytest.mark.parametrize("scaffold", ["_harness.py", "_hosting.py"])
def test_scaffold_imports_no_story_module(scaffold: str) -> None:
"""The dependency is one-way: ``_harness.py`` / ``_hosting.py`` import no ``stories.<story>`` module."""
story_refs = sorted(
p for p in _module_paths(_parse(STORIES_DIR / scaffold), package="stories") if _is_story_module(p)
)
assert not story_refs, f"{scaffold} imports a story module: {story_refs}"