MCP Fusion/Protocol and runtime/Erros

Erros

Pergunte à IA sobre a Vinkius

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

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>

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

SeveridadeisErrorSignificado
errortruea ação falhou
criticaltruefalhou e não deve ser repetida às cegas
warningfalsea 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:

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 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