{"record":{"id":"c1f991e3c01c3597","repo":"can1357/oh-my-pi","slug":"failed-to-parse-z-ai-mcp-response","errorCode":null,"errorMessage":"Failed to parse Z.AI MCP response","messagePattern":"Failed to parse Z\\.AI MCP response","errorType":"http","errorClass":"SearchProviderError","httpStatus":500,"severity":"error","filePath":"packages/coding-agent/src/web/search/providers/zai.ts","lineNumber":110,"sourceCode":"function parseZaiMcpResponse(rawText: string): unknown {\n\tconst parsedMessages: unknown[] = [];\n\tfor (const line of rawText.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed.startsWith(\"data:\")) continue;\n\t\tconst data = trimmed.slice(5).trim();\n\t\tif (!data) continue;\n\t\ttry {\n\t\t\tparsedMessages.push(JSON.parse(data));\n\t\t} catch {\n\t\t\t// Ignore non-JSON data events.\n\t\t}\n\t}\n\n\tif (parsedMessages.length === 0) {\n\t\ttry {\n\t\t\tparsedMessages.push(JSON.parse(rawText));\n\t\t} catch {\n\t\t\tthrow new SearchProviderError(\"zai\", \"Failed to parse Z.AI MCP response\", 500);\n\t\t}\n\t}\n\n\treturn parsedMessages[parsedMessages.length - 1];\n}\n\nasync function postZaiMcp(\n\tapiKey: string,\n\tmethod: string,\n\tparams: Record<string, unknown>,\n\tsessionId: string | undefined,\n\tsignal: AbortSignal | undefined,\n\tfetchImpl: FetchImpl,\n\texpectResponse: boolean,\n\ttimeoutMs?: number,\n): Promise<ZaiMcpPostResult> {\n\tconst headers: Record<string, string> = {\n\t\tAuthorization: `Bearer ${apiKey}`,","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/zai.ts#L92-L128","documentation":"This SearchProviderError is thrown by parseZaiMcpResponse when the HTTP response body from Z.AI's MCP endpoint is neither a valid SSE stream (no 'data:' lines with JSON payloads) nor a valid JSON document. The provider attempted SSE-style extraction first, then a whole-body JSON.parse fallback, and both failed, meaning the body is HTML, empty, or otherwise non-JSON text returned with a 2xx status.","triggerScenarios":"postZaiMcp received response.ok === true from https://api.z.ai/api/mcp/web_search_prime/mcp, but the body text contains no parseable 'data:' SSE events and JSON.parse(rawText) throws. Typical bodies: an HTML login/interstitial page served behind a proxy, an empty body, a plain-text quota notice, or an unexpected content-type (e.g. text/html) from the endpoint.","commonSituations":"Corporate proxy or captive portal intercepting api.z.ai and returning HTML with status 200; Z.AI changing the MCP endpoint contract or serving a maintenance page; a misconfigured fetch override (params.fetch) in tests that returns mock HTML; region blocks redirecting to an HTML error page.","solutions":["Inspect the raw response body: log the response text and Content-Type header right before parseZaiMcpResponse is called to see what the server actually returned.","Check for a proxy, VPN, or captive portal intercepting api.z.ai traffic and bypass or disable it.","Verify Z.AI service status / the MCP endpoint URL (ZAI_MCP_URL) has not changed or moved.","If you pass a custom fetch implementation, ensure it returns the real JSON/SSE body rather than an HTML mock or error page."],"exampleFix":"// before: custom fetch swallows real response\nconst fetch = async () => new Response(await Bun.file('fixture.html').text(), { status: 200 });\n// after: mock must return valid JSON-RPC body\nconst fetch = async () => Response.json({ jsonrpc: '2.0', id: 1, result: {} });","handlingStrategy":"try-catch","validationCode":"// Pre-validate the response shape before trusting the search result:\nconst resp = await fetchImpl(url, init);\nconst ct = resp.headers.get('content-type') ?? '';\nif (!resp.ok) throw new Error(`HTTP ${resp.status}`);\nif (!/json|event-stream/i.test(ct)) throw new Error(`Unexpected content-type: ${ct}`);","typeGuard":"function isJsonRpcLike(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const result = await searchZai({ query, authStorage });\n} catch (err) {\n  if (err instanceof SearchProviderError && err.message.includes('Failed to parse Z.AI MCP response')) {\n    // Non-JSON body with HTTP 200: fall back to another search provider.\n    return fallbackSearch(query);\n  }\n  throw err;\n}","preventionTips":["Check the Content-Type header of responses from api.z.ai before parsing.","Bypass corporate proxies/VPNs for api.z.ai or add it to proxy exclusions.","Keep custom fetch mocks returning valid JSON-RPC or SSE data payloads.","Monitor Z.AI endpoint contract changes; pin and review provider updates."],"tags":["http","json-parsing","sse","third-party-api","response-shape"],"backgroundTag":"invalid-json-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}