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. When the platform's shared allowance refuses a call, the envelope adds retryAfter, current.{concurrency, rpm}, and limits.{maxConcurrency, maxRpm}, same shape as the underlying REST API errors.

When one of your plan's own limits refuses a call, the code IS that limit: plan_limit_ followed by credits, bandwidth, rate, concurrency, browser_daily, premium or feature. retryAfter carries the wait where a wait clears it, and is absent for a limit that a wait cannot clear, such as a feature the plan does not include. plan_limit_browser_daily carries no retryAfter either: it clears at midnight UTC.

On foura_auto, a plan limit met inside its ladder comes back as rate_limited or forbidden, with the plan's code in reason.

Stable code values

Code HTTP Meaning Retry safe?
ssrf_blocked n/a The target is a private or reserved address (RFC 5735, RFC 6598, IPv6 reserved), the URL isn't http(s), or its host name didn't resolve No, check the URL. A lookup that failed briefly can be retried
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, or the tool couldn't complete the call at all (no API key configured, API unreachable) Maybe: check the setup, then report
bad_request 400 Input shape rejected by the FourA API, or the target URL points at FourA's own API instead of a page (only /inspect can be fetched there). Nothing is fetched or charged 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 (a site check, a country restriction) No, or switch to foura_proxy
not_found 404 Target URL or endpoint doesn't exist No
rate_limited 429 The platform's shared per-minute allowance, or a 429 from the target that your validate rejected. On foura_auto it can also be your plan's credits, traffic or rate limit (see reason) Yes, wait retryAfter when it's there, otherwise back off
at_capacity 503 Concurrency cap hit (current.concurrency > limits.maxConcurrency) Yes, wait retryAfter seconds
service_disabled 503 The service is switched off for maintenance. A tool your plan doesn't include comes back as plan_limit_feature Contact support
service_unavailable 503 Generic 503 from upstream Yes, short backoff
upstream_error 500+ or 0 The target answered with a server error, or on foura_proxy, foura_browser and foura_auto never answered Yes, exponential backoff
upstream_client_error 4xx Other 4xx not covered above Usually no
upstream_unknown other The request ran but produced no accepted answer: on foura_single the target never answered (timeout, refused connection), and on any tool your validate rejected a 2xx or 3xx response. Read status and error 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
plan_limit_credits 429 The monthly credits on your plan are spent Yes, after retryAfter, or change the plan
plan_limit_bandwidth 429 The traffic allowance on your plan is spent for this billing period Yes, after retryAfter, or change the plan
plan_limit_rate 429 Your plan's requests per minute for that endpoint Yes, after retryAfter
plan_limit_concurrency 429 Your plan's simultaneous requests for that endpoint Yes, after retryAfter
plan_limit_browser_daily 429 Your plan's daily Browser allowance is spent Yes, tomorrow, or use foura_single / foura_proxy
plan_limit_premium 403 You sent exitClass: "premium" on a plan that does not include premium exits No, drop the parameter or change the plan
plan_limit_feature 403 The plan does not carry that endpoint or that capability No, change the plan

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 (from @modelcontextprotocol/sdk): 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07. Upgrade foura-mcp's SDK pin to extend.
401 A tool call or resource read with no API key. Listing tools and prompts works without one JSON-RPC error + WWW-Authenticate: Bearer realm="foura-mcp"
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.

Refused browser profiles

A browser profile that the catalogue cannot present, or a profile sent with unblocker set to false, comes back as an upstream failure with the reason in error and the request never leaves FourA. The message names what is available, so retry with one of the listed combinations rather than the same one.

These are refusals, not outages: retrying the identical request cannot succeed, and no other browser was used in its place.

Retry strategy

Five buckets:

  • Your own plan refused it, not the target: any plan_limit_* code. The same work through another tool is refused as well, so switching endpoints spends nothing but time. Wait out retryAfter where there is one; otherwise the plan has to change. plan_limit_premium is the one you can clear yourself, by dropping exitClass.
  • Wait and retry: rate_limited, at_capacity, service_unavailable, upstream_error. Honour retryAfter when present. Use exponential backoff with jitter when it isn't there. Don't answer it by re-issuing every queued tool call at once: cut how many you run in parallel instead.
  • 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
  • Rate Limits, the account and platform limits behind rate_limited and at_capacity
Last updated: October 7, 2026