Referencia avanzada de manejo de errores y control de generación en el servidor MCP de DevSpeak. Cubre el envelope con success, data y error, el significado de HTTP 400, 401, 403, 429 y 5xx, cómo respetar Retry-After ante límites de tasa, cómo manejar timeouts en generaciones largas, cómo fijar un modelo con managedModel, y cómo solicitar XML con responseFormat.

Maneja Errores, Límites de Tasa y Selección de Modelo en MCP

Toda herramienta MCP de DevSpeak termina fallando en algo — una cuota, un límite de tasa, un timeout. Este tutorial cubre cómo leer esos fallos con precisión y los dos controles de generación que vale la pena conocer: fijación de modelo y salida XML.

Lo que Aprenderás

  • Interpretar el envelope de errores de la API de DevSpeak
  • Asociar cada código HTTP con su causa real
  • Aplicar backoff correctamente ante un 429
  • Fijar un modelo gestionado con managedModel
  • Solicitar XML legible por máquina con responseFormat
  • Requisitos Previos

  • Una conexión MCP funcionando
  • Familiaridad con translate_text y refine_translation
  • Tiempo Estimado

    ~10 minutos

    El Envelope de Respuesta

    Toda respuesta usa una sola forma:

    ``json

    {

    "success": true,

    "data": { "output": "## Especificación Técnica\n\n..." },

    "error": null

    }

    `

    Ante un fallo:

    `json

    {

    "success": false,

    "data": null,

    "error": "Input must be at least 10 characters"

    }

    `

    El servidor MCP expone error como el mensaje de error de la herramienta. Cuando tu asistente reporta un fallo de DevSpeak, esa cadena viene del servidor — no es una paráfrasis del asistente.

    Un caso produce una forma distinta. Si un gateway o el fallback de la SPA responde con HTML en lugar de JSON, obtienes:

    `text

    Malformed response from https://.../api/v1/translate:

    expected JSON, received text/html.

    `

    Eso significa que la petición nunca llegó a la API de DevSpeak — normalmente una URL base incorrecta, o un proxy o gateway respondiendo en su lugar. DevSpeak responde a una ruta /api/* no coincidente con un envelope de error JSON 404, así que revisa la URL base antes que nada.

    Referencia de Códigos de Estado

    | Código | Significado | ¿Reintentar? | Solución |

    | ------ | ----------------------------- | ---------------- | ------------------------------------------------- |

    | 400 | Validación fallida | No | Corrige el parámetro nombrado en error |

    | 401 | Clave inválida o revocada | No | Regenérala en Ajustes de la Cuenta → Credenciales |

    | 403 | El tier no incluye la función | No | Revisa get_account_info, actualiza el plan |

    | 429 | Límite de tasa alcanzado | Sí, tras esperar | Respeta Retry-After |

    | 5xx | Fallo del servidor | Sí, con backoff | Reintenta; muchos se resuelven solos |

    La distinción que importa: 401 significa que falló _quién eres_, 403 significa que falló _qué puedes hacer_. Regenerar una clave no resuelve un 403.

    Límites de Tasa y Backoff

    Las peticiones tienen un límite de tasa por clave de API, y algunos endpoints sensibles tienen además un límite propio más estricto. Superar cualquiera de los dos devuelve un 429.

    Un 429 incluye el tiempo de espera en el encabezado Retry-After, en segundos. El cuerpo JSON indica la misma espera en su mensaje:

    `json

    {

    "success": false,

    "error": "Rate limit exceeded. Retry after <n> seconds."

    }

    `

    Algunas respuestas 429 además repiten la espera como campo retryAfter en el cuerpo. No dependas de ese campo; lee el encabezado.

    Por MCP solo ves la cadena error (el error de la herramienta se construye con ella), así que espera los segundos que indica ese mensaje. Quien llame directamente por REST debe leer el encabezado. No lo adivines, y no reintentes de inmediato:

    `javascript

    async function callWithBackoff(fn, maxAttempts = 3) {

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {

    const response = await fn(); // fn devuelve una Response de fetch

    if (response.status !== 429 || attempt === maxAttempts) return response;

    const waitSeconds = Number(response.headers.get("Retry-After"));

    if (!(waitSeconds > 0)) return response; // sin valor del servidor: devuelve el 429, no adivines

    await new Promise((r) => setTimeout(r, waitSeconds * 1000));

    }

    }

    `

    Solo vale la pena reintentar 429 y 5xx. Reintentar un 400 o un 403 consume cuota para recibir el mismo error.

    Timeouts

    Las generaciones largas pueden agotar el tiempo de espera. Por MCP, una generación lenta aparece como un error del lado del cliente — DevSpeak API request to <url> timed out after <n>s. — sin detalle del servidor, y el servidor aún puede estar trabajando en la petición.

    Un timeout suele deberse a:

  • Entrada cercana al límite de 10.000 caracteres combinada con un tone alto
  • Varios conectores habilitados en un Context Project
  • La solución es reducir trabajo: acorta la entrada, baja el tone o deshabilita conectores que no necesites.

    Fijar un Modelo Gestionado

    Por defecto DevSpeak elige por su cuenta un modelo gestionado para cada petición. Tú no gestionas esto.

    Para fijar un modelo específico, pasa managedModel:

    `json

    {

    "input": "Diseñar una política de reintentos para webhooks.",

    "audience": "Senior Dev",

    "context": "Backend",

    "format": "Technical Spec",

    "tone": 70,

    "managedModel": "<id-del-modelo>"

    }

    `

    En el espacio de traducción, elige un modelo en el control Modelo; muestra etiquetas visibles, no ids. Por MCP o REST, managedModel recibe un id de modelo, y el servidor acepta solo ids de su allowlist vigente — cualquier otro devuelve 400. El conjunto cambia al añadir o retirar modelos, así que cuando no necesites un modelo específico, omite el campo.

    Dos consecuencias que conviene conocer:

    1. El modelo fija su proveedor. Seleccionar un modelo enruta al proveedor que lo posee.

    2. Omite BYOK. Un managedModel explícito anula una credencial propia almacenada. Seleccionar un modelo gestionado nunca enruta en silencio a tu endpoint personalizado.

    La allowlist se aplica del lado del servidor. Un modelo no listado devuelve 400; la petición no se ejecuta en silencio con otro modelo.

    Cuándo fijarlo: reproducibilidad. Si estás comparando salidas o depurando un prompt, una petición sin fijar puede ser atendida por un modelo distinto entre ejecuciones, porque DevSpeak elige por petición. Fuera de eso, déjalo sin definir y deja que DevSpeak elija.

    Solicitar Salida XML

    responseFormat controla la estructura de salida:

    `json

    {

    "input": "Diseñar una política de reintentos para webhooks.",

    "audience": "Senior Dev",

    "context": "Backend",

    "format": "Technical Spec",

    "tone": 70,

    "responseFormat": "xml"

    }

    `

    | Valor | Salida | Úsalo cuando |

    | ---------- | -------------------------------------- | ---------------------- |

    | markdown | Prosa (por defecto) | Lo lee una persona |

    | xml | Documento validado legible por máquina | Lo consume un programa |

    La salida XML se valida contra un esquema del servidor, lo que la hace segura de parsear programáticamente. Los encabezados Markdown son una convención; la estructura XML es un contrato. Elige xml cuando un script consuma el resultado — entregárselo a una persona desperdicia la garantía y se lee peor.

    Ten presente el alcance: webSearch, responseFormat y managedModel están soportados en el servidor MCP y en la app web. La CLI y la extensión de VS Code excluyen deliberadamente estas banderas.

    Punto de Control

    En este punto deberías poder:

  • [ ] Explicar la diferencia entre 401 y 403
  • [ ] Aplicar backoff usando Retry-After en lugar de adivinar
  • [ ] Decir qué le hace managedModel a una credencial BYOK almacenada
  • [ ] Elegir entre markdown y xml de forma deliberada
  • Solución de Problemas

    optimize_text rechaza format, tone o customInstructions

    El esquema de la herramienta optimize_text es estricto, así que estos parámetros fallan la validación antes de enviar cualquier petición, y sabes que nunca aplicaron. Llamado directamente por REST, /api/v1/optimize en cambio descarta los campos no soportados y los registra, sin devolver un 400.

    429 repetidos pese a aplicar backoff

    El límite por clave se comparte entre todos los clientes que la usan. Un job de CI y tu editor sobre una misma clave compiten entre sí. Emite claves separadas.

    En el plan FREE, las traducciones con los modelos propios de DevSpeak también cuentan para un límite por hora. Ese 429 lo indica en error, y su Retry-After apunta al inicio de la próxima hora, así que un backoff corto solo acumula más. Espera al reinicio, conecta tu propia clave de proveedor (a la que no aplica el límite) o mejora tu plan.

    La calidad de la salida cambió entre ejecuciones idénticas

    Otro modelo atendió la petición — sin fijarlo, DevSpeak elige el modelo por petición. Fija con managedModel cuando necesites reproducibilidad.

    400 en un nombre de modelo que parece válido

    El modelo no está en la allowlist del servidor — puede ser una etiqueta del selector en lugar de un id, o un modelo que ya se retiró. Omite managedModel para que DevSpeak elija, o elige el modelo en el control Modelo del espacio de traducción.

    Resumen

    Aprendiste a:

  • Interpretar el envelope de errores de DevSpeak y su modo de fallo en HTML
  • Asociar códigos de estado con causas y decidir qué es reintentable
  • Aplicar backoff usando el Retry-After` del propio servidor
  • Fijar un modelo gestionado y entender su anulación de BYOK
  • Solicitar XML validado para consumo programático
  • Siguientes Pasos

  • [Fundamenta Traducciones MCP en un Context Project](/tutorials/mcp/ground-translations-on-a-context-project)
  • [Automatiza Especificaciones con la CLI de DevSpeak](/tutorials/api-and-cli/automate-with-the-cli)