different-ai/openwork · critical · EnterpriseMcpClientError
MCP_AUTHORIZATION_CALLBACK_FAILED
MCP_AUTHORIZATION_CALLBACK_FAILED
Error message
Enterprise MCP failed during ${phaseLabel[input.operationPhase]}${request}. What it means
In completeAuthorization(), the authorization code was exchanged (tokens saved) but the subsequent validation (protocol handshake / tools/list) failed, AND the cleanup attempt to invalidate the just-exchanged credentials via credentialPort.invalidate() also failed. The client wraps both failures in an AggregateError inside an EnterpriseMcpClientError with operationPhase "authorization-callback" and code MCP_AUTHORIZATION_CALLBACK_FAILED. This is serious: possibly-orphaned OAuth credentials remain persisted and unvalidated.
Source
Thrown at packages/enterprise-mcp-client/src/enterprise-mcp-client.ts:572
operationFailed = true
let credentialCleanupError: unknown = null
if (exchangedTokens && credentialPort) {
try {
const cleanupController = new AbortController()
await credentialPort.invalidate({
context: {
connectionId: input.connection.id,
commitExpiresAt: clock.now() + closeTimeoutMs,
signal: cleanupController.signal,
},
reason: "post-authorization-validation-failed",
})
} catch (cleanupError) {
credentialCleanupError = cleanupError
}
}
if (credentialCleanupError) {
throw new EnterpriseMcpClientError({
operationPhase: "authorization-callback",
requestPhase: session.observer.lastRequestPhase(),
cause: new AggregateError(
[error, credentialCleanupError],
"Post-authorization validation failed and the exchanged credentials could not be invalidated.",
),
})
}
throw error
} finally {
try {
await closeWithinDeadline(() => session.client.close(), closeTimeoutMs)
} catch (error) {
if (!operationFailed) {
throw new EnterpriseMcpClientError({
operationPhase: "shutdown",
requestPhase: session.observer.lastRequestPhase(),
cause: error,View on GitHub (pinned to 2b7df46e8a)
Solutions
- Inspect the AggregateError's two causes: the primary validation failure and the cleanup failure; fix each accordingly.
- Manually invalidate the exchanged credentials for this connectionId in your persistence store to avoid orphaned tokens.
- Check the persistence adapter's health and its commit deadline handling; increase the lifecycle window if it expired mid-callback.
- Retry completeAuthorization after cleanup is possible; the exchange may need to be redone from a fresh authorization code.
Defensive patterns
Strategy: try-catch
Validate before calling
async function assertPersistenceHealthy(credentialsPort) {
// cheap probe that the credential store accepts writes before exchanging the code
await credentialsPort.ping?.();
} Type guard
function isAggregateCleanupError(e: unknown): e is AggregateError {
return e instanceof AggregateError && e.message.includes("could not be invalidated");
} Try / catch
try {
await client.completeAuthorization(input);
} catch (e) {
if (e.code === "MCP_AUTHORIZATION_CALLBACK_FAILED") {
const [validationError, cleanupError] = e.cause?.errors ?? [];
// 1) manually invalidate credentials for connectionId, 2) restart OAuth from a fresh code
}
throw e;
} Prevention
- Monitor the persistence backend's health before running OAuth callbacks.
- Keep the lifecycle deadline long enough to cover exchange + validation + cleanup.
- Reconcile orphaned credentials periodically (tokens persisted without a validated session).
- Alert on MCP_AUTHORIZATION_CALLBACK_FAILED — it implies two simultaneous failures.
When it happens
Trigger: finishAuth(code) succeeds, then connectWithProtocolNegotiation or listTools throws (e.g. server 401/5xx/network drop), then persistence.credentials.invalidate({ reason: "post-authorization-validation-failed" }) also throws (persistence backend down, commit deadline expired, signal aborted).
Common situations: Persistence adapter (database) outage coinciding with a failing MCP server; lifecycle deadline expiring mid-callback; the OAuth adapter rejecting the invalidation because the transaction already committed or the signal was aborted.
Related errors
- `${t("providers.no_oauth_prefix")} ${resolved}. ${t("provide
- `${t("providers.not_oauth_flow_prefix")} ${resolved}.`
- t("providers.oauth_method_required")
- OpenWork-managed MCP OAuth is currently available for local
- OpenWork-managed OAuth requires a remote MCP URL.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/f2008e1a73c0b01a.
Report an issue: GitHub.