MCP Server Errors

MCP Server Errors

How to handle errors returned by the foura-mcp server.

Every error response from any of the four tools (foura_auto, foura_single, foura_proxy, foura_browser) is structured. LLM agents can read the code field for retry logic without parsing prose.

Envelope shape

Every error (isError: true) carries a structuredContent block. Minimum fields on every error:

{
  "service": "auto | single | proxy | browser",
  "code": "rate_limited",
  "error": "Rate limit exceeded"
}

On upstream errors with HTTP status, status is also present. On rate-limit and capacity errors, the envelope adds retryAfter, current.{concurrency, rpm}, and limits.{maxConcurrency, maxRpm}, same shape as the underlying REST API errors.

Stable code values

Code HTTP Meaning Retry safe?
ssrf_blocked n/a Target IP in a private or reserved range (RFC 5735, RFC 6598, IPv6 reserved) No, change the URL
upstream_non_json varies Upstream returned a body that wasn't valid JSON Maybe, investigate
output_validation_failed n/a The MCP server's outputSchema rejected the upstream response (server bug or unexpected upstream shape) Maybe, report
bad_request 400 Input shape rejected by the FourA API No, fix the arguments
auth_failed 401 The FourA API key is missing, invalid, or deactivated; this is not about target-site credentials No, fix the FourA key
forbidden 403 Target rejected the request (anti-bot, geo-block) No, or switch to foura_proxy
not_found 404 Target URL or endpoint doesn't exist No
rate_limited 429 Per-key RPM cap hit Yes, wait retryAfter seconds
at_capacity 503 Concurrency cap hit (current.concurrency > limits.maxConcurrency) Yes, wait retryAfter seconds
service_disabled 503 Service disabled for your account (plan or maintenance) Contact support
service_unavailable 503 Generic 503 from upstream Yes, short backoff
upstream_error 500+ Upstream 5xx Yes, exponential backoff
upstream_client_error 4xx Other 4xx not covered above Usually no
upstream_unknown other Defensive, should not occur in practice Investigate
no_eligible_proxy n/a No proxy matches the strict exitCountries allowlist; details.exitCountries contains the normalized scope Retry later; change scope only explicitly

HTTP-level errors from the MCP server

Some failures happen at the MCP transport layer, before any tool is called. These return raw JSON-RPC errors (no structuredContent):

HTTP When What you see
400 Unsupported MCP-Protocol-Version header Unsupported MCP-Protocol-Version: <value>. Supported: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07.
401 Missing or malformed Authorization header JSON-RPC error + WWW-Authenticate: Bearer realm="foura-mcp", resource_metadata="https://foura.ai/docs/mcp/server#auth"
403 Disallowed Origin or Host header (DNS-rebinding defense, CVE-2025-66414) Origin <value> is not in the allowlist or Host <value> is not in the allowlist
405 GET or DELETE on /mcp (stateless mode) Method not allowed in stateless mode. Use POST /mcp.
413 Request body > 256 KB Express default 413

The allowlists for 403 are env-configurable for self-hosters via FOURA_MCP_ALLOWED_HOSTS and FOURA_MCP_ALLOWED_ORIGINS.

Retry strategy

Four buckets:

  • Wait and retry: rate_limited, at_capacity, service_unavailable, upstream_error. Honour retryAfter when present. Use exponential backoff with jitter when it is absent.
  • Preserve scope and retry later: no_eligible_proxy. Do not remove exitCountries or substitute another country silently. Change or widen the allowlist only when the user explicitly changes the requirement.
  • Do not retry until the input or credential is fixed: bad_request, auth_failed, not_found, ssrf_blocked. For auth_failed, verify the FourA API key, not target-site credentials.
  • Switch tool when the content requires it: forbidden on foura_single can justify a bounded foura_proxy attempt. Use foura_browser when the desired content needs JavaScript. After a successful proxy selection, pass the returned proxy ID into foura_browser.proxy instead of starting a new selection.

Retry example (TypeScript, MCP-side)

async function callWithRetry(call: () => Promise<any>, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const r = await call();
    if (!r.isError) return r;

    const code = r.structuredContent?.code;
    const wait = r.structuredContent?.retryAfter ?? Math.min(2 ** attempt, 30);

    if (["rate_limited", "at_capacity", "service_unavailable", "upstream_error"].includes(code)) {
      await new Promise((res) => setTimeout(res, wait * 1000));
      continue;
    }
    // Non-retryable, surface to caller
    throw new Error(`${code}: ${r.structuredContent?.error}`);
  }
  throw new Error("max retries exceeded");
}
  • MCP Server, the four tools and their schemas
  • MCP Recipes, workflow prompts shipped with the server
  • API Errors, same envelope at the underlying REST API layer
Last updated: July 13, 2026