Initial Checks
Release line
2.x (current stable)
Description
What happens
Client(mode="auto") probes server/discover before falling back to the legacy initialize handshake.
ClientSession.send_discover() applies a fixed internal timeout:
DISCOVER_TIMEOUT_SECONDS = 10.0
When the first MCP request triggers an interactive OAuth authorization-code flow, that timeout includes the time spent waiting for the user to complete browser authorization.
If authorization takes longer than 10 seconds:
- The client sends a modern
server/discover request.
- The server returns
401, and the OAuth flow starts.
- The discovery request times out while waiting for the user callback.
mode="auto" falls back to legacy initialize.
- OAuth completes and correctly retries the original authenticated
server/discover.
- The retry receives a valid modern
DiscoverResult, but the client has already selected the legacy protocol.
As a result, a modern upstream is exposed as legacy only on cold interactive OAuth flows. Once credentials are cached, the same upstream negotiates 2026-07-28 correctly.
Expected behavior
Interactive OAuth should not cause a modern server to be classified as legacy solely because user authorization took longer than the discovery timeout.
Environment
mcp==2.1.1
- Python 3.12
- Streamable HTTP transport
Client(mode="auto")
OAuthClientProvider
The current v2.2.0 source still defines DISCOVER_TIMEOUT_SECONDS = 10.0 and uses it directly in send_discover().
Suggested direction
A public configuration point for the discovery timeout would let interactive clients choose an appropriate bound, for example:
Client(
transport,
mode="auto",
discover_timeout_seconds=300.0,
)
That value would need to flow into ClientSession.send_discover() instead of relying only on the fixed module-level constant.
Alternatively, the SDK could avoid counting time spent in an interactive OAuth authorization flow against the discovery timeout.
Example Code
import asyncio
import json
import anyio
import httpx2
from mcp.client import Client
from mcp.client.streamable_http import streamable_http_client
class SlowInteractiveOAuth(httpx2.Auth):
"""Models an OAuth flow which takes longer than the 10s discover timeout."""
requires_response_body = True
async def async_auth_flow(self, request: httpx2.Request):
response = yield request
if response.status_code == 401:
# Simulate the user taking 11 seconds to complete browser authorization.
await anyio.sleep(11)
# Retry the exact original request, now authenticated.
request.headers["Authorization"] = "Bearer access-token"
yield request
async def handler(request: httpx2.Request) -> httpx2.Response:
payload = json.loads(request.content)
method = payload.get("method")
request_id = payload.get("id")
if method == "server/discover":
if "authorization" not in request.headers:
return httpx2.Response(
401,
headers={"WWW-Authenticate": "Bearer"},
)
# The authenticated retry is a valid modern response.
return httpx2.Response(
200,
json={
"jsonrpc": "2.0",
"id": request_id,
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {},
"ttlMs": 0,
"cacheScope": "private",
},
},
)
if method == "initialize":
return httpx2.Response(
200,
json={
"jsonrpc": "2.0",
"id": request_id,
"result": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"serverInfo": {
"name": "legacy-fallback",
"version": "1.0",
},
},
},
)
return httpx2.Response(202)
async def main() -> None:
async with httpx2.AsyncClient(
transport=httpx2.MockTransport(handler),
auth=SlowInteractiveOAuth(),
) as http_client, Client(
streamable_http_client(
"https://mcp.example.test/mcp",
http_client=http_client,
),
mode="auto",
) as client:
print(client.protocol_version)
asyncio.run(main())
Expected output:
2026-07-28
Actual output:
2025-11-25
Python & MCP Python SDK
Initial Checks
Release line
2.x (current stable)
Description
What happens
Client(mode="auto")probesserver/discoverbefore falling back to the legacyinitializehandshake.ClientSession.send_discover()applies a fixed internal timeout:When the first MCP request triggers an interactive OAuth authorization-code flow, that timeout includes the time spent waiting for the user to complete browser authorization.
If authorization takes longer than 10 seconds:
server/discoverrequest.401, and the OAuth flow starts.mode="auto"falls back to legacyinitialize.server/discover.DiscoverResult, but the client has already selected the legacy protocol.As a result, a modern upstream is exposed as legacy only on cold interactive OAuth flows. Once credentials are cached, the same upstream negotiates
2026-07-28correctly.Expected behavior
Interactive OAuth should not cause a modern server to be classified as legacy solely because user authorization took longer than the discovery timeout.
Environment
mcp==2.1.1Client(mode="auto")OAuthClientProviderThe current
v2.2.0source still definesDISCOVER_TIMEOUT_SECONDS = 10.0and uses it directly insend_discover().Suggested direction
A public configuration point for the discovery timeout would let interactive clients choose an appropriate bound, for example:
That value would need to flow into
ClientSession.send_discover()instead of relying only on the fixed module-level constant.Alternatively, the SDK could avoid counting time spent in an interactive OAuth authorization flow against the discovery timeout.
Example Code
Expected output:
2026-07-28
Actual output:
2025-11-25
Python & MCP Python SDK