{"record":{"id":"ee7cc6a1d58d0c7c","repo":"can1357/oh-my-pi","slug":"failed-to-parse-mcp-response-ee7cc6","errorCode":null,"errorMessage":"Failed to parse MCP response","messagePattern":"Failed to parse MCP response","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/web/search/providers/exa.ts","lineNumber":405,"sourceCode":"\t\t}\n\t\tthrow new SearchProviderError(\n\t\t\t\"exa\",\n\t\t\t`Exa MCP request failed (${response.status}): ${errorText}`,\n\t\t\tresponse.status,\n\t\t);\n\t}\n\tconst mcpResponse = parseSSE(await response.text()) as {\n\t\tresult?: {\n\t\t\tcontent?: Array<{ type: string; text?: string }>;\n\t\t\tisError?: boolean;\n\t\t};\n\t\terror?: {\n\t\t\tcode: number;\n\t\t\tmessage: string;\n\t\t};\n\t} | null;\n\tif (!mcpResponse) {\n\t\tthrow new Error(\"Failed to parse MCP response\");\n\t}\n\tif (mcpResponse.error) {\n\t\tthrow new Error(`MCP error: ${mcpResponse.error.message}`);\n\t}\n\tif (mcpResponse.result?.isError) {\n\t\tconst message = mcpResponse.result.content\n\t\t\t?.find(item => item.type === \"text\" && typeof item.text === \"string\")\n\t\t\t?.text?.trim();\n\t\tthrow new SearchProviderError(\"exa\", message || \"Exa MCP returned an error\");\n\t}\n\tconst responsePayload = normalizeExaMcpPayload(mcpResponse.result);\n\tif (isSearchResponse(responsePayload)) {\n\t\treturn responsePayload as ExaSearchResponse;\n\t}\n\n\tconst parsed = parseExaMcpTextPayload(responsePayload);\n\tif (parsed) {\n\t\treturn parsed;","sourceCodeStart":387,"sourceCodeEnd":423,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/exa.ts#L387-L423","documentation":"The provider parsed the SSE stream from the Exa MCP endpoint but could not extract a valid MCP response object (parseSSE returned null/unparseable). This is a plain Error indicating the wire response did not match the expected MCP JSON-RPC/SSE structure.","triggerScenarios":"parseSSE(await response.text()) produced null or a non-object: empty body, non-SSE content (e.g. an HTML error page that slipped past status checks), truncated stream, or a changed MCP response envelope.","commonSituations":"Exa MCP returning an unexpected content type during incidents; intermediary proxies mangling the SSE stream; protocol changes on the MCP endpoint.","solutions":["Log the raw response body to see what the endpoint actually returned.","Retry — a truncated/empty body is often transient.","Confirm the MCP endpoint URL and protocol version are current.","Fall back to the REST API path (configure EXA_API_KEY) or another provider."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isMcpResponse(x: unknown): x is { result?: unknown; error?: { code: number; message: string } } {\n  return typeof x === \"object\" && x !== null && (\"result\" in x || \"error\" in x);\n}","tryCatchPattern":"try {\n  const res = await searchExa({ query });\n} catch (e) {\n  if (e instanceof Error && e.message === \"Failed to parse MCP response\") {\n    // capture raw body / retry / fall back to REST path\n  } else throw e;\n}","preventionTips":["Validate the MCP envelope with a type guard before use (as the provider does)","Log raw SSE bodies when debugging protocol mismatches","Pin the MCP endpoint version and update the client on protocol changes","Keep the REST API path as fallback for MCP parse failures"],"tags":["parse-error","mcp","exa","sse","search-provider"],"backgroundTag":"response-parse-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}