can1357/oh-my-pi · error · Error

Exa MCP search returned unexpected response shape.

Error message

Exa MCP search returned unexpected response shape.

What it means

The Exa MCP call succeeded at the transport level and the result was not flagged as an error, but the payload did not match the expected ExaSearchResponse shape and could not be normalized/parsed into one (normalizeExaMcpPayload, isSearchResponse, and parseExaMcpTextPayload all failed). Thrown as a plain Error indicating a contract violation from the MCP endpoint.

Source

Thrown at packages/coding-agent/src/web/search/providers/exa.ts:426

		throw new Error(`MCP error: ${mcpResponse.error.message}`);
	}
	if (mcpResponse.result?.isError) {
		const message = mcpResponse.result.content
			?.find(item => item.type === "text" && typeof item.text === "string")
			?.text?.trim();
		throw new SearchProviderError("exa", message || "Exa MCP returned an error");
	}
	const responsePayload = normalizeExaMcpPayload(mcpResponse.result);
	if (isSearchResponse(responsePayload)) {
		return responsePayload as ExaSearchResponse;
	}

	const parsed = parseExaMcpTextPayload(responsePayload);
	if (parsed) {
		return parsed;
	}

	throw new Error("Exa MCP search returned unexpected response shape.");
}

/** Execute Exa web search */
export async function searchExa(params: ExaSearchParams): Promise<SearchResponse> {
	// AuthStorage-backed key takes precedence (existing behavior); probe it once
	// so the env-key and keyless-MCP fallbacks below stay intact, then drive the
	// authStorage path through the central force-refresh/rotate retry policy.
	const storedKey = params.authStorage
		? await params.authStorage.getApiKey("exa", params.sessionId, { signal: params.signal })
		: undefined;
	const keyOrResolver: ApiKey | undefined =
		storedKey && params.authStorage
			? params.authStorage.resolver("exa", { sessionId: params.sessionId })
			: getEnvApiKey("exa");
	const response = keyOrResolver
		? await withAuth(keyOrResolver, key => callExaSearch(key, params), { signal: params.signal })
		: await callExaMcpSearch(params);

View on GitHub (pinned to 9690622007)

Solutions

  1. Check for a client/package update that matches the current Exa MCP schema.
  2. Inspect the raw mcpResponse.result payload to identify the new shape and update normalizeExaMcpPayload/isSearchResponse accordingly.
  3. Pin/verify the MCP endpoint version if Exa exposes one.
  4. Fall back to the REST API path or another search provider.

Example fix

// before
const results = await searchExa({ query }); // keyless MCP path, new schema -> throws
// after
const results = await searchExa({ query, apiKey: process.env.EXA_API_KEY }); // stable REST path
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeExaSearchResult(r: unknown): boolean {
  const o = r as { results?: unknown[] } | null;
  return !!o && Array.isArray(o.results);
}

Type guard

function isExaSearchResponse(x: unknown): x is ExaSearchResponse {
  return typeof x === "object" && x !== null && "results" in x && Array.isArray((x as ExaSearchResponse).results);
}

Try / catch

try {
  const res = await searchExa({ query });
} catch (e) {
  if (e instanceof Error && e.message.includes("unexpected response shape")) {
    // schema drift: update client or fall back to REST provider
  } else throw e;
}

Prevention

When it happens

Trigger: Exa changed its MCP result schema (renamed fields, nested differently); the result contained a different tool's payload; an empty/odd result object passed all error checks but failed shape validation.

Common situations: Exa MCP API version drift without a corresponding client update; the agent requesting a tool whose payload shape differs from web_search; caching layers returning stale/reshaped payloads.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — 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/653b92bb3bf5a183. Report an issue: GitHub.