PrefectHQ/fastmcp · error · RuntimeError

The client negotiated a modern protocol era (server/discover

Error message

The client negotiated a modern protocol era (server/discover), which has no InitializeResult. Inspect client.protocol_version, client.server_info, client.server_capabilities, and client.instructions for the metadata available in this mode, or construct the client with mode='legacy'.

What it means

In the modern protocol era ('server/discover' mode) there is no MCP InitializeResult; connection metadata lives in client.protocol_version, client.server_info, client.server_capabilities, and client.instructions instead. Calling `client.initialize()` after such a negotiation raises this RuntimeError, pointing you at those attributes or to legacy mode.

Source

Thrown at fastmcp_slim/fastmcp/client/client.py:940

        Example:
            ```python
            # With auto-initialization disabled
            client = Client(server, auto_initialize=False)
            async with client:
                result = await client.initialize()
                print(f"Server: {result.server_info.name}")
                print(f"Instructions: {result.instructions}")
            ```
        """

        if self.initialize_result is not None:
            return self.initialize_result

        await self._negotiate(timeout=timeout)

        if self.initialize_result is None:
            raise RuntimeError(
                "The client negotiated a modern protocol era (server/discover), which has "
                "no InitializeResult. Inspect client.protocol_version, client.server_info, "
                "client.server_capabilities, and client.instructions for the metadata "
                "available in this mode, or construct the client with mode='legacy'."
            )
        return self.initialize_result

    async def __aenter__(self):
        return await self._connect()

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self._disconnect()

    async def _connect(self):
        """
        Establish or reuse a session connection.

        This method implements the reentrant context manager pattern:

View on GitHub (pinned to 1f02114297)

Solutions

  1. Use client.protocol_version / client.server_info / client.server_capabilities / client.instructions instead of initialize_result
  2. Construct the client with mode='legacy' if you need the classic InitializeResult
  3. Branch on protocol_version and handle the modern-mode metadata path

Example fix

// before
async with client:
    info = await client.initialize()  # RuntimeError in modern mode

// after
async with client:
    info = client.server_info  # or: Client(transport, mode='legacy') then initialize()
Defensive patterns

Strategy: fallback

Validate before calling

def uses_legacy_initialize(client) -> bool:
    return client.protocol_version not in MODERN_PROTOCOL_VERSIONS

Type guard

def has_initialize_result(client) -> bool:
    return client.initialize_result is not None

Try / catch

try:
    info = await client.initialize()
except RuntimeError as e:
    if "modern protocol era" in str(e):
        info = client.server_info
    else:
        raise

Prevention

When it happens

Trigger: Creating `Client(..., mode='server')` (modern mode) and then awaiting `client.initialize()`; calling initialize() on a client whose auto-negotiation selected a modern protocol version (MCP 2026-07-28).

Common situations: Legacy initialize-flow code reused with a modern-mode client; reading initialize_result for server capabilities in a codebase migrated to the new protocol era.

Related errors


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