MCP Server Errors
How to handle errors returned by the foura-mcp server.
Every error response from any of the three tools (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": "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 | Key missing, invalid, or deactivated | No, fix the 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 |
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
Three buckets:
- Wait + retry:
rate_limited,at_capacity,service_unavailable,upstream_error. HonourretryAfterwhen present (server hint, seconds). Use exponential backoff with jitter when not. - Don't retry, fix input:
bad_request,auth_failed,not_found,ssrf_blocked. - Switch tool:
forbiddenonfoura_single→ escalate tofoura_proxy. If the page also needs JavaScript,foura_browser. Iffoura_browserreportsforbidden, chain withfoura_proxyfirst and pass the returnedproxyID intofoura_browser.proxy.
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 three tools and their schemas
- MCP Recipes, workflow prompts shipped with the server
- API Errors, same envelope at the underlying REST API layer