Conversation
In stateful streamable-HTTP mode, StreamableHTTPSessionManager retains each session's server-side state until the client sends DELETE /mcp. Many MCP clients (connectors, agents) create sessions freely and never send it, so a long-running server's memory grows without bound. In one production deployment we measured ~720 never-terminated sessions/day retaining ~110KB each — ~77MiB/day of working-set growth, reclaimed only by restarting the server. --stateless-http opts the streamable-http transport into FastMCP's stateless mode (a fresh ephemeral transport per request, no session registry). It costs server-initiated messages and session resumability, which postgres-mcp does not use. Default behavior is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Problem
In stateful streamable-HTTP mode (the default), the MCP SDK's
StreamableHTTPSessionManagerretains each session's server-side state until the client sendsDELETE /mcp. Many MCP clients — hosted connectors, agent frameworks — create sessions freely and never send that DELETE, so a long-running postgres-mcp server's memory grows without bound.Concretely, in one production deployment we measured ~720 never-terminated sessions/day at ~110KB retained each — about 77MiB/day of working-set growth (48h of logs showed zero
DELETE /mcprequests and zero session terminations). Memory never plateaus and is only reclaimed by restarting the server; we currently run a daily restart as a workaround.Change
Adds a
--stateless-httpflag that opts thestreamable-httptransport into FastMCP's stateless mode (stateless_http=True): the SDK creates a fresh ephemeral transport per request and keeps no session registry, so there is nothing to accumulate. The cost is server-initiated messages and session resumability, which postgres-mcp doesn't use. Default behavior (flag omitted) is unchanged.Testing
Four new tests in
tests/unit/test_transport.py:main()pattern as the existing transport tests):--stateless-httpsetsmcp.settings.stateless_http = True; omitting it leaves the stateful default.tools/listPOST that carries no session ID and no prior initialize (HTTP 200), while a stateful server rejects the identical request (HTTP 400, "Missing session ID"). These pin the SDK behavior the flag relies on, so a future SDK bump that changes stateless semantics fails in CI instead of silently reintroducing unbounded session growth. They use freshFastMCPinstances (a FastMCP builds its session manager once, and a manager can only run once) and a127.0.0.1base URL (the SDK's DNS-rebinding protection rejects the test client's defaulttestserverHost with 421).uv run pytest tests/unitpasses,ruff check/ruff format --checkclean. Also smoke-tested live: with the flag, a sessionlesstools/listreturns the full tool list and no per-session transports are logged, while the same request against a stateful server is rejected with "Missing session ID".🤖 Generated with Claude Code