Skip to content

Commit 2b85dbd

Browse files
committed
refactor: improve OAuth protected resource metadata URL construction per RFC 9728
- Replace string manipulation with proper URL parsing using urllib.parse.urlparse - Handle edge case where resource server path is "/" by treating as empty string - Ensure consistent URL construction for both WWW-Authenticate header and metadata endpoint - Add RFC 9728 §3.1 compliance comments for better code documentation
1 parent 146d7ef commit 2b85dbd

File tree

1 file changed

+16
-2
lines changed

1 file changed

+16
-2
lines changed

src/mcp/server/fastmcp/server.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -938,9 +938,15 @@ def streamable_http_app(self) -> Starlette:
938938
resource_metadata_url = None
939939
if self.settings.auth and self.settings.auth.resource_server_url:
940940
from pydantic import AnyHttpUrl
941+
from urllib.parse import urlparse
941942

943+
# RFC 9728 §3.1: Insert /.well-known/oauth-protected-resource between host and resource path
944+
# This URL will be used in WWW-Authenticate header for client discovery
945+
parsed = urlparse(str(self.settings.auth.resource_server_url))
946+
# Handle trailing slash: if path is just "/", treat as empty
947+
resource_path = parsed.path if parsed.path != "/" else ""
942948
resource_metadata_url = AnyHttpUrl(
943-
str(self.settings.auth.resource_server_url).rstrip("/") + "/.well-known/oauth-protected-resource"
949+
f"{parsed.scheme}://{parsed.netloc}/.well-known/oauth-protected-resource{resource_path}"
944950
)
945951

946952
routes.append(
@@ -963,15 +969,23 @@ def streamable_http_app(self) -> Starlette:
963969
from mcp.server.auth.handlers.metadata import ProtectedResourceMetadataHandler
964970
from mcp.server.auth.routes import cors_middleware
965971
from mcp.shared.auth import ProtectedResourceMetadata
972+
from urllib.parse import urlparse
966973

967974
protected_resource_metadata = ProtectedResourceMetadata(
968975
resource=self.settings.auth.resource_server_url,
969976
authorization_servers=[self.settings.auth.issuer_url],
970977
scopes_supported=self.settings.auth.required_scopes,
971978
)
979+
980+
# RFC 9728 §3.1: Register route at /.well-known/oauth-protected-resource + resource path
981+
parsed = urlparse(str(self.settings.auth.resource_server_url))
982+
# Handle trailing slash: if path is just "/", treat as empty
983+
resource_path = parsed.path if parsed.path != "/" else ""
984+
well_known_path = f"/.well-known/oauth-protected-resource{resource_path}"
985+
972986
routes.append(
973987
Route(
974-
"/.well-known/oauth-protected-resource",
988+
well_known_path,
975989
endpoint=cors_middleware(
976990
ProtectedResourceMetadataHandler(protected_resource_metadata).handle,
977991
["GET", "OPTIONS"],

0 commit comments

Comments
 (0)