No MCP, erro de execução e erro de protocolo são canais diferentes. Se a tool recebeu uma chamada válida, mas não conseguiu encontrar um registro, consultar uma API ou aplicar uma regra de negócio, devolva um resultado com isError: true e uma mensagem que o modelo consiga usar. Se a requisição JSON-RPC está malformada ou o estado do protocolo não permite atendê-la, devolva um erro de protocolo para o host.

Essa escolha responde a uma pergunta simples: quem pode corrigir o problema? O modelo pode ajustar argumentos, escolher outra tool ou informar que um registro não existe. O host precisa corrigir um método, uma versão, uma mensagem JSON-RPC ou a configuração da conexão. Misturar os canais faz o agente tentar novamente algo que o modelo não tem como consertar, ou transforma uma falha recuperável em um erro genérico.

Este artigo complementa o servidor MCP em TypeScript para agentes de código. O tutorial ensina a registrar e conectar tools. Aqui, o foco é o contrato de falha depois que a chamada chega ao servidor.

Diagrama mostra uma chamada de tool no MCP sendo dividida entre um resultado isError visível ao modelo e um erro JSON-RPC visível ao host.

Resposta curta

  • Use isError: true para falhas de execução que o modelo pode entender e corrigir.
  • Use um erro JSON-RPC para problemas da própria requisição ou do estado do protocolo.
  • Escreva a mensagem de isError como uma observação curta: o que falhou e qual próximo passo é válido.
  • Não coloque stack trace, token, argumento sensível ou identificador interno no texto que vai para o modelo.
  • Teste a visibilidade: uma falha de tool deve ser um resultado; um erro de protocolo deve alcançar o host como erro.

isError e erro de protocolo respondem a consumidores diferentes

A especificação do Model Context Protocol, na seção de tratamento de erros, separa duas famílias. Erros de protocolo usam o campo error da resposta JSON-RPC para problemas como requisições malformadas, método desconhecido e falhas do servidor. Erros de execução aparecem dentro do resultado da tool com isError: true; a mensagem pode ajudar o modelo a corrigir os parâmetros ou mudar de caminho.

Falha Canal Quem deve agir Próximo passo típico
Data fora do formato aceito result.isError: true Modelo e runtime Corrigir o argumento e tentar de novo dentro do orçamento
Registro inexistente result.isError: true Modelo Escolher outro identificador ou explicar a ausência
API externa recusou a operação result.isError: true Runtime e modelo Classificar retry, usar alternativa ou parar
JSON-RPC inválido error no JSON-RPC Host MCP Corrigir a mensagem ou o cliente
Método ou versão não suportada error no JSON-RPC Host MCP Ajustar negociação ou configuração
Servidor sem condição de atender error no JSON-RPC ou política do host Host e operação Reestabelecer o servidor antes de continuar

Essa tabela descreve o contrato no fio. SDKs podem normalizar alguns caminhos de forma diferente. A documentação atual do SDK TypeScript do MCP mostra, por exemplo, que uma exceção lançada dentro de um handler de tool vira um resultado com isError: true, enquanto callbacks de recursos e prompts podem lançar ProtocolError para produzir uma resposta JSON-RPC. Leia o comportamento da versão instalada e teste a forma que o seu cliente realmente recebe.

O detalhe importante é a visibilidade. Um resultado com isError: true ainda é uma resposta bem-formada à chamada de tool. O modelo recebe o texto e pode tomar a próxima decisão. Um erro JSON-RPC não tem result; quem recebe a falha é o cliente MCP ou o host que coordena a sessão.

Como escrever uma mensagem de erro que o agente consiga usar?

Uma mensagem model-facing deve conter o fato observável e a correção permitida. Não precisa explicar a exceção inteira. O exemplo da documentação do SDK usa um registro ausente e inclui os identificadores conhecidos para que o modelo possa escolher um valor válido. A mensagem funciona porque descreve uma mudança que o próximo call pode fazer.

Prefira esta forma:

No note with id "drafts". Use one of: welcome, release-notes.

Evite estas duas:

Error: ENOTFOUND db.internal:5432 at QueryRunner.execute (query-runner.ts:81)
The operation failed. Please try again.

A primeira expõe infraestrutura e não diz se repetir pode ajudar. A segunda não dá ao modelo uma observação nova. Uma mensagem útil responde, em poucas palavras, a quatro perguntas: qual tool falhou, qual condição foi observada, o que pode mudar na próxima chamada e quando o agente deve parar.

O texto de erro continua sendo conteúdo não estruturado. Uma issue aberta no repositório do MCP sobre erros de tools discute justamente o risco de deixar stack traces, identificadores internos e dados pessoais atravessarem essa fronteira. A issue é uma discussão de implementadores, não prova de que todo servidor vaza dados. A consequência prática é segura mesmo assim: redija o texto antes de devolvê-lo e trate o conteúdo como entrada para o contexto do modelo.

Como retornar um erro de tool em TypeScript?

O formato abaixo é uma adaptação pequena do resultado documentado pelo SDK TypeScript atual. O tipo é intencionalmente independente do import do SDK para deixar o contrato visível. Os nomes de registro e a função de montagem variam entre versões.

type TextContent = {
  type: "text";
  text: string;
};

type ToolResult = {
  content: TextContent[];
  isError?: true;
};

const notes = new Map([
  ["welcome", "Read tools.md first."],
  ["release-notes", "The latest release is staged."],
]);

export async function readNote(id: string): Promise<ToolResult> {
  const note = notes.get(id);

  if (!note) {
    return {
      content: [{
        type: "text",
        text: `No note with id "${id}". Choose another id.`,
      }],
      isError: true,
    };
  }

  return { content: [{ type: "text", text: note }] };
}

No caminho real, conecte esse resultado ao método de registro de tool do SDK que você instalou. Uma tool com falha conhecida também pode lançar a exceção de tool que o SDK converte para isError: true, mas o retorno explícito dá controle sobre o texto e deixa a política aparente no código. Não presuma que lançar qualquer ProtocolError dentro de um handler produzirá um erro JSON-RPC: a página de erros do SDK TypeScript diz que handlers de tool usam o canal de resultado.

Valide os argumentos antes do efeito externo. Se id precisa ser uma string, o schema de entrada deve rejeitar tipos diferentes. Se a string é válida, mas não corresponde a um registro, a execução chegou à tool e pode devolver uma observação com isError: true. Essa diferença evita que uma validação de transporte e uma ausência de domínio pareçam o mesmo defeito.

Quando o erro JSON-RPC é a escolha certa?

Use o canal de protocolo quando a chamada não representa uma execução de tool válida ou quando o host precisa reparar a sessão. Exemplos são JSON inválido, método não suportado, uma mensagem que não atende ao schema de tools/call e uma versão de protocolo que o servidor não aceita.

Uma resposta de protocolo tem esta forma conceitual:

{
  "jsonrpc": "2.0",
  "id": 7,
  "error": {
    "code": -32602,
    "message": "Invalid tool call request"
  }
}

O número do código precisa seguir o contrato da versão e da biblioteca. Não use -32602 como um rótulo universal para qualquer falha da aplicação. Uma regra de negócio como “não é possível cancelar uma ordem já enviada” pertence ao resultado da tool, mesmo que o runtime use uma exceção internamente para chegar até ele.

O guia de tratamento de erros do SDK Python do MCP mostra a mesma decisão em outra linguagem: uma exceção de tool aparece como is_error=True e o modelo lê a mensagem; uma exceção MCP produz um erro JSON-RPC que o cliente deve tratar. A implementação exata muda, mas a pergunta continua sendo a mesma: a falha está na execução escolhida pelo modelo ou na requisição que o host enviou?

Como testar que o agente recebe a falha certa?

Um teste de erro não deve apenas verificar que alguma exceção ocorreu. Ele precisa provar o formato observado pelo consumidor. A fixture abaixo não abre uma conexão MCP; ela fixa o contrato local para que a camada de integração possa ser testada contra o SDK real.

import assert from "node:assert/strict";

const result = await readNote("missing");

assert.equal(result.isError, true);
assert.match(result.content[0].text, /Choose another id/);
assert.equal("error" in result, false);

const protocolFailure = {
  jsonrpc: "2.0",
  id: 7,
  error: { code: -32602, message: "Invalid tool call request" },
};

assert.equal("result" in protocolFailure, false);
assert.equal(protocolFailure.error.code, -32602);

Em uma suíte que chama o servidor MCP de verdade, acrescente quatro verificações:

  1. Chame uma tool com um argumento de domínio válido, mas inexistente, e confirme isError: true.
  2. Confirme que o texto não contém stack trace, token, caminho absoluto ou payload secreto.
  3. Envie uma requisição JSON-RPC malformada e confirme que o cliente recebe um erro, não um CallToolResult.
  4. Faça uma falha conhecida de upstream e verifique que nenhuma mutação foi executada antes do resultado de erro.

Se o cliente transforma um caso em isError: true quando você esperava uma rejeição JSON-RPC, não corrija o teste para esconder a diferença. Verifique a versão do SDK, o caminho do handler e o wire log. O teste de contratos para servidores MCP em TypeScript cobre a conexão e os schemas; este caso deve entrar como uma extensão focada na semântica de falha.

Como evitar retry infinito depois de isError?

isError: true torna a falha visível ao modelo, mas não decide que ele deve tentar novamente. A política do runtime ainda precisa diferenciar correção de argumento, retry de uma dependência, troca de tool e parada. Um erro de domínio pode ser corrigível uma vez. O mesmo fingerprint repetido várias vezes é um sinal para encerrar ou pedir intervenção.

Não devolva uma mensagem que incentive retry para uma operação que já pode ter produzido um efeito. Se a resposta de uma escrita se perdeu, consulte o estado externo ou use um identificador idempotente antes de expor a ação como repetível. O resultado de erro do MCP deve dizer o que foi observado, não inventar que a mutação não ocorreu.

Também não use isError como controle de acesso. Autorização, allowlist, confirmação humana e escopo do servidor precisam bloquear o efeito antes do handler ou dentro da política do serviço. O canal de erro só informa o resultado da decisão; ele não transforma uma tool perigosa em uma tool segura.

Checklist de tratamento de erros MCP

  • A chamada chegou como uma requisição válida de tools/call?
  • A falha aconteceu durante a execução da tool ou antes dela?
  • O modelo pode corrigir a entrada, escolher outra tool ou usar a observação?
  • O resultado usa isError: true quando a falha é model-facing?
  • O erro JSON-RPC fica reservado para o host reparar a requisição ou o protocolo?
  • A mensagem é curta, acionável e livre de dados sensíveis?
  • O runtime limita retries, registra o motivo e evita repetir uma mutação incerta?
  • O teste verifica a forma recebida pelo modelo e a forma recebida pelo host?
  • O comportamento observado corresponde à versão do SDK instalado?

O ponto não é fazer todo erro chegar ao modelo. É fazer cada consumidor receber a informação que consegue usar. Um resultado com isError: true é uma observação da execução. Um erro JSON-RPC é uma falha do contrato entre cliente e servidor. Quando essa fronteira aparece no código e no teste, o agente deixa de tratar indisponibilidade estrutural como convite para tentar de novo.

Como este artigo foi produzido

Samuel Fajreldines é o autor responsável por este artigo. A pesquisa comparou a especificação do MCP, a documentação atual do SDK TypeScript, o guia de erros do SDK Python e o comportamento documentado de erros visíveis ao modelo em um SDK de agentes. As discussões públicas foram usadas apenas para linguagem, preocupações e ideias de teste. O diagrama é uma SVG autoral, e a fixture de formas de resultado foi verificada localmente com Node.js, sem abrir um servidor MCP, chamar um modelo ou acessar uma API externa. A assistência de IA apoiou descoberta, organização, redação, localização e revisão de consistência. O autor usa o RemoteCode como ferramenta de trabalho, mas ela não executou testes de produção nem forneceu experiência operacional.

Fontes consultadas