Skip to content

Commit 5a0127e

Browse files
committed
docs: deadlock warning for server-to-client requests during initialize
initialize is handled inline (the dispatcher parks the read loop until the handler - middleware included - returns), so awaiting a peer response on that path can never dequeue the response. Warn on ServerMiddleware; TODO at the initialize branch: rework initialization into a pure incoming middleware and add an outgoing-middleware seam whose default chain blocks server-to-client requests when the negotiated protocol version requires initialization and the connection is neither initialized nor stateless.
1 parent 37c28c6 commit 5a0127e

2 files changed

Lines changed: 15 additions & 0 deletions

File tree

‎src/mcp/server/context.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ class ServerMiddleware(Protocol[_MwLifespanT]):
139139
yet). For typed inspection, validate against the model the middleware
140140
expects.
141141
142+
Warning: `initialize` is handled inline - the dispatcher does not read
143+
further inbound messages until the middleware chain returns. Awaiting a
144+
server-to-client request (`ctx.session.send_request`, `send_ping`, ...)
145+
while handling `initialize` therefore deadlocks the connection: the
146+
response can never be dequeued. Send-and-forget notifications are safe.
147+
142148
`Server[L].middleware` holds `ServerMiddleware[L]`, so an app-specific
143149
middleware sees `ctx.lifespan_context: L`. While the context is the
144150
mutable `ServerRequestContext` dataclass it is invariant in `L`, so a

‎src/mcp/server/runner.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,15 @@ async def _inner() -> HandlerResult:
259259
if params is not None:
260260
payload["params"] = dict(params)
261261
client_request_adapter.validate_python(payload, by_name=False)
262+
# TODO(maxisbey): rework initialization into a pure incoming
263+
# middleware, and add an outgoing-middleware seam whose default
264+
# chain blocks server-to-client requests when the negotiated
265+
# protocol version requires initialization and the connection is
266+
# neither initialized nor stateless. Until then, note that
267+
# `initialize` is handled inline (the dispatcher's read loop is
268+
# parked until this whole call - middleware included - returns),
269+
# so awaiting a peer response anywhere on this path deadlocks the
270+
# connection.
262271
if method == "initialize":
263272
return self._handle_initialize(params)
264273
if not self._initialized and method not in _INIT_EXEMPT:

0 commit comments

Comments
 (0)