modelcontextprotocol / modelcontextprotocol/python-sdk
[v2] RFC 9728 PRM URLs and resource matching drop query components
還沒有人認領這個 Issue。
- 主要語言
- Python
- 星號
- 24.3k
- 分支
- 4k
- 平均合併
- 1 天 1 小時
- 30 天內合併 PR
- 31
描述
What happened?
While testing the v2 auth/protected-resource-metadata path, I noticed query-bearing resource identifiers are treated as if the query is not part of the resource.
For a resource server URL like:
https://api.example.com/mcp?tenant=a
three SDK paths currently drop or ignore ?tenant=a:
mcp.server.auth.routes.build_resource_metadata_url()returns:
https://api.example.com/.well-known/oauth-protected-resource/mcp
mcp.client.auth.utils.build_protected_resource_metadata_discovery_urls(None, resource)also tries:
https://api.example.com/.well-known/oauth-protected-resource/mcp
https://api.example.com/.well-known/oauth-protected-resource
mcp.shared.auth_utils.check_resource_allowed()treats different query components as matching, so the client accepts protected resource metadata for?tenant=bwhen the server URL was?tenant=a.
This is latent for path-only deployments, but it matters for query-routed or multi-tenant resource identifiers.
What did you expect?
RFC 9728 derives the protected-resource metadata URL by inserting /.well-known/oauth-protected-resource before the protected resource path and/or query. If the resource identifier includes a query component, the derived metadata URL and resource validation should not silently collapse it with a different query.
For https://api.example.com/mcp?tenant=a, I expected the path-specific metadata URL to be:
https://api.example.com/.well-known/oauth-protected-resource/mcp?tenant=a
And a PRM document whose resource is https://api.example.com/mcp?tenant=b should not validate for a client configured with https://api.example.com/mcp?tenant=a.
Code to reproduce
import anyio
from pydantic import AnyHttpUrl, AnyUrl
from mcp.client.auth import OAuthClientProvider
from mcp.client.auth.utils import build_protected_resource_metadata_discovery_urls
from mcp.server.auth.routes import build_resource_metadata_url
from mcp.shared.auth import OAuthClientMetadata, ProtectedResourceMetadata
from mcp.shared.auth_utils import check_resource_allowed
from tests.interaction.auth._harness import InMemoryTokenStorage
async def main() -> None:
resource = "https://api.example.com/mcp?tenant=a"
print(build_resource_metadata_url(AnyHttpUrl(resource)))
print(build_protected_resource_metadata_discovery_urls(None, resource))
print(check_resource_allowed(
"https://api.example.com/mcp?tenant=a",
"https://api.example.com/mcp?tenant=b",
))
provider = OAuthClientProvider(
server_url=resource,
client_metadata=OAuthClientMetadata(
client_name="probe",
client_uri=AnyHttpUrl("https://example.com"),
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
),
storage=InMemoryTokenStorage(),
)
prm = ProtectedResourceMetadata(
resource=AnyHttpUrl("https://api.example.com/mcp?tenant=b"),
authorization_servers=[AnyHttpUrl("https://auth.example.com")],
)
await provider._validate_resource_match(prm)
print("accepted mismatched query")
anyio.run(main)
Current output:
https://api.example.com/.well-known/oauth-protected-resource/mcp
['https://api.example.com/.well-known/oauth-protected-resource/mcp', 'https://api.example.com/.well-known/oauth-protected-resource']
True
accepted mismatched query
SDK version
Current main branch, v2 development line.
Area
Auth
AI-assisted (Claude/Codex) for navigation and review; change authored and understood by me.
貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
研究方向
從 mcp.server.auth.routes.build_resource_metadata_url()、mcp.client.auth.utils.build_protected_resource_metadata_discovery_urls() 和 mcp.shared.auth_utils.check_resource_allowed() 開始,然後追蹤 OAuthClientProvider._validate_resource_match()。重現 issue 中所示的帶查詢參數案例,並新增針對性的 auth 測試;完成的條件是中繼資料 URL 保留查詢參數,且不同的查詢元件不再相符。
由索引模型根據 Issue 內容生成。
評估
- 技術堆疊
- python
- 領域
- authentication
- Issue 類型
- 缺陷
- 難度
- 3/5
- 預估耗時
- 1-2 天
- 活躍度
- 活躍
- 描述清晰度
- 描述清楚
- 新手友好度
- 74/100