MCP OAuth 授权地址多一个问号
MCP 授权地址已有 query,旧 Python 客户端又加问号,导致 response_type 丢失。保留租户或环境参数,核对 URL 合并、版本与回调清理。
MCP 的浏览器授权页报 invalid_request,或登录后停在普通欢迎页,回调一直收不到授权码。如果授权服务器公布的 authorization_endpoint 已经带查询参数,先看客户端实际打开的地址是否多拼了一个 ?。
例如,服务端公布:
https://auth.example.com/authorize?tenant=acme
旧 Python MCP 客户端可能拼成:
https://auth.example.com/authorize?tenant=acme?response_type=code&client_id=...
授权服务器读到的 tenant 就变成 acme?response_type=code,并没有得到独立的 response_type。这足以让授权流程失败,不能直接认定是账号密码或本机回调端口有问题。
核对公布地址与生成地址
已有 SDK 1.30.0、2.2.0 的近期反馈;已核对的正式版 2.3.0 授权 URL 构造仍使用“端点加问号,再加编码参数”的路径。这不是升级到 2.3.0 就已经消失的问题。
比较可信公开 metadata 的 authorization_endpoint 与客户端生成的授权地址,保留原有 tenant、env、policy 或其他选择参数。它们可能决定授权环境和租户,删除查询串让 URL 看起来整齐,会改变授权目标。
记录时应脱敏 state、授权码和其他会话信息,不能把完整真实授权 URL 贴进公开日志。这篇检查的是浏览器授权地址的 query 合并;metadata 的资源身份不一致则按公开资源端点检查处理。
修复在 URL 构造处合并 query
处理方向是在原授权端点的 query 中加入本次 OAuth 参数,保留已有参数,并继续使用 SDK 生成的 PKCE、state、回调地址和后续 issuer 校验。正常形状应让原来的 tenant=acme 与 response_type=code 分别成为参数。
不能对整条 URL 把第二个问号简单全局替换成 &,也不能自己另造一组 state 或 PKCE 值。若维护本地补丁,应针对所用版本的 _perform_authorization_code_grant 审查改动,用 URL 解析和编码处理 query;修复后仍需完整保留原授权与回调验证。
已有真实部署反馈表明,改正连接分隔符可让不含参数冲突的受影响端点完成授权,但这不等于任意端点都适合字符替换。原端点若已经带 resource、prompt 或其他本次流程也要生成的键,还要明确处理冲突,不能靠服务端“取第一个”或“取最后一个”碰运气。
授权端点不应带 fragment,同一请求参数也不应重复。自建 query 合并逻辑应明确拒绝无效或有歧义的输入;不能把允许重复键的通用 URL 工具行为,直接当成 OAuth 参数冲突已经解决。
目前不能给这处问题指定已核证的正式修复发行版。选择后续版本或下游修复时,要检查实际 URL 构造代码与兼容性;未合入补丁、重复问题关闭或版本号增大,都不足以证明修复已经进入所装的软件。
回调端口错误可能是后续症状
授权立即失败后,应用若反复发起登录,而上一轮回调监听器还没关闭,可能接着报 Address already in use。应先结束旧的授权尝试、按应用生命周期清理自己拥有的监听器,再检查生成地址。
这不表示所有端口占用都由 OAuth URL 造成。不要为隐藏症状结束未知进程,或不断换端口启动更多监听器。
在获准的测试环境中,先按参数解析确认原 query 未变、response_type=code 独立存在,再走授权与回调,确认 state、issuer 和 PKCE 校验仍有效。还要检查没有 query 的普通端点,以及取消或失败后的监听器清理。仅看到登录页出现,不能证明授权码与令牌流程已经完成。


