{"record":{"id":"40d580a18e6617db","repo":"different-ai/openwork","slug":"auth-issuer-discovery","errorCode":"AUTH_ISSUER_DISCOVERY","errorMessage":"Protected-resource metadata had no authorization server","messagePattern":"Protected-resource metadata had no authorization server","errorType":"exception","errorClass":"ProbeFailure","httpStatus":null,"severity":"error","filePath":"packages/enterprise-mcp-mock-server/src/testing/probe.ts","lineNumber":554,"sourceCode":"      await fetchStep(resourceMetadataUrl, undefined, \"AUTH_RESOURCE_DISCOVERY\", overallDeadline),\n      \"AUTH_RESOURCE_DISCOVERY\",\n    )\n    const resourceMetadata = parseAt(\n      protectedResourceMetadataSchema,\n      await parseJson(resourceResponse, \"AUTH_RESOURCE_DISCOVERY\", \"oauth_discovery_resource\"),\n      \"AUTH_RESOURCE_DISCOVERY\",\n      \"oauth_discovery_resource\",\n      \"Protected-resource metadata did not match the required shape\",\n    )\n    if (resourceMetadata.resource !== mcpUrl) {\n      throw new ProbeFailure(\"AUTH_RESOURCE_DISCOVERY\", \"oauth_discovery_resource\", \"Protected-resource metadata did not identify this MCP endpoint\")\n    }\n    recordPassed(phases, \"AUTH_RESOURCE_DISCOVERY\", startedAt, \"Protected-resource metadata is coherent\")\n\n    startedAt = Date.now()\n    const authorizationServerValue = resourceMetadata.authorization_servers[0]\n    if (!authorizationServerValue) {\n      throw new ProbeFailure(\"AUTH_ISSUER_DISCOVERY\", \"oauth_discovery_issuer\", \"Protected-resource metadata had no authorization server\")\n    }\n    const issuerMetadataResponse = await expectOk(\n      await fetchStep(\n        new URL(\"/.well-known/oauth-authorization-server\", assertPinnedOrigin(authorizationServerValue, baseUrl, \"AUTH_ISSUER_DISCOVERY\")),\n        undefined,\n        \"AUTH_ISSUER_DISCOVERY\",\n        overallDeadline,\n      ),\n      \"AUTH_ISSUER_DISCOVERY\",\n    )\n    const issuerMetadata = parseAt(\n      authorizationServerMetadataSchema,\n      await parseJson(issuerMetadataResponse, \"AUTH_ISSUER_DISCOVERY\", \"oauth_discovery_issuer\"),\n      \"AUTH_ISSUER_DISCOVERY\",\n      \"oauth_discovery_issuer\",\n      \"Authorization-server metadata did not match the required shape\",\n    )\n    if (issuerMetadata.issuer !== baseUrl.href.replace(/\\/$/, \"\")) {","sourceCodeStart":536,"sourceCodeEnd":572,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/enterprise-mcp-mock-server/src/testing/probe.ts#L536-L572","documentation":"The protected-resource metadata must list at least one authorization_servers entry; the probe takes the first one to discover the issuer's OAuth metadata. An empty or missing authorization_servers array leaves the client with no authorization server to authenticate against, so the probe fails in AUTH_ISSUER_DISCOVERY with subcode oauth_discovery_issuer.","triggerScenarios":"The parsed metadata document has no authorization_servers key or an empty array (resourceMetadata.authorization_servers[0] is undefined).","commonSituations":"Hand-written metadata fixture omitting the field, a server emitting authorization_servers: [] when its OAuth deployment is not wired up, or a shape-validation pass that allows an empty array.","solutions":["Add \"authorization_servers\": [\"<issuer-url>\"] to the protected-resource metadata document.","Wire the MCP server's OAuth configuration so it knows and advertises its authorization server.","If generating metadata dynamically, guard against emitting an empty array when the AS config is missing."],"exampleFix":"// before\n{ \"resource\": mcpUrl }\n// after\n{ \"resource\": mcpUrl, \"authorization_servers\": [\"https://localhost:3000/\"] }","handlingStrategy":"validation","validationCode":"const md = await (await fetch(metadataUrl)).json()\nif (!Array.isArray(md.authorization_servers) || md.authorization_servers.length === 0) {\n  throw new Error(\"authorization_servers must list at least one issuer URL\")\n}","typeGuard":"function hasAuthorizationServers(md: unknown): md is { authorization_servers: [string, ...string[]] } & Record<string, unknown> {\n  return typeof md === \"object\" && md !== null &&\n    Array.isArray((md as { authorization_servers?: unknown }).authorization_servers) &&\n    (md as { authorization_servers: unknown[] }).authorization_servers.length > 0\n}","tryCatchPattern":"try {\n  await probeEnterpriseMcpMockServer({ baseUrl })\n} catch (e) {\n  if (e instanceof ProbeFailure && e.phase === \"AUTH_ISSUER_DISCOVERY\" && e.message.includes(\"no authorization server\")) {\n    console.error(\"Add authorization_servers to the protected-resource metadata\")\n  } else throw e\n}","preventionTips":["Always include authorization_servers in protected-resource metadata fixtures","Fail fast at server startup if OAuth AS config is missing rather than emitting empty arrays","Validate metadata documents against the RFC 9728 schema in tests"],"tags":["oauth","discovery","auth","metadata"],"backgroundTag":"oauth-metadata-discovery-mismatch","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}