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
- 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
- Set resource_metadata in the WWW-Authenticate Bearer challenge to the exact URL where the document lives so the hint candidate succeeds
- 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
- 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
- Serve the RFC 9724 document at every well-known location candidates are derived from, including the port-specific variant
- Set resource_metadata in the Bearer challenge so the highest-priority candidate is the correct one
- Log at debug level during integration so per-URL failures are visible when this bail appears
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
- Protected Resource Metadata at {} has no authorization_serve
- Could not fetch Authorization Server Metadata for {}
- OAuth endpoint must use HTTPS (got {}://{})
- OAuth endpoint must not point to private/reserved IP: {}
- OAuth endpoint must not point to private/reserved IP: ::ffff
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/5522dd4e17c90fa8.
Report an issue: GitHub.