MCP Fusion/Protocol and runtime/Errores

Errores

Pregunta a la IA sobre Vinkius

El contrato de errores del wire: el sobre XML de tool_error, los códigos de error canónicos, las severidades de warning, los mensajes de validación autoreparables y el ErrorBuilder fluido.

Un error en un servidor MCP es un mensaje para un modelo de lenguaje, no un stack trace. MCP Fusion serializa cada fallo en un sobre XML estructurado con el que el agente puede actuar. Esta página es la especificación de ese sobre.

El sobre tool_error

xml
<tool_error code="NOT_FOUND" severity="error">
  <message>No invoice with that ID</message>
  <recovery>List invoices first, then retry with a real ID</recovery>
  <available_actions>
    <action>billing.list_invoices</action>
  </available_actions>
  <details>
    <detail key="requestedId">inv_2026_9999</detail>
  </details>
  <retry_after>30 seconds</retry_after>
</tool_error>

Lo construye toolError(code, { message, suggestion, availableActions, details, retryAfter }). message es obligatorio; el resto son secciones opcionales que solo se emiten si están definidas. El contenido se escapa en XML solo en & y <, a propósito: > y las comillas siguen siendo legibles para el modelo, que es el único consumidor.

La regla de severidad

SeveridadisErrorSignificado
errortruela acción falló
criticaltruefalló y no debe reintentarse a ciegas
warningfalsela orientación viaja por el camino de éxito

warning es el interesante: una respuesta puede llevar orientación <tool_error> y aun así ser isError: false, así que el modelo recibe la advertencia sin la señal de fallo (parámetro deprecated usado, fallback aplicado, resultado parcial truncado).

Los códigos canónicos

MISSING_DISCRIMINATOR, UNKNOWN_ACTION, VALIDATION_ERROR, MISSING_REQUIRED_FIELD, INTERNAL_ERROR, RATE_LIMITED, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, TIMEOUT, SERVER_BUSY, DEPRECATED, AUTH_REQUIRED, HANDOFF_UPSTREAM_UNAVAILABLE, HANDOFF_NAMESPACE_MISMATCH, HANDOFF_CONNECTING. El tipo es cerrado-y-luego-abierto: el autocomplete muestra estos, pero se permiten códigos personalizados. RATE_LIMITED trae retry_after en segundos; SERVER_BUSY viene del load shedding del concurrency guard, con guía sobre la cola.

Errores de validación escritos para sanarse

Un rechazo de Zod no pasa en crudo. ValidationErrorFormatter emite:

xml
<validation_error action="billing.get_invoice">
  <field name="id">Invalid email format. You sent: 'not-an-email'. Expected a valid email address.</field>
  <recovery>Fix the fields above and call the tool again. Do not explain the error.</recovery>
</validation_error>

Cada problema se convierte en una línea con lo que se envió y lo que se esperaba, afinada por código de issue de Zod (invalid_format, too_small, unrecognized_keys...). La instrucción final, "Do not explain the error", existe porque el instinto del modelo es disculparse ante el usuario en lugar de volver a llamar la tool.

Deltas de contrato encima

Con attachToServer({ selfHealing }) configurado, un error de validación además incorpora una sección <contract_awareness>: los deltas BREAKING y RISKY del último ContractDiff contra el lockfile conocido-bueno, acotados a la action que falló y limitados (cinco por defecto). El agente que aprendió el contrato antiguo recibe exactamente qué cambió, en el mismo turno de error. Ver Governance.

Lanzar o retornar

Los handlers pueden return toolError(...) o throw toolError(...): los throws de ToolResponse con la marca atraviesan el pipeline intactos, así que el código, la severidad y las secciones sobreviven. Un throw sin marca (una excepción real de tu código o de una dependencia) se envuelve como INTERNAL_ERROR con una recuperación "do not blindly retry". El middleware sigue la misma regla.

El builder fluido

f.error('NOT_FOUND', 'Invoice missing') devuelve un builder encadenable: .suggest(), .actions('billing.list_invoices'), .severity('warning'), .details({ requestedId }), .retryAfter(30). Hace duck-typing de ToolResponse (el getter de contenido construye de forma perezosa), así que return f.error(...) funciona directamente en los handlers sin .build().

Del lado del cliente

El cliente tipado vuelve a parsear este sobre: MCPFusionClientError lleva code, recovery, availableActions, severity y el objeto raw parseado, cuando pasas throwOnError: true. Mira Runtime architecture para ver dónde ocurre el envoltorio.

Próximos pasos