{"record":{"id":"6d95b082e8283b71","repo":"vercel/ai","slug":"oauth-authorization-server-metadata-must-be-saveab","errorCode":null,"errorMessage":"OAuth authorization server metadata must be saveable before starting authorization","messagePattern":"OAuth authorization server metadata must be saveable before starting authorization","errorType":"exception","errorClass":"MCPClientOAuthError","httpStatus":null,"severity":"error","filePath":"packages/mcp/src/tool/oauth.ts","lineNumber":1480,"sourceCode":"    authorizationServerUrl,\n    {\n      metadata,\n      clientInformation,\n      state,\n      redirectUrl: provider.redirectUrl,\n      scope: selectedScope,\n      resource,\n    },\n  );\n\n  const savedAuthorizationServerInformation =\n    await saveAuthorizationServerInformation({\n      provider,\n      clientInformation,\n      authorizationServerInformation: currentAuthorizationServerInformation,\n    });\n  if (!savedAuthorizationServerInformation) {\n    throw new MCPClientOAuthError({\n      message:\n        'OAuth authorization server metadata must be saveable before starting authorization',\n    });\n  }\n\n  await provider.saveCodeVerifier(codeVerifier);\n  await provider.redirectToAuthorization(authorizationUrl);\n  return 'REDIRECT';\n}\n","sourceCodeStart":1462,"sourceCodeEnd":1490,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/mcp/src/tool/oauth.ts#L1462-L1490","documentation":"Before redirecting the user to the authorization server, the client saves the current authorization server metadata (issuer pin) via saveAuthorizationServerInformation. If the provider's storage callback reports the metadata was not actually saved, the callback phase later cannot verify the issuer, so authorization is refused before it starts.","triggerScenarios":"Starting a new authorization (no authorizationCode): saveAuthorizationServerInformation({...}) was invoked, but the subsequent read (savedAuthorizationServerInformation) shows nothing was persisted — i.e. the provider's save/storage functions silently drop or fail to persist the authorization server metadata.","commonSituations":"Custom OAuth providers whose storage setter is a no-op or writes to a read-only location; storage implementations that serialize only whitelisted fields; ephemeral environments (serverless) where in-memory storage vanishes between the start and callback of the flow.","solutions":["Make the provider's authorization-server metadata storage actually persist (file, database, signed cookie) and return the saved value.","Verify the storage backend serializes the full object (don't strip nested authorizationServerInformation fields).","In serverless environments, keep the flow within one request/lambda invocation or use external durable storage for OAuth state."],"exampleFix":"// before\nsaveAuthorizationServerInformation: async () => { /* no-op */ }\n// after\nsaveAuthorizationServerInformation: async (info) => {\n  await kv.set('as-metadata', info);\n  return info;\n}","handlingStrategy":"validation","validationCode":"await provider.saveAuthorizationServerInformation?.(asInfo);\nconst readBack = await provider.getAuthorizationServerInformation?.();\nif (readBack == null) {\n  throw new Error('OAuth storage backend is not persisting authorization server metadata');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify your storage setter round-trips (write then read back) at startup","Use durable storage (file, DB, signed cookie), not in-memory state, in serverless environments","Serialize the full metadata object without field whitelisting","Make storage callbacks throw on write failures instead of silently no-oping"],"tags":["oauth","mcp","persistence","configuration"],"backgroundTag":"oauth-metadata-persistence","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}