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