toeverything/AFFiNE · error · BadRequestException
MCP credential not found
Error message
MCP credential not found
What it means
BadRequestException('MCP credential not found') thrown at the top of MCP credential rotation. Rotation looks up the credential by id and requires ALL of: it exists, belongs to the calling userId, belongs to the given workspaceId, is not revoked, not already replaced, and not expired. Any mismatch aborts rotation — you can only rotate a live credential you own.
Source
Thrown at packages/backend/server/src/plugins/copilot/mcp/credential.ts:105
}
@Transactional()
async rotate(
id: string,
userId: string,
workspaceId: string,
expirationDays: number
) {
const current = await this.models.mcpCredential.get(id);
if (
!current ||
current.userId !== userId ||
current.workspaceId !== workspaceId ||
current.revokedAt ||
current.replacedById ||
current.expiresAt <= new Date()
) {
throw new BadRequestException('MCP credential not found');
}
const maximumGraceEnd = new Date(Date.now() + ROTATION_GRACE_MS);
const graceEnd =
current.expiresAt < maximumGraceEnd ? current.expiresAt : maximumGraceEnd;
const issued = await this.issue({
userId,
workspaceId,
name: current.name,
accessMode: current.accessMode,
expirationDays,
familyId: current.familyId,
generation: current.generation + 1,
graceEndsAt: graceEnd,
});
const replaced = await this.models.mcpCredential.replace(
current.id,
userId,View on GitHub (pinned to b4c8548c09)
Solutions
- Re-fetch the credential list for the workspace and rotate the current (unreplaced, unrevoked, unexpired) entry
- If the credential expired, issue a new credential instead of rotating
- Confirm the credential belongs to the same userId and workspaceId used in the rotate call
- Guard against double-rotation: disable the rotate button once a rotation is in flight
Example fix
// before
await credentials.rotate(id, userId, workspaceId); // id already replaced/expired
// after
const current = (await listCredentials(workspaceId)).find(
c => c.familyId === familyId && !c.replacedById && !c.revokedAt && c.expiresAt > new Date()
);
if (!current) {
const issued = await credentials.create({ userId, workspaceId, name, accessMode, expirationDays });
} else {
await credentials.rotate(current.id, userId, workspaceId);
} Defensive patterns
Strategy: validation
Validate before calling
const c = await models.mcpCredential.get(id);
const rotatable =
!!c &&
c.userId === userId &&
c.workspaceId === workspaceId &&
!c.revokedAt &&
!c.replacedById &&
c.expiresAt > new Date();
if (!rotatable) throw new Error('Credential not rotatable — refetch list'); Type guard
const isRotatableCredential = ( c: McpCredential | null | undefined, userId: string, workspaceId: string ): c is McpCredential => !!c && c.userId === userId && c.workspaceId === workspaceId && !c.revokedAt && !c.replacedById && c.expiresAt.getTime() > Date.now();
Try / catch
try {
await credentials.rotate(id, userId, workspaceId);
} catch (e) {
if (e instanceof BadRequestException && e.message === 'MCP credential not found') {
await refreshCredentialList(); // pick the current active credential or create a new one
} else throw e;
} Prevention
- Always rotate the newest active credential in the family, never a cached id
- Disable rotate actions on revoked/replaced/expired rows in the UI
- Issue a fresh credential instead of rotating an expired one
When it happens
Trigger: Calling rotateMcpCredential with a credential id from another user/workspace; rotating a credential already rotated (replacedById set); rotating after it expired (expiresAt <= now); rotating a revoked credential; typo'd/stale id.
Common situations: UI lists credentials from a stale fetch and the credential was rotated in another tab; cron rotates on schedule but the credential already aged out; copying credential ids between workspaces.
Related errors
- Unauthorized
- -32000
- Unsupported MCP credential expiration
- MCP credential name is required
- MCP write tools are not available
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/f73bf15546201b10.
Report an issue: GitHub.