different-ai/openwork · error · EnterpriseMcpOAuthContractError

MCP_OAUTH_PERSISTENCE_INVALID

MCP_OAUTH_PERSISTENCE_INVALID

Error message

The OAuth persistence adapter returned an invalid ${field}.

What it means

assertFiniteEpoch() rejected a numeric epoch value produced by the OAuth persistence layer because it was not a finite, non-negative number. The error (EnterpriseMcpOAuthContractError, code MCP_OAUTH_PERSISTENCE_INVALID) names the offending field via the message (e.g. "client expiration" or "token expiration"). The library treats NaN/Infinity/negative expiration epochs as a contract violation by the persistence adapter, since such values cannot be compared against clock.now().

Source

Thrown at packages/enterprise-mcp-client/src/oauth-provider.ts:42

import { isAuthorizationServerDiscoveryBound } from "./oauth-discovery-binding.js"

type OAuthFlowContext =
  | { kind: "connect"; authorizationId?: string }
  | { kind: "callback"; authorizationId: string }
  | { kind: "runtime" }

type VerifiedOAuthDiscoveryState = OAuthDiscoveryState & {
  openworkMetadataVerification?: {
    version: 1
    issuer: string
  }
}

const oauthClientInformationMixedSchema = OAuthClientInformationFullSchema.or(OAuthClientInformationSchema)

function assertFiniteEpoch(value: number, field: string): number {
  if (!Number.isFinite(value) || value < 0) {
    throw new EnterpriseMcpOAuthContractError(
      "MCP_OAUTH_PERSISTENCE_INVALID",
      `The OAuth persistence adapter returned an invalid ${field}.`,
    )
  }
  return value
}

function clientExpiration(clientInformation: StoredOAuthClientInformation): number | undefined {
  const parsed = OAuthClientInformationFullSchema.safeParse(clientInformation)
  const seconds = parsed.success ? parsed.data.client_secret_expires_at : undefined
  if (seconds === undefined || seconds === 0) return undefined
  return assertFiniteEpoch(seconds * 1_000, "client expiration")
}

function tokenExpiration(tokens: StoredOAuthTokens, now: number): number | undefined {
  if (tokens.expires_in === undefined) return undefined
  if (!Number.isFinite(tokens.expires_in) || tokens.expires_in < 0) {
    throw new EnterpriseMcpOAuthContractError(

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Fix the persistence adapter to store expiresAt / client_secret_expires_at as finite epoch numbers (milliseconds for record.expiresAt, seconds for client_secret_expires_at).
  2. Use undefined (not -1/0-sentinels or NaN) when there is no expiration.
  3. Validate/sanitize the numeric values when writing records, not just when reading.
  4. Inspect the stored record for the named field to confirm it is a finite number ≥ 0.

Example fix

// before (persistence adapter)
expiresAt: row.expires_at === null ? -1 : Date.parse(row.expires_at)
// after
expiresAt: row.expires_at === null ? undefined : Date.parse(row.expires_at)
Defensive patterns

Strategy: validation

Validate before calling

function isValidEpochMs(v: unknown): v is number {
  return typeof v === "number" && Number.isFinite(v) && v >= 0;
}
// run on expiresAt / client_secret_expires_at before persisting

Type guard

function isFiniteEpoch(v: unknown): v is number {
  return typeof v === "number" && Number.isFinite(v) && v >= 0;
}

Try / catch

try {
  await client.connect(input);
} catch (e) {
  if (e.code === "MCP_OAUTH_PERSISTENCE_INVALID") {
    // audit the stored OAuth records for the named field and clear the corrupt entry
  }
  throw e;
}

Prevention

When it happens

Trigger: clientExpiration() computes client_secret_expires_at * 1000 into a non-finite/negative value and passes it to assertFiniteEpoch; or the credentials record's expiresAt loaded from persistence is NaN/Infinity/negative (reached via tokenExpiration / clientInformation / tokens).

Common situations: Persistence adapter stores dates as strings or null and a numeric coercion yields NaN; clock skew or corrupted records producing negative epochs; client_secret_expires_at stored in ms instead of s (huge but finite — usually fine) or as a sentinel like -1 for "never".

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/024640a03cc15046. Report an issue: GitHub.