MCP Fusion/Protocol and runtime/Erreurs

Erreurs

Demandez à l’IA à propos de Vinkius

Le contrat d’erreur du wire : l’enveloppe XML de tool_error, les codes d’erreur canoniques, les sévérités de warning, les messages de validation auto-réparateurs et l’ErrorBuilder fluide.

Une erreur dans un serveur MCP est un message destiné à un modèle de langage, pas une stack trace. MCP Fusion sérialise chaque échec dans une enveloppe XML structurée sur laquelle l’agent peut agir. Cette page est la spécification de cette enveloppe.

L’enveloppe 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>

Construit par toolError(code, { message, suggestion, availableActions, details, retryAfter }). message est obligatoire ; le reste sont des sections optionnelles émises seulement si elles sont définies. Le contenu est échappé en XML uniquement sur & et <, volontairement : > et les guillemets restent lisibles pour le modèle, qui est le seul consommateur.

La règle de sévérité

SévéritéisErrorSignification
errortruel’action a échoué
criticaltrueéchec à ne pas relancer à l’aveugle
warningfalsel’orientation suit le chemin du succès

warning est le plus intéressant : une réponse peut porter des consignes <tool_error> et rester isError: false, si bien que le modèle reçoit la mise en garde sans le signal d’échec (paramètre déprécié utilisé, fallback appliqué, résultat partiel tronqué).

Les codes canoniques

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. Le type est fermé-puis-ouvert : l’autocomplétion affiche ces codes, mais des codes personnalisés sont autorisés. RATE_LIMITED porte retry_after en secondes ; SERVER_BUSY vient du load shedding du concurrency guard, avec des consignes sur la file d’attente.

Des erreurs de validation écrites pour guérir

Un rejet Zod n’est pas transmis brut. ValidationErrorFormatter émet :

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>

Chaque problème devient une ligne indiquant ce qui a été envoyé et ce qui était attendu, ajustée par code d’issue Zod (invalid_format, too_small, unrecognized_keys...). L’instruction finale, "Do not explain the error", existe parce que le réflexe du modèle est de s’excuser auprès de l’utilisateur au lieu de rappeler la tool.

Les deltas de contrat par-dessus

Avec attachToServer({ selfHealing }) configuré, une erreur de validation intègre en plus une section <contract_awareness> : les deltas BREAKING et RISKY du dernier ContractDiff par rapport au lockfile connu-bon, limités à l’action en échec et plafonnés (cinq par défaut). L’agent qui avait appris l’ancien contrat reçoit la description exacte de ce qui a changé, dans le même tour d’erreur. Voir Governance.

Lever ou retourner

Les handlers peuvent return toolError(...) ou throw toolError(...) : les throws de ToolResponse marqués traversent le pipeline intacts, si bien que le code, la sévérité et les sections survivent. Un throw non marqué (une vraie exception de votre code ou d’une dépendance) est enveloppé comme INTERNAL_ERROR avec une récupération "do not blindly retry". Le middleware suit la même règle.

Le builder fluide

f.error('NOT_FOUND', 'Invoice missing') renvoie un builder chaînable : .suggest(), .actions('billing.list_invoices'), .severity('warning'), .details({ requestedId }), .retryAfter(30). Il fait du duck-typing de ToolResponse (le getter de contenu se construit à la demande), donc return f.error(...) fonctionne directement dans les handlers sans .build().

Côté client

Le client typé re-parse cette enveloppe : MCPFusionClientError porte code, recovery, availableActions, severity et l’objet raw parsé, quand vous passez throwOnError: true. Voir Runtime architecture pour savoir où l’enveloppement a lieu.

Prochaines étapes