vercel/ai · error

OAuth client information must be saveable for dynamic regist

Error message

OAuth client information must be saveable for dynamic registration

What it means

During the OAuth authorization-code flow, the MCP client found no stored client registration and needs to dynamically register a new client with the authorization server. Dynamic registration only works if the provider supplies a saveClientInformation callback so the freshly registered client id/secret can be persisted for the subsequent token exchange. Without it, the flow cannot continue safely, so auth() throws.

Source

Thrown at packages/mcp/src/tool/oauth.ts:1324

        clientInformation,
      });
    if (storedAuthorizationServerInformation) {
      assertAuthorizationServerInformationMatches({
        storedAuthorizationServerInformation,
        currentAuthorizationServerInformation,
      });
    }
  }

  if (!clientInformation) {
    if (authorizationCode !== undefined) {
      throw new Error(
        'Existing OAuth client information is required when exchanging an authorization code',
      );
    }

    if (!provider.saveClientInformation) {
      throw new Error(
        'OAuth client information must be saveable for dynamic registration',
      );
    }

    const fullInformation = await registerClient(authorizationServerUrl, {
      metadata,
      clientMetadata: {
        ...clientMetadata,
        scope: selectedScope,
      },
      fetchFn,
    });

    clientInformation = addAuthorizationServerInformationToClientInformation(
      fullInformation,
      currentAuthorizationServerInformation,
    );
    await provider.saveClientInformation(clientInformation);

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Implement saveClientInformation(clientInformation) on your OAuth provider object (e.g. persist to a config file, keychain, or database) so dynamic client registration can be saved.
  2. Pre-register a client with the authorization server and return it from provider.clientInformation() so the dynamic-registration path is never taken.
  3. If using a static client secret, configure provider.clientSecret / clientInformation so clientInformation() returns a value before authorization starts.

Example fix

// before
const provider = {
  redirectUrl: 'http://localhost:3333/callback',
  clientInformation: async () => undefined,
};
// after
const provider = {
  redirectUrl: 'http://localhost:3333/callback',
  clientInformation: async () => loadClientInfo(),
  saveClientInformation: async (info) => {
    await fs.writeFile('client-info.json', JSON.stringify(info));
  },
};
Defensive patterns

Strategy: validation

Validate before calling

if (!provider.clientInformation && typeof provider.clientInformation === 'function') {
  const existing = await provider.clientInformation();
  if (!existing && typeof provider.saveClientInformation !== 'function') {
    throw new Error('OAuth provider must implement saveClientInformation for dynamic registration');
  }
}

Type guard

function hasSaveClientInformation(p: object): p is { saveClientInformation: (info: unknown) => Promise<void> } {
  return typeof (p as any).saveClientInformation === 'function';
}

Try / catch

try {
  await auth(serverUrl, { provider });
} catch (e) {
  if (e instanceof Error && e.message.includes('must be saveable for dynamic registration')) {
    // persist a client registration or supply provider.saveClientInformation
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the MCP OAuth helper (auth() in packages/mcp/src/tool/oauth.ts) when: (1) provider.clientInformation() returns undefined (no previously registered client), and (2) the call carries an authorizationCode === undefined (start of the flow), and (3) provider.saveClientInformation is not implemented in the custom OAuthProvider passed in.

Common situations: Developers implementing a minimal custom OAuth provider for an MCP server forget to implement saveClientInformation, or pass an in-memory provider whose storage was cleared between runs, or use a provider built for a pre-registered/static client while the server actually requires dynamic registration.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/405cd90f7c64f592. Report an issue: GitHub.