koala73/worldmonitor · warning · Error
Sign in to revoke MCP clients.
Error message
Sign in to revoke MCP clients.
What it means
Thrown by revokeMcpClient() in src/services/mcp-clients.ts when getClerkToken() resolves to null before the POST to /api/user/mcp-revoke is attempted. Clerk returns no token when there is no active session: signed out, session expired, or Clerk not yet finished booting. The client-side guard exists because the edge handler would reject an unauthenticated request with 401 anyway.
Source
Thrown at src/services/mcp-clients.ts:77
const rows = await settleAccountOperation(
userId,
'loading MCP clients',
() => client.query((api as any).mcpProTokens.listProMcpTokens, {}),
);
assertAccountStillCurrent(userId, 'loading MCP clients');
return rows as McpClientInfo[];
}
/**
* Revoke a Pro MCP token by tokenId.
*
* Calls the edge endpoint (NOT the public Convex mutation directly) so the
* negative-cache sentinel write is paired atomically with the Convex revoke.
* Throws on non-2xx so the UI can surface the error.
*/
export async function revokeMcpClient(tokenId: string): Promise<void> {
const token = await getClerkToken();
if (!token) throw new Error('Sign in to revoke MCP clients.');
const resp = await fetch('/api/user/mcp-revoke', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ tokenId }),
});
if (resp.ok) return;
if (resp.status === 404) {
throw new Error('This client was already revoked or no longer exists.');
}
if (resp.status === 409) {
throw new Error('This client was already revoked.');
}View on GitHub (pinned to eeab0a219f)
Solutions
- Re-authenticate: redirect to sign-in or call Clerk's openSignIn(), then retry the revoke.
- Only render enable-able revoke buttons after confirming getCurrentClerkUser() is non-null and a token is obtainable.
- Verify the Clerk publishable key configuration if getClerkToken() is null even while visibly signed in (session/token domain mismatch).
- Handle this message specially in the UI as a 'sign in required' state, not a generic error.
Example fix
// before
await revokeMcpClient(tokenId);
// after
import { getCurrentClerkUser } from '@/services/clerk';
if (!getCurrentClerkUser()) {
await clerkOpenSignIn();
return;
}
await revokeMcpClient(tokenId); Defensive patterns
Strategy: validation
Validate before calling
import { getCurrentClerkUser } from '@/services/clerk';
if (!getCurrentClerkUser()) {
await openClerkSignIn();
return;
}
await revokeMcpClient(tokenId); Type guard
function isSignedIn(userId: string | null | undefined): userId is string {
return typeof userId === 'string' && userId.length > 0;
} Try / catch
try {
await revokeMcpClient(tokenId);
} catch (err) {
if (err instanceof Error && err.message === 'Sign in to revoke MCP clients.') {
await openClerkSignIn(); // re-authenticate, then let the user retry
return;
}
throw err;
} Prevention
- Render revoke controls only for a confirmed Clerk session.
- Treat this exact message as a 'sign-in required' UX branch, not a generic error.
- Handle session-expiry events globally so long-open tabs re-auth before destructive actions.
When it happens
Trigger: Clicking 'Revoke' on an MCP client row after the Clerk session expired (idle overnight tab), while signed out in another tab, before Clerk finishes loading after a page refresh, or with a Clerk JS load failure that leaves no session.
Common situations: Long-lived dashboard tabs whose Clerk session lapsed; multi-tab sign-out; restricted networks blocking Clerk's API so no session can be established; opening the settings deep link before Clerk hydration completes.
Related errors
- Not authenticated
- UNAUTHENTICATED
- Authentication unavailable while loading MCP clients. Try ag
- Authentication unavailable while loading Business Pro seats.
- get_intel_timeline requires at least one of domain ("conflic
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/f0016ca5298a7ef5.
Report an issue: GitHub.