{"record":{"id":"b3925b3e5a2cd690","repo":"koala73/worldmonitor","slug":"http-resp-status-b3925b","errorCode":null,"errorMessage":"HTTP ${resp.status}","messagePattern":"HTTP \\$\\{resp\\.status\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/components/McpConnectModal.ts","lineNumber":417,"sourceCode":"    const serverUrl = urlInput.value.trim();\n    if (!serverUrl) return;\n    track('mcp-connect-attempt');\n    connectStatus.textContent = t('mcp.connecting');\n    connectStatus.className = 'mcp-connect-status mcp-status-loading';\n    connectBtn.disabled = true;\n    try {\n      const headers = getEffectiveHeaders();\n      const qs = new URLSearchParams({ serverUrl });\n      if (Object.keys(headers).length) qs.set('headers', JSON.stringify(headers));\n      // premiumFetch attaches the Clerk Pro Bearer for normal web Pro\n      // users. /api/mcp-proxy is in PREMIUM_RPC_PATHS so the path gate\n      // fires; the server-side isCallerPremium check accepts Bearer,\n      // wm_ user keys, and enterprise keys (PR #3768).\n      const resp = await premiumFetch(`${proxyUrl('/api/mcp-proxy')}?${qs}`, {\n        signal: AbortSignal.timeout(20_000),\n      });\n      const data = await resp.json() as { tools?: McpToolDef[]; error?: string };\n      if (!resp.ok || data.error) throw new Error(data.error || `HTTP ${resp.status}`);\n      tools = data.tools ?? [];\n      connectStatus.textContent = t('mcp.foundTools', { count: String(tools.length) });\n      connectStatus.className = 'mcp-connect-status mcp-status-ok';\n      track('mcp-connect-success', { toolCount: tools.length });\n      toolsSection.style.display = '';\n      renderTools(tools);\n    } catch (err) {\n      const msg = err instanceof Error ? err.message : String(err);\n      connectStatus.textContent = `${t('mcp.connectFailed')}: ${msg}`;\n      connectStatus.className = 'mcp-connect-status mcp-status-error';\n    } finally {\n      connectBtn.disabled = false;\n    }\n  });\n\n  addBtn.addEventListener('click', () => {\n    if (!selectedTool) return;\n    track('mcp-panel-add', { tool: selectedTool.name });","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/components/McpConnectModal.ts#L399-L435","documentation":"Thrown by McpConnectModal's connect flow when the GET to /api/mcp-proxy?serverUrl=... (via premiumFetch, 20s AbortSignal.timeout) fails: the error message is data.error from the proxy's JSON body if present (e.g. the remote MCP server was unreachable, the SSE/StreamableHTTP handshake failed, or the server returned a protocol error), otherwise the literal 'HTTP <status>'. The catch shows it next to 'Connect failed'.","triggerScenarios":"Connecting to an MCP server URL that is down, not an MCP endpoint, requires auth not supplied in the custom headers, or speaks a protocol the proxy cannot negotiate (data.error path); a 401/403 from the proxy's isCallerPremium gate (web user without Pro, or a wm_/enterprise key the server rejects); 404/5xx from the proxy itself (HTTP path). Note: exceeding the 20s timeout throws an AbortError with a TimeoutError cause, not this message.","commonSituations":"Typo'd or stale serverUrl (server moved); local MCP server not running when connecting from the hosted app (localhost URL from a deployed page); Pro subscription lapsed so the path gate 401s; corporate proxy stripping SSE so the handshake errors.","solutions":["Verify the MCP server URL opens and negotiates in a standalone MCP client first; if connecting to localhost, run the browser page locally too","Check the user's Pro auth (Bearer/wm_ key per PR #3768) — a 401 here is the premium gate, not the remote server","Use the proxy's data.error text: it names the remote failure reason; correlate with server-side mcp-proxy logs","Slow server? The 20s AbortSignal.timeout in the connect call is the ceiling — start the server before connecting or raise the budget"],"exampleFix":"// before\nconst resp = await premiumFetch(`${proxyUrl('/api/mcp-proxy')}?${qs}`, { signal: AbortSignal.timeout(20_000) });\nif (!resp.ok || data.error) throw new Error(data.error || `HTTP ${resp.status}`);\n\n// after (pre-validate the URL shape before hitting the proxy):\nconst parsed = new URL(serverUrl); // throws on malformed input before any network call\nif (!/^https?:$/.test(parsed.protocol)) throw new Error('serverUrl must be http(s)');\nconst resp = await premiumFetch(`${proxyUrl('/api/mcp-proxy')}?${qs}`, { signal: AbortSignal.timeout(20_000) });\nif (!resp.ok || data.error) throw new Error(data.error || `HTTP ${resp.status}`);","handlingStrategy":"try-catch","validationCode":"let parsed: URL;\ntry { parsed = new URL(serverUrl); } catch { return connectFailed('serverUrl is not a valid URL'); }\nif (!/^https?:$/.test(parsed.protocol)) return connectFailed('serverUrl must be http(s)');","typeGuard":"function isHttpUrl(value: string): boolean { try { return /^https?:$/.test(new URL(value).protocol); } catch { return false; } }","tryCatchPattern":"catch (err) { const msg = err instanceof Error ? err.message : String(err); if (/^HTTP \\d+$/.test(msg)) { const s = Number(msg.slice(5)); if (s === 401 || s === 403) showUpgradeAuth(); else showProxyIssue(s); } else showRemoteServerError(msg); // data.error path names the remote cause }","preventionTips":["Verify the MCP server URL in a standalone MCP client before wiring it into the dashboard","Remember localhost URLs only work when the page itself runs locally","Read data.error first — it carries the remote server's own failure reason; correlate with proxy logs"],"tags":["network","http-status","mcp","proxy","auth"],"backgroundTag":"http-error-status","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}