PrefectHQ/fastmcp · error · RuntimeError
Stateful proxy requires a per-connection server session; no
Error message
Stateful proxy requires a per-connection server session; no connection is available on the current context.
What it means
new_stateful() creates a per-connection stateful proxy client keyed on the server Connection object, which it extracts from the current ServerSession via session._connection. If no Connection is attached to the session (e.g. the proxy client is being created outside a live MCP connection context), it raises RuntimeError because there is no stable per-connection object to key the cache and cleanup on.
Source
Thrown at fastmcp_slim/fastmcp/server/providers/proxy.py:1848
async def clear(self):
"""Clear all cached clients and force disconnect them."""
while self._caches:
_, cache = self._caches.popitem()
await cache._disconnect(force=True)
def new_stateful(self) -> Client[ClientTransportT]:
"""Create a new stateful proxy client instance with the same configuration.
Use this method as the client factory for stateful proxy server.
"""
session = get_context().session
# SDK v2: the ServerSession is per-request; the Connection is the stable
# per-connection object that owns the exit stack. Key the cache and the
# cleanup callback off it so one proxy client is reused for the whole
# connection instead of one per request.
connection = getattr(session, "_connection", None)
if connection is None:
raise RuntimeError(
"Stateful proxy requires a per-connection server session; "
"no connection is available on the current context."
)
proxy_client = self._caches.get(connection, None)
if proxy_client is None:
proxy_client = self.new()
logger.debug(f"{proxy_client} created for {connection}")
self._caches[connection] = proxy_client
async def _on_connection_exit():
self._caches.pop(connection, None)
logger.debug(f"{proxy_client} will be disconnect")
# This callback runs while the connection's exit stack is
# unwinding, which usually happens because the owning task is
# being cancelled. Shield the disconnect so the forced cleanup
# actually runs to completion instead of aborting at the first
# cancellation checkpoint (e.g. acquiring the session lock).View on GitHub (pinned to 1f02114297)
Solutions
- Only create stateful proxy clients inside a live request/connection context where a ServerSession with its Connection exists
- In tests, construct a real ClientSession/Connection pair (or a stub object exposing _connection) instead of a bare mock session
- Fall back to a non-stateful proxy client when no connection context is available, if statefulness isn't required
Example fix
// before: fails outside a connection client = provider.new_stateful(session) # session is a bare mock // after: guard on connection presence connection = getattr(session, '_connection', None) client = provider.new_stateful(session) if connection else provider.new_client()
Defensive patterns
Strategy: try-catch
Validate before calling
connection = getattr(session, '_connection', None)
if connection is None:
raise RuntimeError('new_stateful() requires a live MCP connection context') Type guard
def has_connection(session) -> bool:
return getattr(session, '_connection', None) is not None Try / catch
try:
client = provider.new_stateful(session)
except RuntimeError as e:
if 'no connection is available' in str(e):
client = provider.new_client() # stateless fallback Prevention
- Only build stateful proxies inside request handlers with a real ServerSession
- In tests, use a real client/server session pair or a stub exposing _connection
- Keep the MCP SDK version in sync with what fastmcp expects for session wiring
When it happens
Trigger: Calling new_stateful() (or a stateful proxy client factory) from a context where the ServerSession has no _connection attribute — e.g. in tests without a real transport, in in-memory/off-protocol invocation, or during startup before any client connects.
Common situations: Unit-testing stateful proxies without spinning up a real server session; calling the factory from background tasks or non-MCP entry points; SDK version changes where the session/connection wiring differs.
Related errors
- Client is not connected. Use the 'async with client:' contex
- ClientGroup is already connected
- JWT issuer not initialized. Ensure get_routes() is called be
- FastMCP instance is no longer available
- session_id is not available because no session exists. This
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/38addf73e371e067.
Report an issue: GitHub.