* Fetches authorization server metadata, using a configured metadata URL if available, * otherwise performing RFC 9728 → RFC 8414 discovery via the SDK. * * Discovery order when no configured URL: * 1. RFC 9728: probe /.well-known/oauth-protected-resource on the MCP server, * read authorizat
( serverName: string, serverUrl: string, configuredMetadataUrl: string | undefined, fetchFn?: FetchLike, resourceMetadataUrl?: URL, )
| 254 | * — RFC 8414 mandates OAuth metadata retrieval over TLS. |
| 255 | */ |
| 256 | async function fetchAuthServerMetadata( |
| 257 | serverName: string, |
| 258 | serverUrl: string, |
| 259 | configuredMetadataUrl: string | undefined, |
| 260 | fetchFn?: FetchLike, |
| 261 | resourceMetadataUrl?: URL, |
| 262 | ): Promise<Awaited<ReturnType<typeof discoverAuthorizationServerMetadata>>> { |
| 263 | if (configuredMetadataUrl) { |
| 264 | if (!configuredMetadataUrl.startsWith('https://')) { |
| 265 | throw new Error( |
| 266 | `authServerMetadataUrl must use https:// (got: ${configuredMetadataUrl})`, |
| 267 | ) |
| 268 | } |
| 269 | const authFetch = fetchFn ?? createAuthFetch() |
| 270 | const response = await authFetch(configuredMetadataUrl, { |
| 271 | headers: { Accept: 'application/json' }, |
| 272 | }) |
| 273 | if (response.ok) { |
| 274 | return OAuthMetadataSchema.parse(await response.json()) |
| 275 | } |
| 276 | throw new Error( |
| 277 | `HTTP ${response.status} fetching configured auth server metadata from ${configuredMetadataUrl}`, |
| 278 | ) |
| 279 | } |
| 280 | |
| 281 | try { |
| 282 | const { authorizationServerMetadata } = await discoverOAuthServerInfo( |
| 283 | serverUrl, |
| 284 | { |
| 285 | ...(fetchFn && { fetchFn }), |
| 286 | ...(resourceMetadataUrl && { resourceMetadataUrl }), |
| 287 | }, |
| 288 | ) |
| 289 | if (authorizationServerMetadata) { |
| 290 | return authorizationServerMetadata |
| 291 | } |
| 292 | } catch (err) { |
| 293 | // Any error from the RFC 9728 → RFC 8414 chain (5xx from the root or |
| 294 | // resolved-AS probe, schema parse failure, network error) — fall through |
| 295 | // to the legacy path-aware retry. |
| 296 | logMCPDebug( |
| 297 | serverName, |
| 298 | `RFC 9728 discovery failed, falling back: ${errorMessage(err)}`, |
| 299 | ) |
| 300 | } |
| 301 | |
| 302 | // Fallback only when the URL has a path component; for root URLs the SDK's |
| 303 | // own fallback already probed the same endpoints. |
| 304 | const url = new URL(serverUrl) |
| 305 | if (url.pathname === '/') { |
| 306 | return undefined |
| 307 | } |
| 308 | return discoverAuthorizationServerMetadata(url, { |
| 309 | ...(fetchFn && { fetchFn }), |
| 310 | }) |
| 311 | } |
| 312 | |
| 313 | export class AuthenticationCancelledError extends Error { |
no test coverage detected