From 93b7315d24c63fcae2ab8e2262a8600011fae47d Mon Sep 17 00:00:00 2001 From: Br1an67 <932039080@qq.com> Date: Mon, 2 Mar 2026 01:09:35 +0800 Subject: [PATCH] fix: include transport path in Protected Resource Metadata resource URL The resource field in /.well-known/oauth-protected-resource was set to the base resource_server_url (e.g. http://localhost:8000/) instead of the actual protected endpoint URL (e.g. http://localhost:8000/mcp). Per RFC 9728, the resource identifier must match the URL that clients use to access the protected resource. Append the transport path (streamable_http_path or sse_path) to the resource_server_url in both lowlevel/server.py and mcpserver/server.py. --- src/mcp/server/lowlevel/server.py | 9 +++++++-- src/mcp/server/mcpserver/server.py | 12 ++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index aee644040..df185abc4 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -46,6 +46,7 @@ async def main(): import anyio from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +from pydantic import AnyHttpUrl from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.authentication import AuthenticationMiddleware @@ -595,8 +596,11 @@ def streamable_http_app( # Determine resource metadata URL resource_metadata_url = None if auth and auth.resource_server_url: + # The protected resource URL must include the transport path + # (e.g. http://localhost:8000/mcp) per RFC 9728 + actual_resource_url = AnyHttpUrl(str(auth.resource_server_url).rstrip("/") + streamable_http_path) # Build compliant metadata URL for WWW-Authenticate header - resource_metadata_url = build_resource_metadata_url(auth.resource_server_url) + resource_metadata_url = build_resource_metadata_url(actual_resource_url) routes.append( Route( @@ -615,9 +619,10 @@ def streamable_http_app( # Add protected resource metadata endpoint if configured as RS if auth and auth.resource_server_url: # pragma: no cover + actual_resource_url = AnyHttpUrl(str(auth.resource_server_url).rstrip("/") + streamable_http_path) routes.extend( create_protected_resource_routes( - resource_url=auth.resource_server_url, + resource_url=actual_resource_url, authorization_servers=[auth.issuer_url], scopes_supported=auth.required_scopes, ) diff --git a/src/mcp/server/mcpserver/server.py b/src/mcp/server/mcpserver/server.py index 9c7105a7b..c45a838a0 100644 --- a/src/mcp/server/mcpserver/server.py +++ b/src/mcp/server/mcpserver/server.py @@ -982,10 +982,15 @@ async def handle_sse(scope: Scope, receive: Receive, send: Send): # pragma: no # Determine resource metadata URL resource_metadata_url = None if self.settings.auth and self.settings.auth.resource_server_url: + from pydantic import AnyHttpUrl + from mcp.server.auth.routes import build_resource_metadata_url + # The protected resource URL must include the transport path + # (e.g. http://localhost:8000/sse) per RFC 9728 + actual_resource_url = AnyHttpUrl(str(self.settings.auth.resource_server_url).rstrip("/") + sse_path) # Build compliant metadata URL for WWW-Authenticate header - resource_metadata_url = build_resource_metadata_url(self.settings.auth.resource_server_url) + resource_metadata_url = build_resource_metadata_url(actual_resource_url) # Auth is enabled, wrap the endpoints with RequireAuthMiddleware routes.append( @@ -1023,11 +1028,14 @@ async def sse_endpoint(request: Request) -> Response: # pragma: no cover ) # Add protected resource metadata endpoint if configured as RS if self.settings.auth and self.settings.auth.resource_server_url: # pragma: no cover + from pydantic import AnyHttpUrl + from mcp.server.auth.routes import create_protected_resource_routes + actual_resource_url = AnyHttpUrl(str(self.settings.auth.resource_server_url).rstrip("/") + sse_path) routes.extend( create_protected_resource_routes( - resource_url=self.settings.auth.resource_server_url, + resource_url=actual_resource_url, authorization_servers=[self.settings.auth.issuer_url], scopes_supported=self.settings.auth.required_scopes, )