siyuan-note/siyuan · error

OAuth protected resource metadata not found

Error message

OAuth protected resource metadata not found

What it means

Returned at oauth.go:510-511 when every candidate URL produced by protectedResourceURLs fails to yield a valid PRM document. Candidates include the resource_metadata hint from the WWW-Authenticate challenge and the well-known paths /.well-known/oauth-protected-resource and /.well-known/oauth-protected-resource/<path>; all of them errored in GetProtectedResourceMetadata.

Source

Thrown at kernel/mcp/client/oauth.go:511

func discoverProtectedResource(ctx context.Context, challenges []oauthex.Challenge, resource string, client *http.Client) (*discoveredProtectedResource, error) {
	metadataURL := ""
	for _, challenge := range challenges {
		if strings.EqualFold(challenge.Scheme, "bearer") && challenge.Params["resource_metadata"] != "" {
			metadataURL = challenge.Params["resource_metadata"]
			break
		}
	}
	for _, candidate := range protectedResourceURLs(metadataURL, resource) {
		prm, err := oauthex.GetProtectedResourceMetadata(ctx, candidate.URL, candidate.Resource, client)
		if err != nil {
			continue
		}
		if len(prm.AuthorizationServers) == 0 {
			return nil, fmt.Errorf("OAuth protected resource metadata has no authorization server")
		}
		return &discoveredProtectedResource{ProtectedResourceMetadata: prm, MetadataURL: candidate.URL}, nil
	}
	return nil, fmt.Errorf("OAuth protected resource metadata not found")
}

func (h *mcpOAuthHandler) validateCredentialIssuer(ctx context.Context, credential oauthCredential) (bool, error) {
	var challenges []oauthex.Challenge
	resource := h.server.URL
	if credential.ResourceMetadataURL != "" {
		challenges = []oauthex.Challenge{{Scheme: "bearer", Params: map[string]string{"resource_metadata": credential.ResourceMetadataURL}}}
		resource = credential.Resource
	}
	prm, err := discoverProtectedResource(ctx, challenges, resource, h.client)
	if err != nil {
		return false, fmt.Errorf("validate OAuth protected resource: %w", err)
	}
	if prm.Resource != credential.Resource || len(prm.AuthorizationServers) == 0 {
		return false, nil
	}
	asm, err := auth.GetAuthServerMetadata(ctx, prm.AuthorizationServers[0], h.client)
	if err != nil {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Confirm the resource server implements RFC 9728 and serves /.well-known/oauth-protected-resource at the resource origin.
  2. Check the WWW-Authenticate challenge's resource_metadata hint (if present) and verify that URL is reachable.
  3. Verify network connectivity / TLS / proxy to the resource server URL.
  4. Ensure the resource URL in the MCP server config (h.server.URL) is correct and stable (no trailing path or query that shifts the well-known location).
Defensive patterns

Strategy: retry

Try / catch

// discoverProtectedResource iterates candidate URLs; transient failures yield error 336.
prm, err := discoverProtectedResource(ctx, challenges, resource, client)
if err != nil {
    // Retry once on transient network failures before surfacing as a hard config error.
    if isTransientNetworkErr(err) { prm, err = discoverProtectedResource(ctx, challenges, resource, client) }
    if err != nil { return err } // genuine RFC 9728 gap or persistent network issue
}

Prevention

When it happens

Trigger: Initial discovery (Authorize) or issuer validation where none of the candidate URLs return a fetchable, parseable PRM document. Each candidate's error is silently continued past via the `continue` at oauth.go:503-505.

Common situations: Resource server does not implement RFC 9728 (no well-known endpoint); resource URL incorrect or redirected; 404 on all candidates; network blocked to the resource server; malformed JSON in the document causing parse errors.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/fc3d0cf5f3468519. Report an issue: GitHub.