Errores del servidor MCP

Cómo manejar los errores devueltos por el servidor foura-mcp.

Cada respuesta de error de cualquiera de las tres herramientas (foura_single, foura_proxy, foura_browser) está estructurada. Los agentes LLM pueden leer el campo code para la lógica de reintento sin necesidad de analizar prosa.

Estructura del contenedor

Cada error (isError: true) contiene un bloque structuredContent. Campos mínimos en cada error:

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

En caso de errores de upstream con estado HTTP, status también está presente. En errores de rate limit y de capacidad, el contenedor añade retryAfter, current.{concurrency, rpm} y limits.{maxConcurrency, maxRpm}, con la misma estructura que los errores de la API REST subyacentes.

Valores de code estables

Code HTTP Significado ¿Seguro para reintentar?
ssrf_blocked n/a IP de destino en un rango privado o reservado (RFC 5735, RFC 6598, IPv6 reservado) No, cambie la URL
upstream_non_json varía El upstream devolvió un cuerpo que no era un JSON válido Quizás, investigar
output_validation_failed n/a El outputSchema del servidor MCP rechazó la respuesta del upstream (error del servidor o estructura inesperada del upstream) Quizás, reportar
bad_request 400 Estructura de entrada rechazada por la API de FourA No, corrija los argumentos
auth_failed 401 Clave faltante, no válida o desactivada No, corrija la clave
forbidden 403 El destino rechazó la request (anti-bot, bloqueo geográfico) No, o cambie a foura_proxy
not_found 404 La URL o el endpoint de destino no existen No
rate_limited 429 Límite de RPM por clave alcanzado Sí, espere retryAfter segundos
at_capacity 503 Límite de concurrencia alcanzado (current.concurrency > limits.maxConcurrency) Sí, espere retryAfter segundos
service_disabled 503 Servicio deshabilitado para su cuenta (plan o mantenimiento) Contacte al soporte técnico
service_unavailable 503 503 genérico del upstream Sí, retroceso corto
upstream_error 500+ 5xx del upstream Sí, retroceso exponencial
upstream_client_error 4xx Otro 4xx no cubierto anteriormente Normalmente no
upstream_unknown otro Defensivo, no debería ocurrir en la práctica Investigar

Errores a nivel HTTP del servidor MCP

Algunos fallos ocurren en la capa de transporte de MCP, antes de llamar a cualquier herramienta. Estos devuelven errores JSON-RPC sin procesar (sin structuredContent):

HTTP Cuándo Lo que ve
400 Header MCP-Protocol-Version no soportado Unsupported MCP-Protocol-Version: <value>. Supported: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07.
401 Header Authorization faltante o malformado JSON-RPC error + WWW-Authenticate: Bearer realm="foura-mcp", resource_metadata="https://foura.ai/docs/mcp/server#auth"
403 Header Origin o Host no permitido (defensa contra DNS-rebinding, CVE-2025-66414) Origin <value> is not in the allowlist o Host <value> is not in the allowlist
405 GET o DELETE en /mcp (modo sin estado) Method not allowed in stateless mode. Use POST /mcp.
413 Cuerpo de la request > 256 KB 413 predeterminado de Express

Las listas de permitidos para 403 son configurables mediante variables de entorno para quienes realizan autohospedaje a través de FOURA_MCP_ALLOWED_HOSTS y FOURA_MCP_ALLOWED_ORIGINS.

Estrategia de reintento

Tres grupos:

  • Esperar + reintentar: rate_limited, at_capacity, service_unavailable, upstream_error. Respete retryAfter cuando esté presente (indicación del servidor, en segundos). Utilice retroceso exponencial con jitter cuando no lo esté.
  • No reintentar, corregir entrada: bad_request, auth_failed, not_found, ssrf_blocked.
  • Cambiar de herramienta: forbidden en foura_single → escalar a foura_proxy. Si la página también necesita JavaScript, foura_browser. Si foura_browser reporta forbidden, encadénelo primero con foura_proxy y pase el ID de proxy devuelto a foura_browser.proxy.

Ejemplo de reintento (TypeScript, lado de MCP)

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");
}

Relacionado

Actualizado: 1 de julio de 2026