You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Address review: serve_loop helper, bare 405, INVALID_REQUEST split, migration.md fixes
Four review findings, reworked for by-construction:
1. Non-POST -> bare 405 + Allow header (HTTP-layer rejection happens before
JSON-RPC parsing, so it doesn't go through the JSON-RPC status table).
_write stays table-pure with no override parameter.
2. Stateful HTTP no longer enters lifespan twice. New serve_loop() free
function in runner.py owns the loop-mode dispatcher recipe (notably
inline_methods={'initialize'}); both Server.run() and the manager's
stateful path call it. Manager owns lifespan for all HTTP modes;
Server.run() owns it for stdio/memory. Stateful sessions also now have
their session_id threaded onto Connection.
3. Well-formed JSON that isn't a single request object (notification,
response, batch) returns INVALID_REQUEST instead of PARSE_ERROR.
json.loads and model_validate are now distinct steps; INVALID_REQUEST
added to ERROR_CODE_HTTP_STATUS. The classifier now receives the decoded
body directly rather than a reconstructed dict.
4. migration.md: fixed the broken ctx.connection example (replaced with
prose; the high-level Context surface for this is being finalised),
documented stateless kwarg + StatelessModeNotSupported removals,
updated the ServerSession constructor example, and extended the
lifespan-once entry to cover stateful as well.
If you were mutating these via `mcp.settings` after construction (e.g., `mcp.settings.port = 9000`), pass them to `run()` / `sse_app()` / `streamable_http_app()` instead — these fields no longer exist on `Settings`. The `debug` and `log_level` parameters remain on the constructor.
503
503
504
-
### `stateless_http=True`: lifespan now entered once at startup
504
+
### Streamable HTTP: lifespan now entered once at manager startup
505
505
506
-
When serving streamable HTTP with `stateless_http=True`, the server's `lifespan` context manager is now entered once when `StreamableHTTPSessionManager.run()` starts, and the resulting state is shared across all requests. Previously each incoming request entered (and exited)`lifespan` independently.
506
+
When serving streamable HTTP (stateful or `stateless_http=True`), the server's `lifespan` context manager is now entered once when `StreamableHTTPSessionManager.run()` starts, and the resulting state is shared across all sessions and requests. Previously each session (stateful) or each request (stateless) entered and exited `lifespan` independently.
507
507
508
-
Lifespans that set up process-wide state (connection pools, caches, background tasks) are unaffected — they now run once instead of on every request. If your lifespan was acquiring per-connection resources, move that into the handler and register cleanup on the per-connection `exit_stack`:
508
+
Lifespans that set up process-wide state (connection pools, caches, background tasks) are unaffected — they now run once instead of per session/request. If your lifespan was acquiring per-connection resources, move that acquisition into the handler body; per-connection cleanup belongs on the connection's`exit_stack` (the public surface for reaching it from high-level `@mcp.tool()` handlers is being finalised as part of the public-surface review).
db =await ctx.connection.exit_stack.enter_async_context(open_db())
513
-
...
514
-
```
510
+
### `Server.run()` no longer takes a `stateless` flag; `StatelessModeNotSupported` removed
511
+
512
+
The `stateless: bool` parameter on the lowlevel `Server.run()` has been removed. Stateless serving is now a property of how the connection is constructed (the streamable-HTTP manager builds a born-ready `Connection` per request), not a flag the loop driver inspects.
513
+
514
+
`StatelessModeNotSupported` has been removed. Server-initiated requests that have no channel to travel on now raise `NoBackChannelError` (an `MCPError` subclass) — the same exception regardless of why the channel is absent. If you were catching `StatelessModeNotSupported`, catch `NoBackChannelError` instead.
515
515
516
516
### `MCPServer.get_context()` removed
517
517
@@ -1228,8 +1228,8 @@ from mcp.server import ServerRequestContext
0 commit comments