Domínio 2 — Tool Design & MCP Integration · Lição 3 de 6

Erros estruturados em tool_result e MCP

Objetivos de aprendizagem

Sinalizando erro: is_error (API) e isError (MCP)

Na Claude API, o resultado de uma ferramenta que falhou volta como tool_result com is_error: true e uma mensagem informativa — assim o modelo reconhece a falha e tenta outra abordagem ou pede esclarecimento:

{
  "type": "tool_result",
  "tool_use_id": "toolu_01ABC",
  "content": "Error: cidade 'xyz' nao encontrada. Informe um nome de cidade valido.",
  "is_error": true
}

No MCP, o padrão equivalente é o flag isError no resultado da tool — a falha volta como dado para o agente raciocinar, em vez de explodir como exceção de protocolo:

{
  "content": [
    {
      "type": "text",
      "text": "{\"errorCategory\": \"transient\", \"isRetryable\": true, \"message\": \"Upstream API timeout apos 10s\"}"
    }
  ],
  "isError": true
}

Categorias de erro e retryability

CategoriaExemplosRetryable?Ação esperada do agente
transientTimeout, serviço indisponível, 429/529Sim (com backoff)Repetir; se persistir, tentar alternativa
validationEntrada inválida, formato erradoNão (igual); sim com entrada corrigidaCorrigir o input e chamar de novo
businessViolação de política/regra de negócio (ex.: reembolso acima do limite)Não (retriable: false)Comunicar ao usuário com explicação amigável; escalar
permissionSem autorização para o recursoNãoNão repetir; escalar ou pedir credencial

Um erro genérico "Operation failed" esconde a categoria — o agente não sabe se deve repetir, corrigir o input, desistir ou escalar, e desperdiça turnos em retries inúteis. Metadados estruturados (errorCategory, isRetryable, descrição legível) tornam a decisão de recuperação trivial e evitam retries em erros não-retryable.

Para violações de regra de negócio, inclua retriable: false e uma explicação amigável para o cliente final — o agente pode então comunicar a recusa adequadamente em vez de tentar de novo.

Falha de acesso ≠ resultado vazio

Um erro comum de design: retornar erro quando a consulta funcionou mas não encontrou nada — ou pior, mascarar uma falha de acesso como lista vazia. São situações distintas:

Capturar um timeout e devolver lista vazia marcada como sucesso é o pior dos mundos: suprime o erro, impede qualquer recuperação e produz relatórios silenciosamente incompletos.

Recuperação local vs propagação ao coordenador

Em sistemas multi-agente, o subagente deve resolver localmente o que consegue e propagar com contexto o que não consegue:

flowchart TD
    T[Falha na ferramenta] --> C{Categoria?}
    C -- transient --> R[Retry local com backoff]
    R -- resolveu --> OK[Continua a tarefa]
    R -- esgotou tentativas --> P[Propaga ao coordenador:\ntipo, query tentada,\nresultados parciais, alternativas]
    C -- validation --> F[Corrige input e rechama]
    C -- business / permission --> N[Não repete:\ncomunica / escala]
  

Construindo o tool_result estruturado em Python

import json
import anthropic

client = anthropic.Anthropic()

def build_tool_result(tool_use_id, outcome):
    """Converte o resultado da execucao em um tool_result honesto."""
    if outcome["status"] == "ok" and not outcome["rows"]:
        # Resultado vazio VALIDO: sucesso, sem is_error
        return {
            "type": "tool_result",
            "tool_use_id": tool_use_id,
            "content": json.dumps({
                "results": [],
                "note": "Consulta executada com sucesso: 0 registros no periodo.",
            }),
        }
    if outcome["status"] == "ok":
        return {
            "type": "tool_result",
            "tool_use_id": tool_use_id,
            "content": json.dumps({"results": outcome["rows"]}),
        }
    # Falha: erro estruturado com categoria e retryability
    return {
        "type": "tool_result",
        "tool_use_id": tool_use_id,
        "is_error": True,
        "content": json.dumps({
            "errorCategory": outcome["category"],   # transient | validation | business | permission
            "isRetryable": outcome["category"] == "transient",
            "message": outcome["message"],
            "attempted": outcome.get("attempted"),  # o que foi tentado
            "partialResults": outcome.get("partial", []),
        }),
    }

# Exemplo: timeout transitorio com resultados parciais
result_block = build_tool_result("toolu_01XYZ", {
    "status": "error",
    "category": "transient",
    "message": "Upstream timeout apos 10s na pagina 3 de 5.",
    "attempted": "GET /orders?page=3",
    "partial": [{"order_id": "ORD-1"}, {"order_id": "ORD-2"}],
})

followup = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    thinking={"type": "adaptive"},
    messages=[
        {"role": "user", "content": "Liste meus pedidos do trimestre."},
        {"role": "assistant", "content": [{
            "type": "tool_use", "id": "toolu_01XYZ",
            "name": "list_orders", "input": {"period": "2026-Q3"},
        }]},
        {"role": "user", "content": [result_block]},
    ],
)
print(followup.content[0])

Pegadinhas da prova

Resumo em 5 linhas

  1. Falha de ferramenta = tool_result com is_error: true (API) ou isError: true (MCP), sempre com mensagem informativa.
  2. Categorize: transient (retry com backoff), validation (corrigir input), business (retriable: false + explicação amigável), permission (não repetir, escalar).
  3. "Operation failed" genérico impede decisões de recuperação; metadados estruturados (errorCategory, isRetryable) evitam retries desperdiçados.
  4. Resultado vazio válido é sucesso explícito (results: []), nunca erro — e falha de acesso nunca deve virar lista vazia "de sucesso".
  5. Subagente recupera transientes localmente; o que não resolver, propaga ao coordenador com tipo da falha, o que foi tentado, resultados parciais e alternativas.

Documentação oficial

Questões de fixação

1. Como uma tool MCP deve comunicar uma falha de execução ao agente?

2. Logs mostram o agente repetindo 5 vezes uma chamada que sempre retorna "Operation failed" — o erro real é falta de permissão no recurso. Qual mudança resolve o desperdício de retries?

3. Uma ferramenta de busca de pedidos executa com sucesso, mas não há pedidos no período consultado. Qual é o retorno correto?

4. O subagente de busca esgota os retries locais de um timeout. O que ele deve devolver ao coordenador?