Slim ServerMiddleware to (ctx, call_next) and add OpenTelemetryMiddleware#2941
Merged
Conversation
Kludex
commented
Jun 21, 2026
Member
Author
There was a problem hiding this comment.
I didn't realize I created those tests. I don't think they are necessary. We already have tests for otel.
Kludex
commented
Jun 21, 2026
Kludex
commented
Jun 21, 2026
…dleware` Move `method` and `params` onto `ServerRequestContext` so context-tier middleware reads `ctx.method`/`ctx.params` instead of separate positional args. `CallNext` now takes the context, so middleware can rewrite the inbound message with `call_next(replace(ctx, params=...))`. Add a context-tier `OpenTelemetryMiddleware` alongside the existing dispatch-tier `otel_middleware`, which is left intact.
- OpenTelemetryMiddleware: nest under the ambient span when no traceparent is present. Passing an explicit empty Context to start_as_current_span orphans the span; extract_trace_context now returns None for an absent or malformed carrier so callers fall through to ambient parenting. Adds a regression test with both span tiers installed. - Rename 'mw' -> 'middleware' across runner.py; drop redundant CallNext annotation on the compose-loop accumulator. - Document that 'initialize' is observed by ServerMiddleware but not rewritable: the post-chain handshake commit reads the wire params, so a rewritten ctx.params on initialize does not reach connection state. TODO at the commit site names the desync triggers; resolves when initialize becomes a built-in handler.
3dc073d to
ddd79f0
Compare
A non-empty _meta without a traceparent key (e.g. only a progressToken) made extract() return an empty Context, which orphans the span when passed explicitly to start_as_current_span. Check the extracted context carries a valid span and return None otherwise so callers fall through to ambient parenting.
felixweinberger
approved these changes
Jun 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Reshapes the context-tier
ServerMiddlewareinterface and adds a context-tierOpenTelemetryMiddleware.ServerMiddleware.__call__goes from(ctx, method, params, call_next)to(ctx, call_next).methodandparamsnow live onServerRequestContextasctx.methodand rawctx.params.CallNexttakes the context:Callable[[ServerRequestContext], Awaitable[HandlerResult]]. Middleware passes it through withcall_next(ctx), and can rewrite the inbound message viacall_next(replace(ctx, params=...)).OpenTelemetryMiddleware(context-tier) inmcp/server/_otel.py, mirroring the span shape of the existing dispatch-tierotel_middleware, which is left intact. It spans both requests and notifications, settingjsonrpc.request.idonly when present.Why
The 4-arg signature duplicated
method/paramsthat already belong on the context, andcall_next()could only observe, not rewrite. The new shape matches the pure-ASGI middleware model (single context in, pass it through, mutate before next) and lets middleware redirect params before the handler runs.Notes
ctx.paramsis deliberate: middleware runs before method lookup and validation (it wraps the failure path too), so no single validated type exists at that tier. Authors validate on demand withModel.model_validate(ctx.params or {}).docs/migration.md(no compat shim - the 4-arg form never shipped in a release).request_id is Nonepath was confirmed correct.AI Disclaimer
This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.