MCP Fusion/Protocol and runtime/Fehler
Fehler
Der Fehlervertrag des Wire: das tool_error-XML-Envelope, die kanonischen Fehlercodes, die Warning-Schweregrade, die selbstheilenden Validierungsmeldungen und der fluide ErrorBuilder.
Ein Fehler in einem MCP-Server ist eine Nachricht an ein Sprachmodell, kein Stacktrace. MCP Fusion serialisiert jedes Versagen in ein strukturiertes XML-Envelope, mit dem der Agent handeln kann. Diese Seite ist die Spezifikation dieses Envelopes.
Das tool_error-Envelope
<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>Erzeugt von toolError(code, { message, suggestion, availableActions, details, retryAfter }). message ist Pflicht; der Rest sind optionale Sektionen, die nur gesetzt ausgegeben werden. Der Inhalt wird bewusst nur bei & und < XML-escaped: > und Anführungszeichen bleiben für das Modell lesbar, den einzigen Konsumenten.
Die Schweregrad-Regel
| Schweregrad | isError | Bedeutung |
|---|---|---|
error | true | die Aktion ist fehlgeschlagen |
critical | true | fehlgeschlagen und darf nicht blind wiederholt werden |
warning | false | Guidance reist auf dem Erfolgspfad |
warning ist der interessante Fall: eine Antwort kann <tool_error>-Guidance tragen und trotzdem isError: false sein, sodass das Modell den Hinweis ohne das Failure-Signal bekommt (veralteter Parameter verwendet, Fallback angewandt, Teilergebnis abgeschnitten).
Die kanonischen Codes
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. Der Typ ist erst-closed-dann-open: Autocomplete zeigt diese Codes, eigene Codes sind erlaubt. RATE_LIMITED trägt retry_after in Sekunden; SERVER_BUSY kommt vom Load-Shedding des Concurrency Guards, mit Hinweis zur Queue.
Validierungsfehler, geschrieben zum Heilen
Eine Zod-Ablehnung wird nicht roh durchgereicht. ValidationErrorFormatter gibt aus:
<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>Jedes Problem wird zu einer Zeile mit dem Gesendeten und dem Erwarteten, abgestimmt auf den Zod-Issue-Code (invalid_format, too_small, unrecognized_keys...). Die abschließende Anweisung "Do not explain the error" existiert, weil das Modell instinktiv dazu neigt, sich beim Nutzer zu entschuldigen, statt die tool erneut aufzurufen.
Contract-Deltas obendrauf
Mit konfiguriertem attachToServer({ selfHealing }) bettet ein Validierungsfehler zusätzlich einen <contract_awareness>-Abschnitt ein: die BREAKING- und RISKY-Deltas des letzten ContractDiff gegen den bekannten guten Lockfile, beschränkt auf die fehlgeschlagene Aktion und gekappt (standardmäßig fünf). Der Agent, der den alten Vertrag gelernt hat, erfährt genau, was sich geändert hat, im selben Fehler-Turn. Siehe Governance.
Werfen oder zurückgeben
Handler können return toolError(...) oder throw toolError(...): gebrandete ToolResponse-Throws passieren die Pipeline unverändert, sodass Code, Schweregrad und Sektionen bestehen bleiben. Ein unbrandeter Throw (eine echte Exception aus Ihrem Code oder einer Dependency) wird als INTERNAL_ERROR mit der Recovery "do not blindly retry" verpackt. Middleware folgt derselben Regel.
Der fluide Builder
f.error('NOT_FOUND', 'Invoice missing') gibt einen chainbaren Builder zurück: .suggest(), .actions('billing.list_invoices'), .severity('warning'), .details({ requestedId }), .retryAfter(30). Er duck-typed ToolResponse (der Content-Getter baut lazy), sodass return f.error(...) in Handlers direkt ohne .build() funktioniert.
Auf der Client-Seite
Der typisierte Client parst dieses Envelope zurück: MCPFusionClientError trägt code, recovery, availableActions, severity und das geparste raw-Objekt, wenn Sie throwOnError: true übergeben. Wo das Verpacken passiert, sehen Sie in Runtime architecture.
Nächste Schritte
- Wire format: das Envelope der Erfolgsseite
- Security pipeline: welcher Guard welchen Code ausgibt
- Testing: Assertions auf Fehler-Formen
