context
ServerRequestContext
dataclass
Bases: Generic[LifespanContextT, RequestT]
Per-request context handed to lowlevel request and notification handlers.
Built by ServerRunner._make_context for each inbound message. Carries the
connection-scoped ServerSession (server-to-client requests and
notifications), per-request metadata, and any per-message data the
transport attached (the HTTP request, SSE stream-close callbacks).
Source code in src/mcp/server/context.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | |
HandlerResult
module-attribute
What a request handler (or middleware) may return. ServerRunner serializes
all three to a result dict.
CallNext
module-attribute
CallNext = Callable[
["ServerRequestContext[Any, Any]"],
Awaitable[HandlerResult],
]
Invokes the rest of the chain with the given context. What a context
rewrite (dataclasses.replace(ctx, ...)) can alter depends on the tier:
ServerMiddleware runs before params validation, so its rewrites change what
the handler is invoked with; an Extension interceptor runs after, so its
rewrites change only what the handler observes on ctx.
ServerMiddleware
Bases: Protocol[_MwLifespanT]
Context-tier middleware: (ctx, call_next) -> result.
Runs at the top of ServerRunner._on_request / _on_notify after ctx
is built but before any validation, lookup, or handshake. Wraps every
inbound request and notification: initialize, the pre-init gate,
METHOD_NOT_FOUND, params validation, the handler call, and
notifications/initialized all run inside call_next(ctx).
notifications/cancelled is observed too; the dispatcher applies the
cancellation itself, then forwards the notification. A request-side
failure reaches the middleware as a raised MCPError (or
ValidationError for malformed params) so observation/logging middleware
can record it. Listed outermost-first on Server.middleware.
The method and the raw inbound params are ctx.method and ctx.params (no
model validation has happened yet). To rewrite either before the handler
runs, pass an adjusted context: await call_next(replace(ctx, params=...)).
ctx.request_id is None distinguishes a notification from a request. For
notifications call_next(ctx) returns None (a dropped or unhandled
notification also returns None) and the middleware's own return value is
discarded.
Warning
initialize is handled inline - the dispatcher does not read
further inbound messages until the middleware chain returns. Awaiting a
server-to-client request (ctx.session.send_request, send_ping, ...)
while handling initialize therefore deadlocks the connection: the
response can never be dequeued. Send-and-forget notifications are safe.
initialize is observed but not rewritable: the post-chain handshake
commit reads the wire params, so to veto the handshake raise before
call_next().
Server[L].middleware holds ServerMiddleware[L], so an app-specific
middleware sees ctx.lifespan_context: L. While the context is the
mutable ServerRequestContext dataclass it is invariant in L, so a
reusable middleware should be typed ServerMiddleware[Any] to register on
any Server[L].
Source code in src/mcp/server/context.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |