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

  1. Wrap usage in `async with client:` before touching client.session
  2. Use high-level client methods (list_tools, call_tool, ...) inside the context manager instead of caching the session
  3. Keep all calls within the same connection scope (e.g. after manual initialize())
  4. 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

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


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/a601ab9239c2367c. Report an issue: GitHub.