{"record":{"id":"6d47ddfb23adc52b","repo":"koala73/worldmonitor","slug":"http-resp-status-6d47dd","errorCode":null,"errorMessage":"HTTP ${resp.status}","messagePattern":"HTTP \\$\\{resp\\.status\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/components/McpDataPanel.ts","lineNumber":118,"sourceCode":"    this.showLoading();\n    try {\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'), {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          serverUrl: this.spec.serverUrl,\n          toolName: this.spec.toolName,\n          toolArgs: this.spec.toolArgs,\n          customHeaders: this.spec.customHeaders,\n        }),\n        signal: AbortSignal.any([this.destroyController.signal, AbortSignal.timeout(20_000)]),\n      });\n      const data = await resp.json() as { result?: McpResult; error?: string };\n      if (!resp.ok || data.error) throw new Error(data.error || `HTTP ${resp.status}`);\n      this.lastFetchedAt = Date.now();\n      this.renderResult(data.result ?? {});\n    } catch (err) {\n      const msg = err instanceof Error ? err.message : String(err);\n      this.showError(msg);\n    }\n  }\n\n  private renderResult(result: McpResult): void {\n    const jsonData = this.extractJsonData(result);\n\n    if (jsonData !== null && isProWidgetEnabled()) {\n      const hash = JSON.stringify(jsonData).slice(0, 8192);\n      if (hash === this.lastJsonHash && this.cachedWidgetHtml) {\n        this.setSafeContent(unsafeRawHtml(`\n          <div class=\"mcp-panel-meta\">${this.buildMetaLine()}</div>\n          <div class=\"mcp-panel-content mcp-panel-widget\">${wrapProWidgetHtml(this.cachedWidgetHtml)}</div>\n        `, 'legacy Panel.setContent() migration'));","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/components/McpDataPanel.ts#L100-L136","documentation":"Thrown by McpDataPanel when invoking a remote MCP tool via POST /api/mcp-proxy fails: the JSON body carries { serverUrl, toolName, toolArgs, customHeaders }, and on failure the message is data.error from the proxy response (remote tool error, unknown tool, argument validation failure at the MCP server) or the literal 'HTTP <status>' when the body carries no error field. The signal is AbortSignal.any([destroyController, timeout(20s)]), so panel destruction aborts instead of throwing this.","triggerScenarios":"Calling a tool whose server went down after connect (data.error); passing toolName/toolArgs the server rejects — unknown tool or schema-invalid arguments (data.error); losing Pro auth between connect and invoke (401/403, HTTP path); proxy 5xx. Slow tools exceeding 20s abort with TimeoutError instead — this message means a completed HTTP exchange with a bad outcome.","commonSituations":"Dashboard configs persisting a widget spec whose MCP server credentials expired; toolArgs exported from a different server version (schema drift); panel kept alive across sessions where the stored serverUrl rotted.","solutions":["Validate toolName and toolArgs against the tools list returned at connect time before invoking; re-connect to refresh it","Read data.error first — it is the remote MCP server's own error text and names the real cause (unknown tool vs auth vs crash)","Re-run connect to confirm the server is still reachable and the user's premium auth still passes the proxy gate","Persist server credentials with the spec so customHeaders survive reloads instead of silently degrading to 401"],"exampleFix":"// before\nconst data = await resp.json();\nif (!resp.ok || data.error) throw new Error(data.error || `HTTP ${resp.status}`);\n\n// after (gate the call on the connected tool inventory):\nif (!this.knownTools?.some(t => t.name === this.spec.toolName)) {\n  this.showError(`Unknown tool: ${this.spec.toolName}`); // fail before the network round-trip\n  return;\n}\nconst data = await resp.json();\nif (!resp.ok || data.error) throw new Error(data.error || `HTTP ${resp.status}`);","handlingStrategy":"try-catch","validationCode":"const known = this.knownTools?.map(t => t.name) ?? [];\nif (!known.includes(this.spec.toolName)) { this.showError(`Unknown tool: ${this.spec.toolName}`); return; }","typeGuard":"function isKnownTool(spec: { toolName: string }, known: { name: string }[]): boolean { return known.some(t => t.name === spec.toolName); }","tryCatchPattern":"catch (err) { const msg = err instanceof Error ? err.message : String(err); if (/^HTTP \\d+$/.test(msg)) { handleProxyStatus(Number(msg.slice(5))); } else { showError(msg); // data.error: remote MCP server's own message — show verbatim } }","preventionTips":["Re-connect to refresh the tool inventory before invoking stored widget specs","Persist customHeaders with the spec so auth survives reloads","Abort on destroy (the panel already unions the destroy signal) so teardown never surfaces as this error"],"tags":["network","http-status","mcp","proxy","tool-invocation"],"backgroundTag":"http-error-status","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}