{"record":{"id":"1f1076f008a6904b","repo":"different-ai/openwork","slug":"mcp-oauth-configuration-required","errorCode":"MCP_OAUTH_CONFIGURATION_REQUIRED","errorMessage":"The OAuth authorization response cannot be validated without a bound authorization-server issuer.","messagePattern":"The OAuth authorization response cannot be validated without a bound authorization-server issuer\\.","errorType":"error_code","errorClass":"EnterpriseMcpOAuthContractError","httpStatus":null,"severity":"error","filePath":"packages/enterprise-mcp-client/src/authorization-response.ts","lineNumber":52,"sourceCode":"  expectedIssuer?: string | null\n  discoveryState?: unknown\n  /** Undefined means the response omitted `iss`; an empty value is present and invalid. */\n  responseIssuer?: string\n  mixUpDefense?: McpAuthorizationResponseMixUpDefense\n}): McpAuthorizationResponseIssuerValidation {\n  const discovery = isRecord(input.discoveryState) ? input.discoveryState : undefined\n  const authorizationServerMetadata = isRecord(discovery?.authorizationServerMetadata)\n    ? discovery.authorizationServerMetadata\n    : undefined\n  const resourceMetadata = isRecord(discovery?.resourceMetadata)\n    ? discovery.resourceMetadata\n    : undefined\n  const metadataIssuer = optionalString(authorizationServerMetadata?.issuer)\n  const discoveryIssuer = optionalString(discovery?.authorizationServerUrl)\n  const expectedIssuer = input.expectedIssuer ?? metadataIssuer ?? discoveryIssuer\n\n  if (!expectedIssuer) {\n    throw new EnterpriseMcpOAuthContractError(\n      \"MCP_OAUTH_CONFIGURATION_REQUIRED\",\n      \"The OAuth authorization response cannot be validated without a bound authorization-server issuer.\",\n    )\n  }\n\n  const advertisedIssuers = Array.isArray(resourceMetadata?.authorization_servers)\n    ? resourceMetadata.authorization_servers.filter((value): value is string => typeof value === \"string\")\n    : undefined\n  const discoveryIsBound = discoveryIssuer\n    ? isAuthorizationServerDiscoveryBound({\n        authorizationServerUrl: discoveryIssuer,\n        authorizationServerMetadata: metadataIssuer ? { issuer: metadataIssuer } : undefined,\n        resourceMetadata: resourceMetadata\n          ? {\n              resource: optionalString(resourceMetadata.resource),\n              authorization_servers: advertisedIssuers,\n            }\n          : undefined,","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/enterprise-mcp-client/src/authorization-response.ts#L34-L70","documentation":"validateMcpAuthorizationResponseIssuer needs an expected authorization-server issuer to bind the OAuth authorization response against. The expected issuer can come from input.expectedIssuer, the authorization-server metadata's issuer field, or stored discovery's authorizationServerUrl; if none is available, the contract cannot be enforced and MCP_OAUTH_CONFIGURATION_REQUIRED is thrown. This is a configuration gap, not a mismatch.","triggerScenarios":"Calling validateMcpAuthorizationResponseIssuer (during OAuth response validation) when input.expectedIssuer is undefined, authorizationServerMetadata is absent or lacks issuer, and discovery has no authorizationServerUrl.","commonSituations":"Skipping server metadata discovery before processing the callback; a stored MCP connection missing its discovery record (e.g. after DB migration or partial save); constructing validation input manually without passing expectedIssuer; metadata endpoints that return a payload without an issuer field.","solutions":["Run authorization-server metadata discovery first and pass its issuer as input.expectedIssuer","Ensure the stored MCP connection has a discovery record containing authorizationServerUrl before validating responses","Pass expectedIssuer explicitly when metadata/discovery are not available","Fix the metadata endpoint or its caching so issuer is present in authorizationServerMetadata"],"exampleFix":"// before\nvalidateMcpAuthorizationResponseIssuer({ response, resourceMetadata }) // no issuer source\n// after\nconst metadata = await discoverAuthorizationServerMetadata(issuerUrl)\nvalidateMcpAuthorizationResponseIssuer({ response, resourceMetadata, expectedIssuer: metadata.issuer })","handlingStrategy":"validation","validationCode":"function assertIssuerSource(input) {\n  const has = input.expectedIssuer\n    || (input.authorizationServerMetadata && input.authorizationServerMetadata.issuer)\n    || (input.discovery && input.discovery.authorizationServerUrl);\n  if (!has) throw new Error('Provide expectedIssuer, metadata.issuer, or discovery.authorizationServerUrl before validating the authorization response');\n}","typeGuard":"function hasExpectedIssuer(input: ValidationInput): input is ValidationInput & { expectedIssuer: string } {\n  return typeof input.expectedIssuer === 'string' && input.expectedIssuer.length > 0;\n}","tryCatchPattern":"try {\n  validateMcpAuthorizationResponseIssuer(input)\n} catch (e) {\n  if (e instanceof EnterpriseMcpOAuthContractError && e.code === 'MCP_OAUTH_CONFIGURATION_REQUIRED') {\n    // run metadata discovery and retry with expectedIssuer\n  } else throw e\n}","preventionTips":["Always run AS metadata discovery before processing OAuth callbacks","Persist discovery (with authorizationServerUrl) alongside the MCP connection record","Pass expectedIssuer explicitly when constructing validation input","Validate metadata payloads include an issuer field"],"tags":["oauth","mcp","configuration","issuer"],"backgroundTag":"missing-oauth-issuer-configuration","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}