{"record":{"id":"516f759ff72d6d4f","repo":"can1357/oh-my-pi","slug":"mcp-error-response-error-message","errorCode":null,"errorMessage":"MCP error: ${response.error.message}","messagePattern":"MCP error: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/exa/mcp-client.ts","lineNumber":84,"sourceCode":"\t\tif (isSearchResponse(candidate)) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\treturn payload;\n}\n\n/** Fetch available tools from Exa MCP */\nexport async function fetchExaTools(apiKey: string | null, toolNames: string[]): Promise<MCPTool[]> {\n\tconst params = new URLSearchParams();\n\tif (apiKey) params.set(\"exaApiKey\", apiKey);\n\tparams.set(\"toolNames\", toolNames.join(\",\"));\n\tconst url = `https://mcp.exa.ai/mcp?${params.toString()}`;\n\tconst response = (await callMCP(url, \"tools/list\")) as MCPToolsResponse;\n\n\tif (response.error) {\n\t\tlogger.error(\"MCP tools/list error\", { toolNames, error: response.error });\n\t\tthrow new Error(`MCP error: ${response.error.message}`);\n\t}\n\n\treturn response.result?.tools ?? [];\n}\n\n/** Fetch available tools from Websets MCP */\nexport async function fetchWebsetsTools(apiKey: string): Promise<MCPTool[]> {\n\tconst url = `https://websetsmcp.exa.ai/mcp?exaApiKey=${encodeURIComponent(apiKey)}`;\n\tconst response = (await callMCP(url, \"tools/list\")) as MCPToolsResponse;\n\n\tif (response.error) {\n\t\tlogger.error(\"Websets MCP tools/list error\", { error: response.error });\n\t\tthrow new Error(`MCP error: ${response.error.message}`);\n\t}\n\n\treturn response.result?.tools ?? [];\n}\n","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/exa/mcp-client.ts#L66-L102","documentation":"`fetchExaTools` performs an MCP `tools/list` JSON-RPC request against https://mcp.exa.ai/mcp. When the response carries an `error` object (JSON-RPC error or MCP protocol error), the function logs it and rethrows it as `Error(\"MCP error: <message>\")`, surfacing the server-side reason to the caller.","triggerScenarios":"Calling `fetchExaTools` (directly or via `tools`/`fetchMCPToolSchema`) when the Exa MCP server rejects `tools/list`: invalid or expired EXA_API_KEY passed as the exaApiKey query param, unknown/unsupported toolNames, rate limiting, or Exa-side protocol/deployment errors.","commonSituations":"Rotated or revoked EXA_API_KEY still cached in env/.env; requesting a tool name Exa no longer exposes; regional outages or MCP protocol version mismatch; hitting the endpoint without network egress allowed.","solutions":["Read `response.error.message` in the thrown text — it states the server's exact reason (auth, unknown tool, rate limit).","Verify EXA_API_KEY is current (test with a direct curl to the Exa API) and update .env/env.","Retry with backoff for transient 5xx/rate-limit errors; check Exa status if it persists.","Confirm the requested toolNames exist against Exa's current MCP catalog and remove stale names."],"exampleFix":"// before\nconst tools = await fetchExaTools(staleApiKey, [\"web_search_exa\"]); // MCP error: invalid api key\n// after\nconst apiKey = findApiKey();\nif (!apiKey) throw new Error(\"EXA_API_KEY missing\");\nconst tools = await fetchExaTools(apiKey, [\"web_search_exa\"]);","handlingStrategy":"try-catch","validationCode":"const apiKey = findApiKey();\nif (!apiKey) throw new Error(\"EXA_API_KEY missing — set it in env or .env before listing Exa MCP tools\");","typeGuard":"function isMCPToolsResponse(r: unknown): r is MCPToolsResponse {\n  return typeof r === \"object\" && r !== null && !(\"error\" in r && r.error);\n}","tryCatchPattern":"let tools: MCPTool[];\ntry {\n  tools = await fetchExaTools(apiKey, toolNames);\n} catch (err) {\n  const msg = err instanceof Error ? err.message : String(err);\n  if (msg.startsWith(\"MCP error:\") && /rate|429/i.test(msg)) {\n    await Bun.sleep(retryDelay);\n    tools = await fetchExaTools(apiKey, toolNames);\n  } else {\n    throw err; // auth/unknown-tool errors are not retryable\n  }\n}","preventionTips":["Keep EXA_API_KEY fresh and validate it with a cheap API call at startup.","Check the thrown message for the server's reason before choosing retry vs abort.","Cache tool schemas (fetchMCPToolSchema already does) to reduce tools/list traffic.","Handle the error at the MCPWrappedTool.execute boundary, which already converts thrown errors into tool-result text instead of crashing the run."],"tags":["typescript","network","mcp","api-key","json-rpc"],"backgroundTag":"mcp-server-error","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}