can1357/oh-my-pi · error · SearchProviderError

Failed to parse Z.AI MCP response

Error message

Failed to parse Z.AI MCP response

What it means

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.

Source

Thrown at packages/coding-agent/src/web/search/providers/zai.ts:110

function parseZaiMcpResponse(rawText: string): unknown {
	const parsedMessages: unknown[] = [];
	for (const line of rawText.split("\n")) {
		const trimmed = line.trim();
		if (!trimmed.startsWith("data:")) continue;
		const data = trimmed.slice(5).trim();
		if (!data) continue;
		try {
			parsedMessages.push(JSON.parse(data));
		} catch {
			// Ignore non-JSON data events.
		}
	}

	if (parsedMessages.length === 0) {
		try {
			parsedMessages.push(JSON.parse(rawText));
		} catch {
			throw new SearchProviderError("zai", "Failed to parse Z.AI MCP response", 500);
		}
	}

	return parsedMessages[parsedMessages.length - 1];
}

async function postZaiMcp(
	apiKey: string,
	method: string,
	params: Record<string, unknown>,
	sessionId: string | undefined,
	signal: AbortSignal | undefined,
	fetchImpl: FetchImpl,
	expectResponse: boolean,
	timeoutMs?: number,
): Promise<ZaiMcpPostResult> {
	const headers: Record<string, string> = {
		Authorization: `Bearer ${apiKey}`,

View on GitHub (pinned to 9690622007)

Solutions

  1. 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.
  2. Check for a proxy, VPN, or captive portal intercepting api.z.ai traffic and bypass or disable it.
  3. Verify Z.AI service status / the MCP endpoint URL (ZAI_MCP_URL) has not changed or moved.
  4. If you pass a custom fetch implementation, ensure it returns the real JSON/SSE body rather than an HTML mock or error page.

Example fix

// before: custom fetch swallows real response
const fetch = async () => new Response(await Bun.file('fixture.html').text(), { status: 200 });
// after: mock must return valid JSON-RPC body
const fetch = async () => Response.json({ jsonrpc: '2.0', id: 1, result: {} });
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate the response shape before trusting the search result:
const resp = await fetchImpl(url, init);
const ct = resp.headers.get('content-type') ?? '';
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
if (!/json|event-stream/i.test(ct)) throw new Error(`Unexpected content-type: ${ct}`);

Type guard

function isJsonRpcLike(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Try / catch

try {
  const result = await searchZai({ query, authStorage });
} catch (err) {
  if (err instanceof SearchProviderError && err.message.includes('Failed to parse Z.AI MCP response')) {
    // Non-JSON body with HTTP 200: fall back to another search provider.
    return fallbackSearch(query);
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/c1f991e3c01c3597. Report an issue: GitHub.