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

  1. Re-authenticate: redirect to sign-in or call Clerk's openSignIn(), then retry the revoke.
  2. Only render enable-able revoke buttons after confirming getCurrentClerkUser() is non-null and a token is obtainable.
  3. Verify the Clerk publishable key configuration if getClerkToken() is null even while visibly signed in (session/token domain mismatch).
  4. 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

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


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/f0016ca5298a7ef5. Report an issue: GitHub.