Tratando erros
Tradução automática
Esta página foi traduzida automaticamente a partir da documentação em inglês, e a página em inglês é a versão de referência. Se algo parecer errado, Traduções explica como avisar.
Uma ferramenta (tool) pode falhar de três maneiras, e o SDK trata cada uma de forma diferente.
Lance ToolError e é o modelo que vê a sua mensagem. Lance MCPError e é o protocolo que a vê. Lance qualquer outra coisa e é um crash: o modelo só fica sabendo que a chamada falhou, e o seu log recebe o traceback.
Esta página é sobre essa escolha.
Um erro que o modelo consegue corrigir
Pegue uma ferramenta que faz uma consulta e deixe a consulta não encontrar nada:
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
mcp = MCPServer("Bookshop")
CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}
@mcp.tool()
def get_author(title: str) -> str:
"""Look up the author of a book in the catalog."""
if title not in CATALOG:
raise ToolError(f"No book titled {title!r} in the catalog.")
return CATALOG[title]
ToolError, de mcp.server.mcpserver.exceptions, é como uma ferramenta avisa ao modelo que algo deu errado.
Chame a ferramenta com um título que não está no catálogo e veja o resultado:
result.is_error # True
result.content # [TextContent(text="Error executing tool get_author: No book titled 'Nothing' in the catalog.")]
result.structured_content # None
- A requisição foi bem-sucedida. Há um resultado; nada foi lançado no lado de quem chamou.
is_erroréTrue, e a sua mensagem (prefixada com o nome da ferramenta) está emcontent, exatamente onde o modelo lê.structured_contentéNone. Uma chamada que falhou não tem valor de retorno para estruturar.
Isso é um erro de ferramenta, e é quase sempre o que você quer.
Quem chama a sua ferramenta é o modelo. Foi ele que escolheu os argumentos. Então um erro de ferramenta é um turno na conversa: o modelo lê "No book titled 'Nothing' in the catalog.", percebe que chutou o título errado e chama de novo com um melhor. Você escreveu um raise e ganhou um agente que se corrige sozinho.
No servidor, um ToolError é uma linha INFO no log, sem traceback. Você já esperava por ele, então não há nada para investigar.
Tip
Nunca faça return de uma mensagem de erro em uma ferramenta. Uma string retornada tem is_error=False, então, para o
modelo (e para toda interface de cliente), parece que a ferramenta funcionou e que aquela string era a resposta.
Use raise. A flag é o sinal.
Um erro que o modelo não consegue corrigir
Agora troque ToolError por MCPError.
from mcp import MCPError
from mcp.server import MCPServer
from mcp.types import INVALID_PARAMS
mcp = MCPServer("Bookshop")
CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}
@mcp.tool()
def get_author(title: str) -> str:
"""Look up the author of a book in the catalog."""
if title not in CATALOG:
raise MCPError(code=INVALID_PARAMS, message=f"No book titled {title!r} in the catalog.")
return CATALOG[title]
MCPError é o erro de protocolo do SDK. É a única exceção que o wrapper da ferramenta não captura: ela se propaga, e a requisição tools/call inteira falha com um erro JSON-RPC em vez de um resultado.
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog."
}
- Não há resultado. Sem
content, semis_error: nada para o modelo ler. - Quem recebe o erro é a aplicação host, do mesmo jeito que receberia se a ferramenta nem existisse.
code,messageedatachegam intactos.INVALID_PARAMSé-32602;mcp.typesexporta esse e os outros códigos de erro JSON-RPC (INVALID_REQUEST,INTERNAL_ERROR, ...) como constantes, para que você nunca precise digitar um número mágico.
Check
Mesma consulta, mesma falha, mas agora a chamada lança a exceção no lado do cliente em vez de retornar:
mcp.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.
A primeira versão entregou ao modelo uma frase à qual ele podia reagir. Esta não entrega nada.
Para get_author isso é estritamente pior, e é esse o ponto da próxima seção.
Qual delas lançar
Os dois caminhos respondem a duas perguntas diferentes.
- Lance
ToolErrorpara uma falha de execução: aquilo que a sua ferramenta tentou fazer não funcionou. Foi o modelo que escolheu a chamada, então é o modelo que deve ver a consequência e ter a chance de se recuperar. Um título escrito errado, uma API upstream que deu timeout, uma linha que não existe: tudo erro de ferramenta. - Lance
MCPErrorquando a própria requisição deve ser rejeitada: o cliente não tem uma capacidade da qual a sua ferramenta depende, o servidor não está em condições de atender ninguém, quem chamou pulou uma etapa obrigatória. Nenhuma nova tentativa do modelo corrige nada disso, então não há nada a ganhar entregando a mensagem a ele.
Uma pergunta decide: um modelo mais esperto teria evitado isso? Sim -> ToolError. Não -> MCPError.
Por esse critério, a segunda versão de get_author fez a escolha errada: um título melhor resolve, então o modelo merecia ver a mensagem. Ela está ali para mostrar o mecanismo, não para recomendá-lo.
Info
MCPError fica em from mcp import MCPError e recebe code, message e um payload
data opcional. O que você colocar neles é o que o cliente recebe: o SDK repassa um
MCPError lançado tal e qual, em vez de sanitizá-lo.
Qualquer outra exceção
Agora tire a verificação e deixe a consulta ao dicionário falhar sozinha:
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}
@mcp.tool()
def get_author(title: str) -> str:
"""Look up the author of a book in the catalog."""
return CATALOG[title]
CATALOG[title] lança KeyError. Você não planejou isso, então o SDK trata como um crash:
result.is_error # True
result.content # [TextContent(text="Error executing tool get_author")]
A chamada ainda retorna is_error=True, então o modelo sabe que falhou e pode seguir em frente. O que ele não recebe é o texto da exceção: um KeyError do seu código, ou uma pilha de SQL vinda de um driver três bibliotecas abaixo, pode descrever o funcionamento interno do seu servidor, então nunca sai do servidor.
Quem recebe é você. O servidor registra o crash em ERROR com o traceback completo, como Tool 'get_author' raised an unexpected exception. Um log de produção em WARNING, portanto, fica quieto a cada ToolError e se manifesta no instante em que algo está de fato quebrado.
Um recurso que não existe
Recursos fazem a mesma distinção, e vêm com uma exceção nomeada para o caso mais comum.
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ResourceNotFoundError
mcp = MCPServer("Bookshop")
CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}
@mcp.resource("books://{title}")
def book(title: str) -> str:
"""The catalog entry for one book."""
if title not in CATALOG:
raise ResourceNotFoundError(f"No book titled {title!r} in the catalog.")
return f"{title} by {CATALOG[title]}"
books://{title} é um template. Ele casa com qualquer título, então "a URI está bem formada" e "o livro existe" são duas perguntas diferentes, e só a sua função consegue responder à segunda.
Quando não consegue, lance ResourceNotFoundError. O SDK a transforma no erro de protocolo que a especificação atribui a um recurso ausente: -32602 com a URI requisitada em data, para que o cliente saiba qual leitura falhou.
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog.",
"data": {"uri": "books://Nothing"}
}
Repare que aqui não existe um meio-resultado com is_error=True. A leitura de um recurso ou retorna conteúdo ou falha: recursos só têm o caminho do protocolo. ResourceError é a mesma coisa para uma falha que não é "não encontrado" (-32603, com a sua mensagem), e as duas são uma linha INFO no seu log. Qualquer outra exceção, exceto MCPError, é um crash: o cliente recebe -32603 citando apenas a URI, e o traceback vai para o seu log em ERROR. Templates e todo o resto sobre recursos ficam em Recursos.
Erros que você nunca lança
Um argumento inválido nunca chega à sua função.
Mande para get_author um title que não seja uma string e o SDK o rejeita com base no schema de entrada antes de chamar você, como o mesmo tipo de erro de ferramenta com is_error=True que o modelo consegue ler e corrigir. Ferramentas mostra a mesma rejeição com uma restrição Field(le=50).
Isso significa uma classe inteira de instruções raise que você não escreve: não revalide as suas próprias anotações de tipo.
Info
Tudo o que um cliente vê nesta página, o Client em memória com o qual você vai escrever
seus testes também vê. Nem raise_exceptions=True devolve a exceção de uma ferramenta que falhou
a quem chamou: no momento em que essa flag poderia agir, a sua exceção já virou o
resultado com is_error=True. Faça o assert no resultado. Se você precisar do traceback de um crash, ele está no
log do servidor, e o caplog do pytest o captura. Testes cobre o padrão.
Recapitulando
- Lance
ToolErrorem uma ferramenta -> a chamada retornais_error=Truecom a sua mensagem emcontent. O modelo lê e pode tentar de novo. - Lance
MCPError-> a própria chamada falha com um erro JSON-RPC. O modelo não vê nada; quem lida com isso é o host.code,messageedatasobrevivem intactos. - A pergunta que decide: um modelo mais esperto teria evitado isso? Sim ->
ToolError. Não ->MCPError. - Qualquer outra exceção é um crash ->
is_error=Truesó comError executing tool <name>para o modelo, e um registroERRORcom o traceback para você. ResourceNotFoundErrorem um handler de recurso -> o-32602do protocolo, com a URI emdata.- Argumentos inválidos são rejeitados com base no schema antes de a sua função executar; você não dá
raisepara eles. - Imports:
from mcp import MCPError,from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError, e as constantes de código de erro demcp.types.
Erros tratados. Isso é tudo o que um servidor expõe. O que cada handler pode ler, e fazer de volta ao cliente enquanto executa, é a próxima seção: Dentro do seu handler.
O texto exato dos erros do SDK que você tem mais chance de encontrar, o que cada um significa e a correção de um passo só para cada um estão em Solução de problemas.