can1357/oh-my-pi · error · Error
Broker returned non-OAuth credential for ${provider}
Error message
Broker returned non-OAuth credential for ${provider} What it means
refreshBrokeredMcpOAuthCredential delegates an MCP OAuth refresh to the auth broker via authStorage.forceRefreshCredentialById and assumes the refreshed entry is of type "oauth". If the broker hands back a credential with a different type (e.g. "api_key"), the shape expected by the OAuth refresh path (access/refresh/expires fields) is not there, so this error is thrown as a type-invariant guard.
Source
Thrown at packages/coding-agent/src/mcp/oauth-credentials.ts:131
const authorizationUrl = material && "authorizationUrl" in material ? material.authorizationUrl : undefined;
const resourceIsFallback = !material?.resource && Boolean(opts.serverUrl);
const resource = material?.resource ?? (resourceIsFallback ? opts.serverUrl : undefined);
return refreshMCPOAuthToken(tokenUrl, credential.refresh, material?.clientId, material?.clientSecret, resource, {
authorizationUrl,
stripSameOriginResource: resourceIsFallback,
signal: opts.signal,
});
}
async function refreshBrokeredMcpOAuthCredential(
authStorage: AuthStorage,
credentialId: number,
provider: string,
signal?: AbortSignal,
): Promise<OAuthCredentials> {
const entry = await authStorage.forceRefreshCredentialById(credentialId, signal);
if (entry.credential.type !== "oauth") {
throw new Error(`Broker returned non-OAuth credential for ${provider}`);
}
const refreshed = entry.credential;
return {
access: refreshed.access,
refresh: REMOTE_REFRESH_SENTINEL,
expires: refreshed.expires,
accountId: refreshed.accountId,
email: refreshed.email,
projectId: refreshed.projectId,
enterpriseUrl: refreshed.enterpriseUrl,
};
}
/**
* Resolve and refresh one stored MCP OAuth row through the durable credential owner.
*
* Local rows use their embedded OAuth metadata; broker-redacted rows delegate the
* grant to the broker. The MCP manager and standalone credential consumers shareView on GitHub (pinned to 9690622007)
Solutions
- Verify the credential id maps to an OAuth-type credential in the broker's vault (re-register or fix the id)
- Re-authenticate the MCP server so the vault row is recreated with type "oauth"
- Check for vault migration or corruption that relabeled the credential type and restore from a known-good state
- Update the caller's credential-id resolution (e.g. mcpOAuthServerUrlFromCredentialId mapping) if ids shifted
Example fix
// before: passing an id that points at an api_key row
await refreshStoredManagedMcpOAuthCredential(stored, { brokerUrl, credentialId: 42 });
// after: resolve the id through the broker's typed lookup first
const entry = await authStorage.getCredentialById(42);
if (entry?.credential.type !== "oauth") throw new Error("id 42 is not an OAuth credential");
await refreshStoredManagedMcpOAuthCredential(stored, { brokerUrl, credentialId: 42 }); Defensive patterns
Strategy: type-guard
Validate before calling
const entry = await authStorage.getCredentialById(credentialId);
if (entry?.credential.type !== 'oauth') {
throw new Error(`credential ${credentialId} is not OAuth; fix the id mapping before refresh`);
} Type guard
function isOAuthCredential(c) {
return typeof c === 'object' && c !== null && c.type === 'oauth' &&
typeof c.access === 'string';
} Try / catch
try {
creds = await refreshStoredManagedMcpOAuthCredential(stored, { brokerUrl, credentialId });
} catch (e) {
if (e.message.includes('non-OAuth credential')) {
// re-register/re-authenticate so the vault row has type 'oauth'
await reauthenticateProvider(provider);
} else throw e;
} Prevention
- Keep a single source of truth mapping MCP server names to credential ids
- After vault migrations or re-registrations, verify credential types before refresh
- Log credential type at registration time to catch mismatches early
When it happens
Trigger: Calling refreshStoredManagedMcpOAuthCredential for a broker-managed credential id that resolves in the vault to a non-OAuth credential type, causing forceRefreshCredentialById to return entry.credential.type !== "oauth".
Common situations: Credential-id mixups where an mcp_oauth: id actually points at an api_key row; vault state desync after re-registering a provider under the same id; a broker configured against a changed or migrated credential store.
Related errors
- MCP OAuth credential is missing refresh material
- Token exchange returned no access token${providerError ? `:
- MCP OAuth refresh failed: ${response.status} ${text}
- HTTP ${response.status} resuming MCP SSE stream: auth refres
- Invalid OAuth URLs. Please check: Authorization URL: ${aut
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3138f772cb1efc6b.
Report an issue: GitHub.