PrefectHQ/fastmcp · error · ValueError

mode must be 'legacy', 'auto', or one of {list(MODERN_PROTOC

Error message

mode must be 'legacy', 'auto', or one of {list(MODERN_PROTOCOL_VERSIONS)}; got {mode!r}{hint}

What it means

Client.__init__ validates the mode parameter: it must be 'legacy', 'auto', or one of the modern protocol version strings in MODERN_PROTOCOL_VERSIONS. Anything else — including handshake-era version dates like '2025-03-26' — raises this ValueError; handshake-era values get an explicit hint to use mode='legacy'.

Source

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

        verify: ssl.SSLContext | bool | str | None = None,
        mode: ConnectMode = "auto",
        prior_discover: mcp_types.DiscoverResult | None = None,
        input_required_max_rounds: int = DEFAULT_INPUT_REQUIRED_MAX_ROUNDS,
        cache: CacheConfig | bool | None = None,
        extensions: Sequence[ClientExtension] | None = None,
        result_claims: Mapping[str, Sequence[ResultClaim[Any]]] | None = None,
    ) -> None:
        self.name = name or self.generate_name()

        self.input_required_max_rounds = input_required_max_rounds

        if mode not in ("legacy", "auto") and mode not in MODERN_PROTOCOL_VERSIONS:
            hint = (
                f" ({mode!r} is a handshake-era version; use mode='legacy')"
                if mode in HANDSHAKE_PROTOCOL_VERSIONS
                else ""
            )
            raise ValueError(
                "mode must be 'legacy', 'auto', or one of "
                f"{list(MODERN_PROTOCOL_VERSIONS)}; got {mode!r}{hint}"
            )
        self.mode: ConnectMode = mode
        self._prior_discover = prior_discover

        self.transport = cast(ClientTransportT, infer_transport(transport))

        if verify is not None:
            from fastmcp.client.transports.http import StreamableHttpTransport
            from fastmcp.client.transports.sse import SSETransport

            if isinstance(self.transport, StreamableHttpTransport | SSETransport):
                self.transport.verify = verify
                # Re-sync existing OAuth auth with the new verify setting,
                # but only if the transport doesn't have a custom factory
                # (which takes precedence and was already applied to OAuth).
                if (

View on GitHub (pinned to 1f02114297)

Solutions

  1. Use mode='auto' (default) or mode='legacy'.
  2. To pin a version, use one of the strings listed in the error message (values of MODERN_PROTOCOL_VERSIONS).
  3. If you passed a handshake-era date, replace it with mode='legacy' as the hint suggests.
  4. Check spelling/casing of the mode value.

Example fix

// before: handshake-era version pin rejected
client = Client(url, mode='2025-03-26')
// after
client = Client(url, mode='legacy')
Defensive patterns

Strategy: validation

Validate before calling

from fastmcp.client.client import MODERN_PROTOCOL_VERSIONS
mode = '2025-03-26'
assert mode in ('legacy', 'auto', *MODERN_PROTOCOL_VERSIONS), f'invalid mode: {mode!r}'

Type guard

from typing import Literal
from fastmcp.client.client import MODERN_PROTOCOL_VERSIONS
ValidMode = Literal['legacy', 'auto'] | str
def is_valid_mode(mode: ValidMode) -> bool:
    return mode in ('legacy', 'auto') or mode in MODERN_PROTOCOL_VERSIONS

Try / catch

try:
    client = Client(url, mode=mode)
except ValueError as e:
    if 'mode must be' in str(e):
        mode = 'legacy'  # or a listed modern version
        client = Client(url, mode=mode)
    else:
        raise

Prevention

When it happens

Trigger: Passing mode=<anything not in {'legacy','auto'} ∪ MODERN_PROTOCOL_VERSIONS> to Client(...) or Client.new(...) — e.g. mode='2025-03-26' (handshake-era), mode='v4', mode=None, or a typo like mode='Legacy'.

Common situations: Copying a protocol date string from older MCP docs/examples; trying to pin a pre-modern protocol version; casing or spelling mistakes in 'legacy'/'auto'.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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