mastra-ai/mastra · error · Error

No OAuth authorization is pending for this server. Call conn

Error message

No OAuth authorization is pending for this server. Call connect() first.

What it means

finishAuth(authorizationCode) completes a pending OAuth token exchange started when connect() hit a 401 and deferred to an authorization flow. If no such pending authorization exists (pendingAuthTransport is undefined), this error is thrown because there is no transport waiting for the authorization code.

Source

Thrown at packages/mcp/src/client/client.ts:791

  get authState(): MCPServerAuthState | undefined {
    return this._authState;
  }

  /**
   * Completes a pending OAuth authorization-code flow.
   *
   * Exchanges the authorization code captured at the redirect URI on the same
   * transport that started the flow, then leaves the client ready to connect().
   *
   * @param authorizationCode - The authorization code captured at the redirect URI
   * @throws {Error} If no authorization flow is pending for this server
   *
   * @internal
   */
  async finishAuth(authorizationCode: string): Promise<void> {
    const pending = this.pendingAuthTransport;
    if (!pending) {
      throw new Error('No OAuth authorization is pending for this server. Call connect() first.');
    }
    this.pendingAuthTransport = undefined;
    try {
      await pending.finishAuth(authorizationCode);
    } finally {
      // The pending transport only ran the token exchange; the next connect() builds a fresh one.
      void pending.close().catch(() => {});
    }
  }

  private isConnected: Promise<boolean> | null = null;
  private reconnectPromise: Promise<void> | null = null;
  private lifecycleGeneration = 0;

  /**
   * Connects to the MCP server using the configured transport.
   *
   * Automatically detects transport type based on configuration (stdio vs HTTP).

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Only call finishAuth after connect() rejected with an authorization requirement
  2. Track whether an OAuth flow is in progress; gate the callback on that flag
  3. Make the OAuth callback idempotent (ignore repeat calls)
  4. If state was lost, call connect() again to restart the authorization flow

Example fix

// before
await client.finishAuth(req.query.code); // throws when nothing pending
// after
try {
  await client.finishAuth(req.query.code);
} catch (e) {
  if (!(e instanceof Error) || !e.message.includes('No OAuth authorization is pending')) throw e;
  await client.connect(); // restart or ignore duplicate callback
}
Defensive patterns

Strategy: try-catch

Validate before calling

// only call finishAuth when connect() surfaced an authorization requirement
let authPending = false;
try { await client.connect(); } catch { authPending = true; }
if (authPending) await client.finishAuth(code);

Try / catch

try {
  await client.finishAuth(code);
} catch (e) {
  if (e instanceof Error && e.message.includes('No OAuth authorization is pending')) {
    // duplicate callback or no flow in progress: ignore or restart connect()
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling client.finishAuth(code) without a preceding connect() that triggered the OAuth flow; calling finishAuth twice; calling it after connect() already completed the exchange; calling it after a disconnect cleared the pending transport.

Common situations: OAuth callback invoked more than once (double browser redirect); application flow calls finishAuth unconditionally instead of only after a 401-driven redirect; server credentials already valid so connect() never triggered OAuth.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/73d68529acb28876. Report an issue: GitHub.