Skip to content

Commit 140c701

Browse files
committed
update auth url discovery
1 parent 65b36de commit 140c701

File tree

1 file changed

+30
-24
lines changed

1 file changed

+30
-24
lines changed

src/mcp/client/auth/utils.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -128,46 +128,52 @@ def get_client_metadata_scopes(
128128

129129
def 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

173179
async def handle_protected_resource_response(

0 commit comments

Comments
 (0)