PrefectHQ/fastmcp · error · RuntimeError
Client is not connected. Use the 'async with client:' contex
Error message
Client is not connected. Use the 'async with client:' context manager first.
What it means
FastMCP's Client only holds an active MCP session between entering and exiting its async context manager. The `session` property raises this RuntimeError when accessed outside that window because there is no underlying ClientSession to return. It guards low-level session APIs against use on a disconnected client.
Source
Thrown at fastmcp_slim/fastmcp/client/client.py:654
def _reset_session_state(self, full: bool = False) -> None:
"""Reset session state after disconnect or cancellation.
Args:
full: If True, also resets session_task and nesting_counter.
Use full=True for cancellation cleanup where the session
task was started but never completed normally.
"""
self._session_state.session = None
self._session_state.initialize_result = None
if full:
self._session_state.session_task = None
self._session_state.nesting_counter = 0
@property
def session(self) -> ClientSession:
"""Get the current active session. Raises RuntimeError if not connected."""
if self._session_state.session is None:
raise RuntimeError(
"Client is not connected. Use the 'async with client:' context manager first."
)
return self._session_state.session
@property
def prior_discover(self) -> mcp_types.DiscoverResult | None:
"""The configured result to adopt when `mode` pins a modern version."""
return self._prior_discover
@property
def initialize_result(self) -> mcp_types.InitializeResult | None:
"""Get the result of the initialization request.
`None` on a modern (`server/discover`) connection, which negotiates via a
`DiscoverResult` rather than an `InitializeResult`. Use `protocol_version`,
`server_info`, `server_capabilities`, and `instructions` for era-neutral
access to the negotiated server metadata.View on GitHub (pinned to 1f02114297)
Solutions
- Wrap usage in `async with client:` before touching client.session
- Use high-level client methods (list_tools, call_tool, ...) inside the context manager instead of caching the session
- Keep all calls within the same connection scope (e.g. after manual initialize())
- Verify a prior `async with` block didn't already exit before the call
Example fix
// before
client = Client(transport)
result = await client.session.list_tools() # RuntimeError
// after
client = Client(transport)
async with client:
result = await client.list_tools() # or client.session.list_tools() Defensive patterns
Strategy: try-catch
Validate before calling
def ensure_connected(client) -> None:
if client._session_state.session is None:
raise RuntimeError("Enter 'async with client:' before using the session") Try / catch
try:
session = client.session
except RuntimeError as e:
if "not connected" in str(e):
async with client:
session = client.session
else:
raise Prevention
- Always access session-dependent APIs inside `async with client:`
- Never cache client.session beyond the context manager's lifetime
- Prefer high-level client methods over raw session access
When it happens
Trigger: Accessing `client.session` (directly or via session-level methods) before `async with client:` is entered, after the context has exited, or after a failed/closed connection reset the session state.
Common situations: Caching `client.session` in a variable and reusing it after the `async with` block ended; calling client methods on a constructed-but-never-entered client; an earlier connection failure left the session as None.
Related errors
- ClientGroup is already connected
- FastMCP instance is no longer available
- FastMCP instance is no longer available
- '{name}' uses a sync function but has task execution enabled
- module {__name__!r} has no attribute {name!r}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/a601ab9239c2367c.
Report an issue: GitHub.