Add story-style examples suite (27 stories + harness + CI)

Merged
MA
4mo ago
+6801-26

Adds a story-style examples suite: one self-contained folder per protocol feature, each with server.py (MCPServer), server_lowlevel.py (lowlevel Server), client.py, and README.md. Mirrors the typescript-sdk v2-2026-07-28 examples layout.

Motivation and Context

The existing examples are spread across four parallel layouts and only everything-server runs in CI. We want examples that:

  • answer "how do I do X?" with one small, focused folder a user can read top to bottom — the duplication across folders is deliberate
  • run as part of the test suite, so any public-API change is immediately visible against real usage
  • cover both the 2025-handshake and 2026-stateless eras, with both MCPServer and lowlevel Server variants wherever the feature allows

What's in this PR

  • 27 runnable stories under examples/stories/: tools, prompts, resources, lifespan, dual_era, streaming, legacy_elicitation, sampling, stickynotes, custom_methods, schema_validators, middleware, parallel_calls, roots, pagination, error_handling, serve_one, stateless_legacy, json_response, legacy_routing, starlette_mount, sse_polling, standalone_get, reconnect, bearer_auth, oauth, oauth_client_credentials — 23 of them with a lowlevel Server sibling.
  • 7 README-only stubs for features not yet runnable here: caching, mrtr, subscriptions, tasks, apps, skills, events.
  • Every client.py constructs the Client inline. The body is async def main(target, *, mode="auto"): async with Client(target, mode=mode) as client: ... — the one line a client example exists to teach is visible in every file, and the harness only decides what target is. An AST shape-check (tests/examples/test_story_shape.py) keeps it that way.
  • Test harness (tests/examples/): manifest.toml parametrizes each story across (server variant × transport × era) — 266 legs in ~1.5s, all in-memory or ASGI-bridged, no subprocesses or sleeps. Plus a 3-leg subprocess smoke test (gated on MCP_EXAMPLES_SMOKE=1) that runs the literal documented commands over real stdio/uvicorn.
  • Self-hosting HTTP runs. python -m stories.<story>.client --http with no URL starts the sibling server on a port it owns, waits for it, runs the scenario, and tears it down — nothing to background, kill, or collide. --http <url> still targets a server you run yourself, and stays the documented form in the stories where hosting is the lesson.
  • Status markers. The manifest carries status = current | legacy | deprecated, surfaced in the story index; legacy-mechanism stories (e.g. legacy_elicitation, the handshake-era push that MRTR replaces) and deprecated features (roots, sampling, the logging half of streaming) open their READMEs with a banner and a migration note.
  • Scaffolding: _harness.py (client-side) and _hosting.py (server-side, isolating the HTTP entry calls still being reshaped so one edit propagates), _shared/auth.py (in-process AS for the auth stories). Single examples/pyproject.toml workspace member, so from stories.tools.server import build_server works in scripts, -m, and pytest.
  • CI: the in-memory matrix runs in the existing test job; the smoke leg runs on one ubuntu cell.
  • The existing examples/clients/, examples/servers/*, examples/mcpserver/, and examples/snippets/ directories are left in place for now.

How Has This Been Tested?

  • ./scripts/test: 100% coverage, strict-no-cover clean (net −6 # pragma: no cover in src/ — the stories exercise previously-uncovered branches)
  • pytest tests/examples/: 261 passed, 2 xfail (a documented progress-drop on the modern HTTP path), 3 skipped (smoke without the env var)
  • MCP_EXAMPLES_SMOKE=1 pytest tests/examples/test_stories_smoke.py: 3 passed over real subprocess stdio + uvicorn
  • pyright, ruff, markdownlint, pre-commit: clean

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Intentional behavior changes

  • src/mcp/server/{elicitation,lowlevel/server,mcpserver/server}.py, src/mcp/shared/exceptions.py: removed 6 # pragma: no cover markers on branches the new stories now exercise. No logic change.

Known caveats / follow-ups

  • mrtr/ and subscriptions/ are stubs even though their lowlevel registration surface now exists on main (#2967); implementing them is a deliberate follow-up to keep this PR reviewable. Same for the deferred extension stories.
  • Several SDK ergonomic gaps surfaced while writing these (e.g. no Client(stdio_params) overload, no auth= passthrough on Client(url), no public MCPServer middleware accessor, no server-side supported-protocol-versions override). The stories work around them in the harness, never in the user-facing files; tracked separately.
  • Consolidating the old examples/{clients,servers,mcpserver}/ trees into stories/ is a follow-up.

AI Disclaimer