1b74b06753
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.
middleware
Register a single async (ctx, call_next) -> result function on
Server.middleware to observe or alter every request and notification the
server receives, across both protocol eras and any transport. Middleware sits
outside method lookup and params validation, so it sees initialize,
server/discover, notifications/*, and unknown methods too. The chain runs
outermost-first.
Run it
# stdio (default — the client spawns the server as a subprocess)
uv run python -m stories.middleware.client
# HTTP — the client self-hosts the server on a free port, runs, then tears it down
uv run python -m stories.middleware.client --http
What to look at
client.pymain— opens withasync with Client(target, mode=mode). The story owns that construction; the harness only picks the target and era. Middleware is invisible from this side — only theaudit_logresult proves the wrap happened.server.py—server.middleware.append(record_calls)is the public registration point onmcp.server.lowlevel.Server.client.py— the asserted log ends at"tools/call"without a:donesuffix:audit_logruns insidecall_next(ctx), so thefinallyhasn't fired yet. That's the wrap.
Caveats
- Lowlevel-only.
Server.middlewareonmcp.server.lowlevel.Serveris the one public hook;MCPServerhas no public accessor for it yet (aMCPServer.middlewareaccessor is planned before beta). - The middleware signature is provisional (see the TODO in
src/mcp/server/lowlevel/server.py): it tightens to a covariantContext[L]and gains an outbound seam before v2 final. ServerMiddleware/CallNext/HandlerResultare imported frommcp.server.context(helper tier); not re-exported atmcp.server.lowlevel.- Do not
await ctx.session.send_request(...)while wrappinginitialize—initializeis dispatched inline and the outbound channel isn't open yet. - To rewrite
ctx.method/ctx.paramsbefore the handler runs, pass an adjusted context through:await call_next(dataclasses.replace(ctx, ...)).docs/migration.mdshows the full recipe.
Spec
Middleware is SDK architecture, not an MCP spec feature.
See also
custom_methods/ (a vendor acme/search handler registered with
add_request_handler — middleware wraps it like any spec method),
src/mcp/server/_otel.py (OpenTelemetryMiddleware, the SDK's own consumer).