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, )