can1357/oh-my-pi · error · Error
MCP OAuth refresh failed: ${response.status} ${text}
Error message
MCP OAuth refresh failed: ${response.status} ${text} What it means
Thrown when the OAuth token refresh POST to the provider returns a non-OK HTTP status. The message embeds the status code and the raw response body text, which usually contains a JSON error like `invalid_grant` (refresh token revoked/expired). The caller must re-authenticate interactively; silent refresh is impossible at that point.
Source
Thrown at packages/coding-agent/src/mcp/oauth-flow.ts:827
if (normalizedClientId) params.set("client_id", normalizedClientId);
// Drop redundant indicators so refresh stays consistent with the initial
// grant; see {@link filterResourceIndicator} for context.
const resolvedResource = filterResourceIndicator(resolveResourceUri(resource), filterAnchor, {
stripSameOriginResource: optsFromTrailing?.stripSameOriginResource,
});
if (resolvedResource) params.set("resource", resolvedResource);
if (clientSecret) params.set("client_secret", clientSecret);
const response = await fetchImpl(tokenUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params.toString(),
signal: optsFromTrailing?.signal,
});
if (!response.ok) {
const text = await response.text();
throw new Error(`MCP OAuth refresh failed: ${response.status} ${text}`);
}
const data = (await response.json()) as {
access_token: string;
refresh_token?: string;
expires_in?: number;
};
const expiresIn = data.expires_in ?? 3600;
return {
access: data.access_token,
refresh: data.refresh_token ?? refreshToken,
expires: Date.now() + expiresIn * 1000,
};
}
View on GitHub (pinned to 9690622007)
Solutions
- Read the body after the status code — `invalid_grant`/`invalid_token` means re-authentication is required
- Delete the stored token file / clear cached credentials for that MCP server and run the OAuth login flow again
- Confirm the stored refresh token is current if your provider rotates refresh tokens (store the new one from every refresh response)
- Check provider status pages if the failure is a 5xx rather than 4xx
Example fix
// before: retrying refresh forever on invalid_grant
try { await refresh(token.refreshToken); } catch { /* retry */ }
// after: detect invalid_grant and fall back to interactive login
catch (e) {
if (String(e.message).includes("invalid_grant")) await runOAuthLogin(server);
else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// check a refresh token exists and isn't obviously stale before refreshing
if (!stored.refreshToken) throw new Error("No refresh token stored; interactive login required");
if (Date.now() - stored.refreshedAt > MAX_REFRESH_TOKEN_AGE_MS) promptRelogin(); Type guard
function isInvalidGrant(err: unknown): boolean {
return err instanceof Error && /invalid_grant|invalid_token/i.test(err.message);
} Try / catch
try {
await refreshAccessToken(refreshToken);
} catch (err) {
if (isInvalidGrant(err)) {
await clearStoredCredentials(serverId);
await startInteractiveOAuthLogin(serverId); // re-auth is the only fix
} else throw err;
} Prevention
- Persist rotated refresh tokens — many providers invalidate the old token on each refresh
- Schedule proactive refresh before access_token expiry to reduce refresh-path failures
- Never store refresh tokens in shared/cache locations that other tooling may overwrite
- Treat invalid_grant as terminal: clear credentials and re-login rather than retrying
When it happens
Trigger: Refreshing an expired MCP OAuth access token when the server responds 400/401/403 — e.g. refresh token expired, revoked, or rotated and the stored one is stale.
Common situations: User revoked the app in the provider's dashboard, provider rotated refresh tokens on each use and an old refresh_token was reused, long-idle sessions past the refresh token's absolute lifetime, or provider changed endpoint requirements.
Related errors
- MCP OAuth credential is missing refresh material
- Could not discover OAuth endpoints from server response.
- ${label} missing refresh_token
- MCP request failed: ${response.status} ${response.statusText
- Broker returned non-OAuth credential for ${provider}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3491d30259387b30.
Report an issue: GitHub.