can1357/oh-my-pi · error · Error

OAuth authentication failed: ${errorMsg}

Error message

OAuth authentication failed: ${errorMsg}

What it means

This is the catch-all branch of the MCP OAuth login flow in MCPCommandController. When the underlying MCPOAuthFlow (dynamic client registration, authorization-code exchange, or token fetch) fails with an error that does not match the known timeout/403/invalid_grant/network patterns, the controller rethrows it wrapped as "OAuth authentication failed: <original message>" so the user still sees the root cause. It signals that the OAuth handshake failed for a reason the controller does not specifically classify.

Source

Thrown at packages/coding-agent/src/modes/controllers/mcp-command-controller.ts:1040

			// cancellations. The timeout path also aborts the controller but does
			// not set this flag, so it remains a surfaced error.
			if (cancellationRequested) {
				throw new MCPOAuthCancelledError();
			}

			const errorMsg = error instanceof Error ? error.message : String(error);

			// Provide helpful error messages based on failure type
			if (errorMsg.includes("timeout") || errorMsg.includes("timed out")) {
				throw new Error("OAuth flow timed out. Please try again.");
			} else if (errorMsg.includes("403") || errorMsg.includes("unauthorized")) {
				throw new Error("OAuth authorization failed. Please check your client credentials.");
			} else if (errorMsg.includes("invalid_grant")) {
				throw new Error("OAuth authorization code is invalid or expired. Please try again.");
			} else if (errorMsg.includes("ECONNREFUSED") || errorMsg.includes("fetch failed")) {
				throw new Error("Could not connect to OAuth server. Please check the URLs and your network connection.");
			} else {
				throw new Error(`OAuth authentication failed: ${errorMsg}`);
			}
		} finally {
			this.ctx.editor.onEscape = originalOnEscape;
			externalSignal?.removeEventListener("abort", onExternalAbort);
			manualInputClaim?.clear("Manual MCP OAuth input cleared");
			flowClaim.release();
		}
	}

	/**
	 * Fold a completed OAuth flow back into a server config. Owns the
	 * persistence policy in one place: the auth block records the credential
	 * pointer plus refresh material, the oauth block echoes the client id for
	 * pre-auth reuse, and only a user-supplied client secret is ever written —
	 * DCR-issued secrets stay embedded in the stored credential so they cannot
	 * leak into (possibly shared/committed) config files.
	 */
	#persistOAuthResult(

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the embedded original message after the colon — it names the actual OAuth failure (e.g. invalid_client, invalid_scope) and fix the client credentials/scopes in the server config accordingly.
  2. Verify the server's OAuth discovery metadata is reachable: curl <server-url>/.well-known/oauth-authorization-server and .well-known/oauth-protected-resource.
  3. Re-run the auth flow — transient token-endpoint errors (5xx) resolve on retry.
  4. If the provider requires a pre-registered client, add the correct clientId/clientSecret to the MCP server's auth block instead of relying on dynamic client registration.

Example fix

// before: relying on DCR against a provider that rejects it
{ "type": "http", "url": "https://mcp.example.com/mcp" }
// after: supply pre-registered OAuth client credentials
{
  "type": "http",
  "url": "https://mcp.example.com/mcp",
  "oauth": { "clientId": "my-registered-client", "clientSecret": "..." }
}
Defensive patterns

Strategy: try-catch

Validate before calling

const meta = await fetch(`${serverUrl.replace(/\/$/, '')}/.well-known/oauth-authorization-server`);
if (!meta.ok) throw new Error(`No OAuth metadata at ${serverUrl} (HTTP ${meta.status})`);

Type guard

function isOAuthError(err: unknown): err is Error & { oauthFailure: true } {
  return err instanceof Error && err.message.startsWith("OAuth authentication failed:");
}

Try / catch

try {
  await runMcpOAuthLogin(serverName);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("OAuth authentication failed:")) {
    const rootCause = err.message.slice("OAuth authentication failed: ".length);
    logger.warn("MCP OAuth failed", { serverName, rootCause }); // inspect rootCause for invalid_client/scope
  } else throw err;
}

Prevention

When it happens

Trigger: Running /mcp auth (or a reauth) against an MCP server where the OAuth flow throws an unclassified error: the provider rejects the client_id or redirect_uri, DCR registration fails, the token endpoint returns an unexpected status/body, the callback server port is taken, or the server returns a non-standard error code not containing 'timeout', '403', 'unauthorized', 'invalid_grant', 'ECONNREFUSED', or 'fetch failed'.

Common situations: Misconfigured OAuth client credentials in the MCP server config; the authorization server returning 400 invalid_client or invalid_scope; an IdP that does not support RFC 8414 metadata discovery; a corporate proxy returning a non-standard error page; a server whose token endpoint replies with a 500 that the flow surfaces verbatim.

Understand the failure class

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/20b62d9e01bc821d. Report an issue: GitHub.