different-ai/openwork · error · EnterpriseMcpOAuthContractError
MCP_OAUTH_AUTHORIZATION_ID_REQUIRED
MCP_OAUTH_AUTHORIZATION_ID_REQUIRED
Error message
A signed authorization transaction id is required before starting OAuth.
What it means
state() is the OAuthClientProvider hook that supplies the OAuth state parameter. It requires the current flow to be a "connect" flow carrying a signed authorizationId; in callback or runtime flows (or a connect flow missing the id) it throws EnterpriseMcpOAuthContractError with code MCP_OAUTH_AUTHORIZATION_ID_REQUIRED. The signed id is what binds the browser redirect to a durable authorization transaction; without it the library will not start OAuth.
Source
Thrown at packages/enterprise-mcp-client/src/oauth-provider.ts:140
throw new EnterpriseMcpOAuthContractError(
"MCP_LIFECYCLE_DEADLINE",
"The enterprise MCP lifecycle expired before OAuth persistence could continue.",
)
}
return {
connectionId: this.connectionId,
commitExpiresAt: this.lifecycle.expiresAt,
signal: this.lifecycle.signal,
}
}
get redirectUrl(): string {
return this.redirectUri
}
state(): string {
if (this.flow.kind !== "connect" || !this.flow.authorizationId) {
throw new EnterpriseMcpOAuthContractError(
"MCP_OAUTH_AUTHORIZATION_ID_REQUIRED",
"A signed authorization transaction id is required before starting OAuth.",
)
}
return this.flow.authorizationId
}
get clientMetadata() {
const scope = this.requestedScopes.join(" ")
return {
redirect_uris: [this.redirectUri],
client_name: this.clientName,
grant_types: ["authorization_code", "refresh_token"],
response_types: ["code"],
token_endpoint_auth_method: "none",
application_type: this.applicationType,
...(scope ? { scope } : {}),
}View on GitHub (pinned to 2b7df46e8a)
Solutions
- Start OAuth only via client.connect() with a signed authorizationId — do not rely on runtime operations to initiate OAuth.
- Handle connect()'s { status: "needs_auth" } result to run the browser redirect with the properly signed state.
- Ensure the authorizationId is passed and non-empty when constructing a connect flow provider.
- If a 401 arrives during runtime calls, invalidate credentials and route the user back through connect() rather than re-authenticating in place.
Example fix
// before (initiating OAuth from a runtime session)
await auth(runtimeSession.oauthProvider, { serverUrl })
// after
const result = await client.connect({ connection, redirectUri, authorizationId: signedId })
if (result.status === "needs_auth") redirect(result.authorizeUrl) Defensive patterns
Strategy: validation
Validate before calling
function canStartOAuth(authorizationId) {
return typeof authorizationId === "string" && authorizationId.length > 0;
}
// require this before initiating any OAuth redirect Type guard
function isConnectFlowWithAuthorizationId(flow: { kind: string; authorizationId?: string }): flow is { kind: "connect"; authorizationId: string } {
return flow.kind === "connect" && typeof flow.authorizationId === "string" && flow.authorizationId.length > 0;
} Try / catch
try {
await client.callTool(input);
} catch (e) {
if (e.code === "MCP_OAUTH_AUTHORIZATION_ID_REQUIRED") {
// a 401 started OAuth outside a signed connect flow — reroute via connect() with a signed id
}
throw e;
} Prevention
- Always initiate OAuth through client.connect() with a signed authorizationId.
- Handle { status: "needs_auth" } from connect() to run the redirect instead of letting runtime calls trigger auth.
- Construct providers with the correct flow kind for their use (connect vs runtime).
- On runtime 401s, invalidate credentials and restart via connect() rather than in-place re-auth.
When it happens
Trigger: The MCP SDK's auth() helper reads provider.state() while the provider was constructed with flow { kind: "runtime" } or { kind: "callback" }, or with a connect flow whose authorizationId is undefined — e.g. reusing a runtime session's provider to initiate a new OAuth redirect.
Common situations: Calling runtime operations (listTools/callTool) against a server that answers 401 and triggers an inline OAuth start without a signed id; calling connect() without authorizationId (see the connect-time guard) via a path that skips it; constructing the provider manually with the wrong flow kind.
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/96432b3b8996cd86.
Report an issue: GitHub.