{"record":{"id":"bab4021c8a96ae77","repo":"PrefectHQ/fastmcp","slug":"invalid-params","errorCode":"INVALID_PARAMS","errorMessage":"server/discover result is not conformant with {version}; treating the server as handshake-era","messagePattern":"server/discover result is not conformant with (.+?); treating the server as handshake-era","errorType":"error_code","errorClass":"MCPError","httpStatus":null,"severity":"warning","filePath":"fastmcp_slim/fastmcp/client/client.py","lineNumber":175,"sourceCode":"    for a server with no ``server/discover`` at all.\n    \"\"\"\n    send_discover = session.send_discover\n\n    async def _checked_send_discover(version: str) -> dict[str, Any]:\n        raw = await send_discover(version)\n        try:\n            validate_server_result(\"server/discover\", version, raw)\n        except ValidationError as e:\n            # Ordered before the ValueError arm below: pydantic's ValidationError\n            # subclasses ValueError, so a broader clause first would swallow it.\n            logger.debug(\n                \"server/discover at %s is not %s-conformant (%s); \"\n                \"falling back to the initialize handshake\",\n                version,\n                version,\n                e,\n            )\n            raise MCPError(\n                code=mcp_types.INVALID_PARAMS,\n                message=(\n                    f\"server/discover result is not conformant with {version}; \"\n                    \"treating the server as handshake-era\"\n                ),\n            ) from e\n        except (KeyError, ValueError):\n            # No schema on file for this method/version pair, so there is nothing to\n            # judge the probe against; leave the verdict to negotiate_auto's parse.\n            return raw\n        return raw\n\n    # A transport may itself have installed a `send_discover` override, so restore\n    # whatever was there rather than assuming the class attribute.\n    had_own = \"send_discover\" in vars(session)\n    session.send_discover = _checked_send_discover  # ty: ignore[invalid-assignment]\n    try:\n        yield","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/client/client.py#L157-L193","documentation":"In 'auto' connect mode the client probes server/discover at the newest modern version; the probe result must satisfy the strict per-version schema (resultType/ttlMs/cacheScope required) that will govern later calls. A result that parses loosely but fails validate_server_result is rejected via MCPError(INVALID_PARAMS) so negotiate_auto falls back to the classic initialize handshake instead of adopting an era the server cannot actually serve.","triggerScenarios":"Connecting with mode='auto' (the default) to a server that responds to server/discover with a result missing required fields (or wrong shape) for the probed modern version — e.g. a partially-implemented or handshake-era server that answers the probe non-conformantly.","commonSituations":"Server upgraded/downgraded between MCP spec versions; a proxy or mocked server returning a simplified discover payload; third-party server implementing an older draft of the modern protocol surface.","solutions":["Nothing is broken client-side: the SDK automatically falls back to the initialize handshake; verify the connection works after connect.","If the fallback is unwanted, force mode='legacy' to skip the modern probe entirely.","Update the server to a version whose server/discover result is conformant with the modern protocol version.","Enable debug logging to inspect the validation error (pydantic ValidationError) if you are developing the server."],"exampleFix":"// before: auto probing hits a non-conformant server\nclient = Client('https://mcp.example.com/mcp', mode='auto')\n// after: skip the modern probe for a handshake-era server\nclient = Client('https://mcp.example.com/mcp', mode='legacy')","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    async with client:  # mode='auto': SDK falls back to initialize handshake automatically\n        await client.list_tools()\nexcept Exception as e:\n    from mcp import McpError\n    if isinstance(e, McpError) and e.error.code == -32602:  # INVALID_PARAMS\n        client = Client(url, mode='legacy')  # force handshake-era path\n        async with client:\n            await client.list_tools()","preventionTips":["Keep client and server SDK versions aligned with the same MCP protocol generation.","For known handshake-era servers, opt into mode='legacy' explicitly instead of relying on the probe fallback.","If you develop a server, validate its server/discover payload against the strict per-version schema (resultType/ttlMs/cacheScope).","Enable debug logging during connect to see the underlying ValidationError early."],"tags":["protocol","negotiation","mcp","client"],"backgroundTag":"protocol-version-mismatch","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}