{"record":{"id":"41d80016e0f3cfb9","repo":"vercel/ai","slug":"stored-oauth-authorization-server-metadata-is-requ","errorCode":null,"errorMessage":"Stored OAuth authorization server metadata is required when exchanging an authorization code","messagePattern":"Stored OAuth authorization server metadata is required when exchanging an authorization code","errorType":"exception","errorClass":"MCPClientOAuthError","httpStatus":null,"severity":"error","filePath":"packages/mcp/src/tool/oauth.ts","lineNumber":1362,"sourceCode":"\n  /** On callback, validate state and AS pin before code exchange */\n  if (authorizationCode !== undefined) {\n    if (provider.storedState) {\n      const expectedState = await provider.storedState();\n      if (expectedState !== undefined && expectedState !== callbackState) {\n        throw new Error(\n          'OAuth state parameter mismatch - possible CSRF attack',\n        );\n      }\n    }\n\n    const storedAuthorizationServerInformation =\n      await getStoredAuthorizationServerInformation({\n        provider,\n        clientInformation,\n      });\n    if (!storedAuthorizationServerInformation) {\n      throw new MCPClientOAuthError({\n        message:\n          'Stored OAuth authorization server metadata is required when exchanging an authorization code',\n      });\n    }\n    validateAuthorizationResponseIssuer({\n      callbackIssuer,\n      expectedIssuer:\n        storedAuthorizationServerInformation.issuer ??\n        metadata?.issuer ??\n        String(authorizationServerUrl),\n    });\n    assertAuthorizationServerInformationMatches({\n      storedAuthorizationServerInformation,\n      currentAuthorizationServerInformation,\n    });\n\n    const codeVerifier = await provider.codeVerifier();\n    const tokens = await exchangeAuthorization(authorizationServerUrl, {","sourceCodeStart":1344,"sourceCodeEnd":1380,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/mcp/src/tool/oauth.ts#L1344-L1380","documentation":"During the authorization-code callback, the client must pin the authorization server it originally talked to. It reads the stored authorization server metadata (via getStoredAuthorizationServerInformation, typically attached to the saved client information). If none is stored, the issuer of the callback cannot be verified and the code exchange is refused with an MCPClientOAuthError.","triggerScenarios":"auth() is called with an authorizationCode while getStoredAuthorizationServerInformation({provider, clientInformation}) returns undefined — e.g. client information was saved by an older SDK version or a custom saveClientInformation that strips the pinned AS metadata, or registration happened outside this library.","commonSituations":"Upgrading from a version of @ai-sdk/mcp that did not pin authorization-server metadata onto client information; hand-rolled client registration storing raw RFC 7591 responses; wiping stored credentials between the redirect and the callback.","solutions":["Clear the stored client information and restart the OAuth flow so the library re-registers the client and saves metadata-pinned client information.","If you implement saveClientInformation yourself, persist exactly the object given (including any authorizationServerInformation fields), not a subset.","Upgrade @ai-sdk/mcp and any custom provider code together so the same client-information shape is written and read."],"exampleFix":"// before\nsaveClientInformation: async (info) => {\n  await store.set('client', { client_id: info.client_id });\n}\n// after\nsaveClientInformation: async (info) => {\n  await store.set('client', info); // keep full object incl. pinned AS metadata\n}","handlingStrategy":"validation","validationCode":"const clientInfo = await provider.clientInformation?.();\nif (clientCodeExchangeRequested && clientInfo && !('authorizationServerInformation' in (clientInfo as object))) {\n  // stored client predates AS pinning: clear it and restart the flow\n  await provider.clearClientInformation?.();\n}","typeGuard":null,"tryCatchPattern":"try {\n  await auth(serverUrl, { ...provider, authorizationCode });\n} catch (e) {\n  if (e instanceof MCPClientOAuthError && e.message.includes('Stored OAuth authorization server metadata is required')) {\n    // wipe stale client info and restart authorization from scratch\n    await provider.clearClientInformation?.();\n  }\n  throw e;\n}","preventionTips":["Persist client information objects verbatim (never strip fields)","Keep @ai-sdk/mcp versions consistent between the flow start and callback","Clear old-format credentials when upgrading the SDK","Test the full redirect round-trip against durable storage"],"tags":["oauth","mcp","issuer-validation","state-persistence"],"backgroundTag":"oauth-issuer-pin-missing","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}