MCP Fusion/Protocol and runtime/Erros
Erros
O contrato de erros do wire: o envelope XML de tool_error, os códigos de erro canônicos, as severidades de warning, as mensagens de validação autocuráveis e o ErrorBuilder fluente.
Um erro em um servidor MCP é uma mensagem para um modelo de linguagem, não um stack trace. O MCP Fusion serializa cada falha em um envelope XML estruturado com o qual o agente pode agir. Esta página é a especificação desse envelope.
O envelope tool_error
<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>Construído por toolError(code, { message, suggestion, availableActions, details, retryAfter }). message é obrigatório; o resto são seções opcionais emitidas só quando definidas. O conteúdo é escapado em XML apenas em & e <, de propósito: > e as aspas continuam legíveis para o modelo, que é o único consumidor.
A regra de severidade
| Severidade | isError | Significado |
|---|---|---|
error | true | a ação falhou |
critical | true | falhou e não deve ser repetida às cegas |
warning | false | a orientação segue pelo caminho de sucesso |
warning é o interessante: uma resposta pode carregar orientação <tool_error> e ainda assim ser isError: false, então o modelo recebe a ressalva sem o sinal de falha (parâmetro deprecated usado, fallback aplicado, resultado parcial truncado).
Os 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. O tipo é fechado-e-depois-aberto: o autocomplete mostra esses códigos, mas códigos customizados são permitidos. RATE_LIMITED carrega retry_after em segundos; SERVER_BUSY vem do load shedding do concurrency guard, com orientação sobre a fila.
Erros de validação escritos para se curar
Uma rejeição do Zod não passa crua. O ValidationErrorFormatter emite:
<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 vira uma linha com o que foi enviado e o que era esperado, ajustada por código de issue do Zod (invalid_format, too_small, unrecognized_keys...). A instrução final, "Do not explain the error", existe porque o instinto do modelo é pedir desculpas ao usuário em vez de chamar a tool de novo.
Deltas de contrato por cima
Com attachToServer({ selfHealing }) configurado, um erro de validação embute também uma seção <contract_awareness>: os deltas BREAKING e RISKY do último ContractDiff contra o lockfile conhecido-bom, escopados na ação que falhou e limitados (cinco por padrão). O agente que aprendeu o contrato antigo recebe exatamente o que mudou, na mesma rodada de erro. Veja Governance.
Lançar ou retornar
Handlers podem return toolError(...) ou throw toolError(...): throws de ToolResponse com a marca passam pela pipeline intactos, então o código, a severidade e as seções sobrevivem. Um throw sem marca (uma exceção real do seu código ou de uma dependência) é embrulhado como INTERNAL_ERROR com uma recuperação "do not blindly retry". O middleware segue a mesma regra.
O builder fluente
f.error('NOT_FOUND', 'Invoice missing') retorna um builder encadeável: .suggest(), .actions('billing.list_invoices'), .severity('warning'), .details({ requestedId }), .retryAfter(30). Ele faz duck-typing de ToolResponse (o getter de conteúdo constrói de forma preguiçosa), então return f.error(...) funciona direto nos handlers, sem .build().
No lado do cliente
O cliente tipado interpreta esse envelope de volta: MCPFusionClientError carrega code, recovery, availableActions, severity e o objeto raw parseado, quando você passa throwOnError: true. Veja Runtime architecture para saber onde o embrulho acontece.
Próximos passos
- Wire format: o envelope do lado do sucesso
- Security pipeline: qual guard emite qual código
- Testing: asserções sobre os formatos de erro
