`Client(mode=”auto”)` falls back to legacy when interactive OAuth exceeds the fixed `server/discover` timeout

当 Client(mode="auto") 访问一个走交互式 OAuth 的现代 MCP 服务器时,如果用户在浏览器里完成授权超过 10 秒, server/discover 探测会本地超时并让客户端错误地回退到 legacy 的 initialize 握手;优先排查 OAuth 授权耗时是否超过固

快速结论:当 Client(mode="auto") 访问一个走交互式 OAuth 的现代 MCP 服务器时,如果用户在浏览器里完成授权超过 10 秒,server/discover 探测会本地超时并让客户端错误地回退到 legacy 的 initialize 握手;优先排查 OAuth 授权耗时是否超过固定 discovery 超时。

适用环境:已确认使用 mcp==2.1.1(讨论中提到 v2.2.0 源码仍存在该常量)、Python 3.12、Streamable HTTP transport、Client(mode="auto") 及 OAuthClientProvider;Issue 未提供操作系统、CUDA、显卡等更多信息。

最快修复方案:暂无确认的一步修复方案。该 Issue 已关闭,但讨论中提到的可配置 discover_timeout_seconds、区分本地超时来源、或不计入交互式 OAuth 等待时间,均属于建议方向,未在 Issue 中确认合入。

注意事项:不要仅把 10 秒常量改大当作通用修复,因为问题还涉及“本地超时被当作远端协议时代证据”的语义混淆;另外,凭据一旦缓存,同一 upstream 会正常协商到 2026-07-28,所以问题只在冷启动的交互式 OAuth 流程中复现,容易漏测。

问题场景

在使用 MCP Python SDK 的 Client(mode="auto") 连接现代 MCP 服务器时,如果第一次请求触发交互式 OAuth 授权码流程(例如用户需要在浏览器中登录并授权),就会触发该问题。流程大致为:客户端先发送现代的 server/discover 请求,服务器返回 401 并启动 OAuth;等待用户回调的时间被计入 server/discover 的固定超时预算;一旦超过 10 秒,mode="auto" 就回退到 legacy initialize 握手,即使随后 OAuth 完成、认证后的 server/discover 重试返回了合法的现代 DiscoverResult,客户端也已经选定了 legacy 协议。

报错原文

Client(mode="auto") falls back to legacy when interactive OAuth exceeds the fixed `server/discover` timeout

DISCOVER_TIMEOUT_SECONDS = 10.0

原因分析

最可能的原因是 ClientSession.send_discover() 使用了固定的内部超时 DISCOVER_TIMEOUT_SECONDS = 10.0,而该超时预算覆盖了交互式 OAuth 等待用户完成浏览器授权的时间。讨论中进一步指出,调用链为 Client(mode="auto") → negotiate_auto() → send_discover() → CallOptions(timeout=10.0) → JSONRPCDispatcher.send_raw_request() → Streamable HTTP POST task → 401 / interactive OAuth → redirect_handler() / callback_handler() → authenticated retry。当本地 10 秒预算耗尽时,JSONRPCDispatcher 会将其转为 MCPError(REQUEST_TIMEOUT);随后 negotiate_auto() 几乎把任何 MCPError 都当作回退到 initialize() 的充分理由。也就是说,本地生成的超时被误用为关于远端服务器协议时代的证据。讨论中提出三种可能的修复层面:将 discovery 超时做成可配置项、保留超时来源以区分本地 deadline 与对端 REQUEST_TIMEOUT、或者不把交互式授权等待时间计入 discovery 服务时间预算。

环境排查

  • 确认 MCP Python SDK 版本:Issue 中为 mcp==2.1.1,讨论提到 v2.2.0 源码仍定义 DISCOVER_TIMEOUT_SECONDS = 10.0。
  • 确认 Python 版本:Issue 中为 Python 3.12。
  • 确认传输方式:Issue 中使用 Streamable HTTP transport。
  • 确认客户端模式:Client(mode="auto")。
  • 确认认证方式:OAuthClientProvider,且为冷启动、无缓存凭据的交互式 OAuth 流程。
  • 确认首次请求是否触发 401 并进入浏览器授权,以及用户完成授权是否可能超过 10 秒。
  • 本地超时后是否出现回退到 legacy initialize 的日志或协议协商结果。
  • 凭据缓存后重试同一 upstream 时,是否能正常协商到 2026-07-28。

解决步骤

  1. 先复现并确认触发条件:在无缓存凭据的情况下,用 Client(mode="auto") 连接一个需要交互式 OAuth 的现代 MCP 服务器,让浏览器授权耗时超过 10 秒,观察是否回退到 legacy initialize。
  2. 检查当前 SDK 源码中 ClientSession.send_discover() 是否仍然直接使用固定的 DISCOVER_TIMEOUT_SECONDS = 10.0,以及 CallOptions(timeout=10.0) 是否进入该路径。
  3. 对照讨论中的调用链,确认超时是由本地 JSONRPCDispatcher 生成,而不是由对端服务器返回的 REQUEST_TIMEOUT;区分这两者对判断回退逻辑是否合理很关键。
  4. 如果只是需要在当前版本绕过,可优先尝试缩短浏览器端授权时间(例如预先登录、减少跳转步骤),或先完成一次授权让凭据缓存,但这只是规避而非修复。
  5. 如果希望按讨论中的方向修复,可关注或参考“在 Client / ClientSession 上配置 discover_timeout_seconds 并向后兼容”的 PR 方向;在合入前不要假设该配置项已存在。
  6. 如果自行修改,注意只调大常量可能不足以覆盖语义问题;讨论中建议保留超时来源,避免本地 deadline 被当作 legacy 回退的肯定证据。

验证方法

验证时应在无缓存凭据的冷启动场景下,让交互式 OAuth 授权耗时超过 DISCOVER_TIMEOUT_SECONDS,同时保持 mode="auto",确认客户端最终仍完成现代握手,而不是回退到 legacy initialize。讨论中提到可添加授权时间超过 DISCOVER_TIMEOUT_SECONDS 且 mode=auto 仍完成现代握手的测试。如果只是缓存凭据后重试,则不能证明问题已修复,因为凭据缓存场景本来就会正常协商到 2026-07-28。

参考来源

modelcontextprotocol/python-sdk #3601

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 27035

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注