{"record":{"id":"e2892859475ea59a","repo":"zed-industries/zed","slug":"protected-resource-metadata-at-has-no-authoriza","errorCode":null,"errorMessage":"Protected Resource Metadata at {} has no authorization_servers","messagePattern":"Protected Resource Metadata at (.+?) has no authorization_servers","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/context_server/src/oauth.rs","lineNumber":776,"sourceCode":"            urls\n        }\n        Some(url) => {\n            log::warn!(\n                \"Ignoring cross-origin resource_metadata URL {} \\\n                 (server origin: {})\",\n                url,\n                server_url.origin().unicode_serialization()\n            );\n            protected_resource_metadata_urls(server_url)\n        }\n        None => protected_resource_metadata_urls(server_url),\n    };\n\n    for url in &candidate_urls {\n        match fetch_json::<ProtectedResourceMetadataResponse>(http_client, url).await {\n            Ok(response) => {\n                if response.authorization_servers.is_empty() {\n                    bail!(\n                        \"Protected Resource Metadata at {} has no authorization_servers\",\n                        url\n                    );\n                }\n                return Ok(ProtectedResourceMetadata {\n                    resource: response.resource.unwrap_or_else(|| server_url.clone()),\n                    authorization_servers: response.authorization_servers,\n                    scopes_supported: response.scopes_supported,\n                });\n            }\n            Err(err) => {\n                log::debug!(\n                    \"Failed to fetch Protected Resource Metadata from {}: {}\",\n                    url,\n                    err\n                );\n            }\n        }","sourceCodeStart":758,"sourceCodeEnd":794,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/context_server/src/oauth.rs#L758-L794","documentation":"During OAuth discovery, Zed successfully fetched a Protected Resource Metadata document (RFC 9724) but its authorization_servers array is empty. That array is the only way discovery learns where to continue, so an otherwise valid JSON document with zero entries is treated as a hard error rather than silently trying nothing. The bail happens inside the loop over candidate URLs on the first fetch that succeeds.","triggerScenarios":"fetch_protected_resource_metadata() gets a 2xx JSON response whose authorization_servers is [] (or missing and defaulted to empty). Typical when a hand-written .well-known/oauth-protected-resource file was deployed with the field omitted or set to an empty list.","commonSituations":"First-time setup of a Protected Resource Metadata file where the author forgot to list their authorization server; tooling that generates the document with a placeholder empty array; a test fixture copied from a partial example; accidentally pointing authorization_servers at the resource's own URL with a typo that made deserialization drop it.","solutions":["Set authorization_servers to a non-empty array containing the issuer URL of your OAuth authorization server, e.g. [\"https://auth.example.com\"]","Validate the deployed document with curl https://server/.well-known/oauth-protected-resource and a JSON schema check for RFC 9724 fields","If you intended the resource to be its own authorization server, list its own issuer URL in the array rather than leaving it empty"],"exampleFix":"// before\n{ \"resource\": \"https://mcp.example.com\",\n  \"authorization_servers\": [] }\n\n// after\n{ \"resource\": \"https://mcp.example.com\",\n  \"authorization_servers\": [\"https://auth.example.com\"] }","handlingStrategy":"validation","validationCode":"// client-side: verify the document before feeding discovery\nasync fn check_prm_document(http: &Arc<dyn HttpClient>, url: &Url) -> Result<bool> {\n    let doc: serde_json::Value = fetch_json(http, url).await?;\n    Ok(doc.get(\"authorization_servers\")\n        .and_then(|v| v.as_array())\n        .is_some_and(|a| !a.is_empty()))\n}","typeGuard":"fn prm_has_auth_servers(doc: &serde_json::Value) -> bool {\n    doc.get(\"authorization_servers\")\n        .and_then(|v| v.as_array())\n        .is_some_and(|a| !a.is_empty())\n}","tryCatchPattern":"match fetch_protected_resource_metadata(&client, &server_url, &challenge).await {\n    Err(err) if err.to_string().contains(\"no authorization_servers\") => {\n        // fetched fine but empty — fix the served document; retrying will not help until it changes\n        report_server_config_issue(&server_url, \"authorization_servers is empty\");\n        Err(err)\n    }\n    other => other,\n}","preventionTips":["Add a deploy-time JSON assertion that authorization_servers is a non-empty array of https URLs","Smoke-test the whole discovery chain (401 -> PRM -> AS metadata) in CI for your MCP server","Include the resource and scopes_supported fields while you are at it to catch adjacent metadata mistakes"],"tags":["oauth","mcp","metadata","rfc-9724","discovery"],"backgroundTag":"oauth-server-metadata-invalid","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}