zed-industries/zed · error

Could not fetch Protected Resource Metadata for {}

Error message

Could not fetch Protected Resource Metadata for {}

What it means

Every candidate URL for the Protected Resource Metadata document failed, so fetch_protected_resource_metadata() gives up. Candidates are the resource_metadata hint from the WWW-Authenticate header (if present) plus the RFC 9724 well-known locations derived from the server URL; each failure is logged at debug level ('Failed to fetch Protected Resource Metadata from ...') before the final bail mentions only the server_url. The individual causes are hidden unless debug logging is enabled, so the underlying reasons are transport errors, non-2xx statuses, or JSON parse failures from fetch_json.

Source

Thrown at crates/context_server/src/oauth.rs:797

                    );
                }
                return Ok(ProtectedResourceMetadata {
                    resource: response.resource.unwrap_or_else(|| server_url.clone()),
                    authorization_servers: response.authorization_servers,
                    scopes_supported: response.scopes_supported,
                });
            }
            Err(err) => {
                log::debug!(
                    "Failed to fetch Protected Resource Metadata from {}: {}",
                    url,
                    err
                );
            }
        }
    }

    bail!(
        "Could not fetch Protected Resource Metadata for {}",
        server_url
    )
}

/// Fetch Authorization Server Metadata, trying RFC 8414 and OIDC Discovery
/// endpoints in the priority order specified by the MCP spec.
pub async fn fetch_auth_server_metadata(
    http_client: &Arc<dyn HttpClient>,
    issuer: &Url,
) -> Result<AuthServerMetadata> {
    let candidate_urls = auth_server_metadata_urls(issuer);

    for url in &candidate_urls {
        match fetch_json::<AuthServerMetadataResponse>(http_client, url).await {
            Ok(response) => {
                let reported_issuer = response.issuer.unwrap_or_else(|| issuer.clone());

View on GitHub (pinned to f4178619ac)

Solutions

  1. Serve a valid RFC 9724 JSON document at /.well-known/oauth-protected-resource (and the port-specific variant for non-443 ports) with Content-Type application/json
  2. Set resource_metadata in the WWW-Authenticate Bearer challenge to the exact URL where the document lives so the hint candidate succeeds
  3. Enable debug logging to see the per-URL failure reasons, then curl each candidate URL from the client machine to verify reachability and JSON validity
  4. If the server cannot support OAuth discovery, switch the MCP connection to a transport/auth mode that does not require it (e.g. header-based auth)

Example fix

# before: well-known file missing (all candidates 404)
$ curl -i https://mcp.example.com/.well-known/oauth-protected-resource
HTTP/1.1 404 Not Found

# after: serve the document
$ curl -i https://mcp.example.com/.well-known/oauth-protected-resource
HTTP/1.1 200 OK
Content-Type: application/json

{"resource":"https://mcp.example.com","authorization_servers":["https://auth.example.com"]}
Defensive patterns

Strategy: try-catch

Validate before calling

// client-side preflight: probe all candidate PRM URLs before starting the flow
async fn prm_reachable(http: &Arc<dyn HttpClient>, urls: &[Url]) -> Option<Url> {
    for url in urls {
        if fetch_json::<serde_json::Value>(http, url).await.is_ok() {
            return Some(url.clone());
        }
    }
    None
}

Try / catch

match fetch_protected_resource_metadata(&client, &server_url, &challenge).await {
    Ok(meta) => Ok(meta),
    Err(err) if err.to_string().contains("Could not fetch Protected Resource Metadata") => {
        // enable debug logs to see per-URL causes; distinguish 404 (not implemented) from network errors
        log::debug!("PRM candidates failed for {server_url}");
        if is_transient_network(&err) { retry_with_backoff().await } else { Err(err) }
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: An MCP server returns 401 with a Bearer challenge, Zed then GETs each candidate /.well-known/oauth-protected-resource URL (and the resource_metadata hint), and every request errors — DNS failure, connection refused, TLS error, 404, non-JSON body, or a fetch_json parse/size error.

Common situations: Server implements 401 discovery but never deploys the well-known metadata file (404 on all candidates); metadata hosted on a different origin with CORS or a typo'd URL in resource_metadata; local dev server stopped between the 401 and the follow-up fetch; proxy blocking the .well-known path; document served as HTML error page.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/5522dd4e17c90fa8. Report an issue: GitHub.