OAuth client: authorization URL is built with a second `?` when the advertised `authorization_endpoint` already carries a query (RFC 6749 §3.1)

未關閉
#3,505 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

評估

難度
2/5
預估耗時
1-3 小時
新手友好度
35/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
python

研究方向

從 src/mcp/client/auth/oauth2.py:427 開始,檢查 _perform_authorization 如何建構其 URL。閱讀 tests/client/test_auth.py,執行其中的 pytest 命令,並涵蓋一個已有 query 的 authorization_endpoint;當現有 query 被保留且授權參數被正確加入,同時 ruff 和 pyright 仍然通過時,即表示完成。

由索引模型根據 Issue 內容生成。

描述

v1 v2
Initial Checks
  • I confirm that I'm using the newest release of my line (verified on 2.2.0 and 1.30.0, and on main)
  • I confirm that I searched for my issue in the issues before opening this one (searched for "authorization_endpoint query", "authorization_url urlencode", "second ?")
Release line

v2 (and v1 — same code)

Description

OAuthClientProvider._perform_authorization builds the browser redirect as

authorization_url = f"{auth_endpoint}?{urlencode(auth_params)}"   # src/mcp/client/auth/oauth2.py:427 on main

auth_endpoint comes straight from the server's RFC 8414 metadata (authorization_endpoint). RFC 6749 §3.1 says that URI "MAY include an application/x-www-form-urlencoded formatted query component, which MUST be retained when adding additional query parameters". When it does carry one, the f-string produces a second ?:

advertised:  https://auth.example.com/authorize?tenant=acme
sent:        https://auth.example.com/authorize?tenant=acme?response_type=code&client_id=…&redirect_uri=…&state=…&code_challenge=…

The authorization server then receives tenant = "acme?response_type=code" and no response_type at all — a hard failure at the consent page, on every authorization, for every server whose endpoint carries a query. Servers do advertise such endpoints: a tenant/policy selector (Azure AD B2C's ?p=<policy> is the well-known one), or — how we hit it — an environment/tier tag on a multi-tenant consent app (Nevermined advertises https://nevermined.app/oauth/authorize?network=sandbox|live because one consent app fronts two authorization servers). The TypeScript SDK is unaffected: client/auth.js builds the URL with new URL(endpoint) + searchParams.set(...), which retains the existing query.

Example Code

Minimal reproduction of the URL construction (no server needed):

from urllib.parse import urlencode

auth_endpoint = "https://auth.example.com/authorize?tenant=acme"   # from RFC 8414 metadata
auth_params = {"response_type": "code", "client_id": "c", "state": "s"}

print(f"{auth_endpoint}?{urlencode(auth_params)}")
# https://auth.example.com/authorize?tenant=acme?response_type=code&client_id=c&state=s
#                                                ^ second '?' — the server sees tenant="acme?response_type=code"

Expected (RFC 6749 §3.1):

https://auth.example.com/authorize?tenant=acme&response_type=code&client_id=c&state=s

Proposed fix — merge onto the existing query instead of concatenating:

from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit

def build_authorization_url(authorization_endpoint: str, params: dict[str, str]) -> str:
    parts = urlsplit(authorization_endpoint)
    query = parse_qsl(parts.query, keep_blank_values=True) + list(params.items())
    return urlunsplit(parts._replace(query=urlencode(query)))

I have this change ready on a branch — https://github.com/r-marques/python-sdk/tree/fix/authorization-url-retains-endpoint-query — as a small PR (helper + two unit tests + one flow test that drives _perform_authorization with a query-bearing authorization_endpoint; uv run pytest tests/client/test_auth.py → 163 passed / 1 xfailed, ruff + pyright clean) and would be glad to open it if you'd like to take an outside PR for this — happy to defer to a maintainer fix otherwise.

Disclosure: drafted with AI assistance (Claude Code); the behaviour was verified by hand against the 1.30.0 and 2.2.0 wheels and main, and I can explain every line of the proposed change.

Python & MCP Python SDK
Python 3.14.7
mcp 2.2.0 (also reproduced on 1.30.0; the line is unchanged on main @ oauth2.py:427)
主要語言
Python
星號
24.3k
分支
4k
平均合併
1 天 1 小時
30 天內合併 PR
31

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

modelcontextprotocol/python-sdk 的其他 Issue

查看 modelcontextprotocol/python-sdk 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。