@@ -128,46 +128,52 @@ def get_client_metadata_scopes(
128128
129129def build_oauth_authorization_server_metadata_discovery_urls (auth_server_url : str | None , server_url : str ) -> list [str ]:
130130 """
131- Generate ordered list of (url, type) tuples for discovery attempts .
131+ Generate an ordered list of discovery URLs for authorization server metadata .
132132
133133 Args:
134134 auth_server_url: URL for the OAuth Authorization Metadata URL if found, otherwise None
135135 server_url: URL for the MCP server, used as a fallback if auth_server_url is None
136136 """
137-
138- if not auth_server_url :
139- # Legacy path using the 2025-03-26 spec:
140- # link: https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization
141- parsed = urlparse (server_url )
142- return [f"{ parsed .scheme } ://{ parsed .netloc } /.well-known/oauth-authorization-server" ]
143-
144137 urls : list [str ] = []
145- parsed = urlparse (auth_server_url )
138+
139+ # Prefer the advertised authorization server URL when available, otherwise fall back
140+ # to the configured server URL from the client configuration.
141+ source_url = auth_server_url or server_url
142+ parsed = urlparse (source_url )
146143 base_url = f"{ parsed .scheme } ://{ parsed .netloc } "
144+ path = (parsed .path or "" ).rstrip ("/" )
147145
148- # RFC 8414: Path-aware OAuth discovery
149- if parsed .path and parsed .path != "/" :
150- oauth_path = f"/.well-known/oauth-authorization-server{ parsed .path .rstrip ('/' )} "
151- urls .append (urljoin (base_url , oauth_path ))
146+ # If a direct metadata URL was provided, use it first without modification
147+ if auth_server_url and (
148+ auth_server_url .endswith ("/.well-known/openid-configuration" )
149+ or auth_server_url .endswith ("/.well-known/oauth-authorization-server" )
150+ ):
151+ urls .append (auth_server_url )
152152
153+ if path and path != "" :
153154 # RFC 8414 section 5: Path-aware OIDC discovery
154155 # See https://www.rfc-editor.org/rfc/rfc8414.html#section-5
155- oidc_path = f"/.well-known/openid-configuration{ parsed .path .rstrip ('/' )} "
156- urls .append (urljoin (base_url , oidc_path ))
157-
156+ urls .append (urljoin (base_url , f"/.well-known/oauth-authorization-server{ path } " ))
157+ # RFC 8414: also allows using the same logic for OpenID discovery.
158158 # https://openid.net/specs/openid-connect-discovery-1_0.html
159- oidc_path = f" { parsed . path . rstrip ( '/' ) } / .well-known/openid-configuration"
160- urls . append ( urljoin ( base_url , oidc_path ))
161- return urls
162-
163- # OAuth root
159+ urls . append ( urljoin ( base_url , f"/ .well-known/openid-configuration{ path } " ))
160+ # Symmetric path-local OAuth discovery
161+ urls . append ( urljoin ( base_url , f" { path } /.well-known/oauth-authorization-server" ))
162+ urls . append ( urljoin ( base_url , f" { path } /.well-known/openid-configuration" ))
163+ # Always include root-based fallbacks for servers that only expose metadata at the origin.
164164 urls .append (urljoin (base_url , "/.well-known/oauth-authorization-server" ))
165-
166165 # OIDC 1.0 fallback (appends to full URL per OIDC spec)
167- # https://openid.net/specs/openid-connect-discovery-1_0.html
168166 urls .append (urljoin (base_url , "/.well-known/openid-configuration" ))
169167
170- return urls
168+ # De-duplicate while preserving order
169+ seen : set [str ] = set ()
170+ deduped_urls : list [str ] = []
171+ for url in urls :
172+ if url not in seen :
173+ seen .add (url )
174+ deduped_urls .append (url )
175+
176+ return deduped_urls
171177
172178
173179async def handle_protected_resource_response (
0 commit comments