can1357/oh-my-pi · error · Error

MCP error: ${response.error.message}

Error message

MCP error: ${response.error.message}

What it means

`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.

Source

Thrown at packages/coding-agent/src/exa/mcp-client.ts:84

		if (isSearchResponse(candidate)) {
			return candidate;
		}
	}

	return payload;
}

/** Fetch available tools from Exa MCP */
export async function fetchExaTools(apiKey: string | null, toolNames: string[]): Promise<MCPTool[]> {
	const params = new URLSearchParams();
	if (apiKey) params.set("exaApiKey", apiKey);
	params.set("toolNames", toolNames.join(","));
	const url = `https://mcp.exa.ai/mcp?${params.toString()}`;
	const response = (await callMCP(url, "tools/list")) as MCPToolsResponse;

	if (response.error) {
		logger.error("MCP tools/list error", { toolNames, error: response.error });
		throw new Error(`MCP error: ${response.error.message}`);
	}

	return response.result?.tools ?? [];
}

/** Fetch available tools from Websets MCP */
export async function fetchWebsetsTools(apiKey: string): Promise<MCPTool[]> {
	const url = `https://websetsmcp.exa.ai/mcp?exaApiKey=${encodeURIComponent(apiKey)}`;
	const response = (await callMCP(url, "tools/list")) as MCPToolsResponse;

	if (response.error) {
		logger.error("Websets MCP tools/list error", { error: response.error });
		throw new Error(`MCP error: ${response.error.message}`);
	}

	return response.result?.tools ?? [];
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Read `response.error.message` in the thrown text — it states the server's exact reason (auth, unknown tool, rate limit).
  2. Verify EXA_API_KEY is current (test with a direct curl to the Exa API) and update .env/env.
  3. Retry with backoff for transient 5xx/rate-limit errors; check Exa status if it persists.
  4. Confirm the requested toolNames exist against Exa's current MCP catalog and remove stale names.

Example fix

// before
const tools = await fetchExaTools(staleApiKey, ["web_search_exa"]); // MCP error: invalid api key
// after
const apiKey = findApiKey();
if (!apiKey) throw new Error("EXA_API_KEY missing");
const tools = await fetchExaTools(apiKey, ["web_search_exa"]);
Defensive patterns

Strategy: try-catch

Validate before calling

const apiKey = findApiKey();
if (!apiKey) throw new Error("EXA_API_KEY missing — set it in env or .env before listing Exa MCP tools");

Type guard

function isMCPToolsResponse(r: unknown): r is MCPToolsResponse {
  return typeof r === "object" && r !== null && !("error" in r && r.error);
}

Try / catch

let tools: MCPTool[];
try {
  tools = await fetchExaTools(apiKey, toolNames);
} catch (err) {
  const msg = err instanceof Error ? err.message : String(err);
  if (msg.startsWith("MCP error:") && /rate|429/i.test(msg)) {
    await Bun.sleep(retryDelay);
    tools = await fetchExaTools(apiKey, toolNames);
  } else {
    throw err; // auth/unknown-tool errors are not retryable
  }
}

Prevention

When it happens

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

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

Related errors


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