{"record":{"id":"03aeed8e238b0e42","repo":"koala73/worldmonitor","slug":"authentication-unavailable-while-loading-mcp-clien","errorCode":null,"errorMessage":"Authentication unavailable while loading MCP clients. Try again.","messagePattern":"Authentication unavailable while loading MCP clients\\. Try again\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/mcp-clients.ts","lineNumber":52,"sourceCode":"}\n\nexport interface McpQuota {\n  used: number;\n  /** Plan-resolved daily allowance. `null` = unlimited (plan 2026-07-25-001 U3b). */\n  limit: number | null;\n  resetsAt: string;\n}\n\n/** List all Pro MCP tokens for the current user. */\nexport async function listMcpClients(): Promise<McpClientInfo[]> {\n  const userId = getCurrentClerkUser()?.id;\n  if (!userId) return [];\n\n  const [client, api] = await Promise.all([getConvexClient(), getConvexApi()]);\n  if (!client || !api) return [];\n  if (!await waitForConvexAuthForUser(userId)) {\n    assertAccountStillCurrent(userId, 'loading MCP clients');\n    throw new Error('Authentication unavailable while loading MCP clients. Try again.');\n  }\n\n  // Mirror services/api-keys.ts:listApiKeys cast pattern — the generated\n  // Convex `api` is fully typed at module level but each service casts\n  // `as any` at the call-site to avoid pulling the entire generated index\n  // type into every service file.\n  const rows = await settleAccountOperation(\n    userId,\n    'loading MCP clients',\n    () => client.query((api as any).mcpProTokens.listProMcpTokens, {}),\n  );\n  assertAccountStillCurrent(userId, 'loading MCP clients');\n  return rows as McpClientInfo[];\n}\n\n/**\n * Revoke a Pro MCP token by tokenId.\n *","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/services/mcp-clients.ts#L34-L70","documentation":"Thrown by listMcpClients() in src/services/mcp-clients.ts when the Convex client and API loaded fine but waitForConvexAuthForUser(userId) returned false for the still-current Clerk user. That gate waits up to 10 s for the Convex server to confirm setAuth for exactly this userId and fails when the barrier times out or is superseded by a newer auth generation. The listProMcpTokens query is skipped because issuing it unauthenticated would fail server-side Clerk auth.","triggerScenarios":"Opening the Connected MCP clients settings tab immediately after sign-in before the Convex WebSocket auth handshake completes (10 s barrier timeout); a corporate proxy blocking the Convex WebSocket so setAuth never confirms; Clerk token issuance stalling; sign-out/sign-in churn replacing the auth barrier generation mid-wait.","commonSituations":"Slow first-load networks; enterprise networks that break WebSocket upgrades; Clerk dev-instance slowness; users who sign out and back in quickly while the settings panel is already mounted.","solutions":["Retry the listing after a brief delay — the auth barrier normally settles just past the timeout.","Confirm in DevTools (Network → WS) that the Convex socket connects and authenticates; investigate proxy/VPN interference if it stalls.","Check that Clerk and Convex are paired correctly (same deployment, working token exchange) if the error is deterministic.","Render an empty-but-retryable state in the MCP clients tab for this error instead of a hard failure."],"exampleFix":"// before\nconst clients = await listMcpClients();\n\n// after\nlet clients: McpClientInfo[];\ntry {\n  clients = await listMcpClients();\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Authentication unavailable')) {\n    await new Promise((r) => setTimeout(r, 1500));\n    clients = await listMcpClients();\n  } else {\n    throw err;\n  }\n}","handlingStrategy":"retry","validationCode":"import { waitForConvexAuthForUser } from '@/services/convex-client';\nimport { getCurrentClerkUser } from '@/services/clerk';\n\nconst userId = getCurrentClerkUser()?.id;\nif (!userId) return [];\nif (!await waitForConvexAuthForUser(userId, 15_000)) {\n  renderClientsRetryableEmpty();\n  return [];\n}\nreturn await listMcpClients();","typeGuard":null,"tryCatchPattern":"try {\n  return await listMcpClients();\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Authentication unavailable')) {\n    await delay(1500);\n    return await listMcpClients();\n  }\n  throw err;\n}","preventionTips":["Load the MCP clients tab data after the Convex auth barrier confirms, not concurrently with sign-in.","Expose a manual refresh on the tab for transient auth settling instead of showing a dead error.","Track how often the 10 s barrier times out per network to catch proxy/WebSocket interference early."],"tags":["convex","clerk","authentication","websocket","timeout","retryable"],"backgroundTag":"auth-handshake-timeout","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}