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.
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
Four buckets:
- Wait and retry:
rate_limited,at_capacity,service_unavailable,upstream_error. HonourretryAfterwhen present. Use exponential backoff with jitter when it is absent. - Preserve scope and retry later:
no_eligible_proxy. Do not removeexitCountriesor 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. Forauth_failed, verify the FourA API key, not target-site credentials. - Switch tool when the content requires it:
forbiddenonfoura_singlecan justify a boundedfoura_proxyattempt. Usefoura_browserwhen the desired content needs JavaScript. After a successful proxy selection, pass the returnedproxyID intofoura_browser.proxyinstead 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");
}
Related
- 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