Обработка ошибок
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
Инструмент может завершиться неудачей тремя способами, и SDK обрабатывает каждый из них по-своему.
Выбросьте ToolError — и ваше сообщение увидит модель. Выбросьте MCPError — и его увидит протокол. Выбросьте что-то другое — и это уже сбой: модель узнает только, что вызов не удался, а трассировка попадёт в ваш лог.
Эта страница о том, как выбрать.
Ошибка, которую модель может исправить
Возьмём инструмент, который что-то ищет, и пусть поиск ничего не найдёт:
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 из mcp.server.mcpserver.exceptions — это способ, которым инструмент сообщает модели, что что-то пошло не так.
Вызовите его с названием, которого нет в каталоге, и посмотрите на результат:
result.is_error # True
result.content # [TextContent(text="Error executing tool get_author: No book titled 'Nothing' in the catalog.")]
result.structured_content # None
- Запрос выполнен успешно. Результат есть; на вызывающей стороне ничего не выброшено.
is_errorравенTrue, а ваше сообщение (с префиксом в виде имени инструмента) лежит вcontent— ровно там, где читает модель.structured_contentравенNone. У неудачного вызова нет возвращаемого значения, которое можно было бы структурировать.
Это ошибка инструмента, и почти всегда это именно то, что нужно.
Вызывает ваш инструмент именно модель. Она же выбрала аргументы. Поэтому ошибка инструмента — это реплика в диалоге: модель читает «No book titled 'Nothing' in the catalog.», понимает, что ошиблась с названием, и вызывает инструмент снова с более подходящим. Вы написали один raise и получили агента, который исправляет себя сам.
На сервере ToolError — это одна строка уровня INFO в логе, без трассировки. Вы её предвидели, так что расследовать нечего.
Tip
Никогда не возвращайте сообщение об ошибке из инструмента через return. У возвращённой строки
is_error=False, поэтому для модели (и для любого клиентского интерфейса) всё выглядит так, будто
инструмент сработал, а эта строка и есть ответ. Используйте raise. Сигналом служит флаг.
Ошибка, которую модель исправить не может
Теперь замените ToolError на 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 — это ошибка протокола в SDK. Это единственное исключение, которое обёртка инструмента не перехватывает: оно проходит дальше, и весь запрос tools/call завершается ошибкой JSON-RPC вместо результата.
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog."
}
- Результата нет. Ни
content, ниis_error— модели нечего читать. - Вместо этого ошибку получает приложение-хост — так же, как если бы инструмента не существовало вовсе.
code,messageиdataдоходят без изменений.INVALID_PARAMS— это-32602; модульmcp.typesэкспортирует его и остальные коды ошибок JSON-RPC (INVALID_REQUEST,INTERNAL_ERROR, ...) как константы, чтобы никогда не приходилось набирать магическое число.
Check
Тот же поиск, тот же промах, но теперь вызов на стороне клиента выбрасывает исключение вместо того, чтобы вернуть результат:
mcp.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.
Первая версия передавала модели фразу, на которую та могла отреагировать. Эта не передаёт ничего.
Для get_author это однозначно хуже — о чём и следующий раздел.
Что выбрасывать
Два пути отвечают на два разных вопроса.
- Выбрасывайте
ToolErrorпри сбое выполнения: то, что инструмент пытался сделать, не получилось. Вызов выбрала модель, значит, модель и должна увидеть последствия и получить шанс исправиться. Опечатка в названии, тайм-аут внешнего API, несуществующая строка в таблице — всё это ошибки инструмента. - Выбрасывайте
MCPError, когда отклонить нужно сам запрос: у клиента нет возможности, от которой зависит инструмент, сервер не в состоянии обслуживать кого бы то ни было, вызывающая сторона пропустила обязательный шаг. Никакая повторная попытка модели ничего из этого не исправит, так что передавать ей сообщение бессмысленно.
Решает один вопрос: могла бы более умная модель этого избежать? Да -> ToolError. Нет -> MCPError.
По этому критерию вторая версия get_author выбрала неверно: правильное название всё исправляет, значит, модель заслуживала увидеть сообщение. Она здесь, чтобы показать механизм, а не чтобы его рекомендовать.
Info
MCPError импортируется как from mcp import MCPError и принимает code, message и необязательную
полезную нагрузку data. Что бы вы в них ни положили, именно это и получит клиент: SDK передаёт
выброшенный MCPError дословно, не очищая его.
Любое другое исключение
Теперь уберите проверку и дайте поиску по словарю упасть самому:
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] выбрасывает KeyError. Вы этого не предусмотрели, поэтому SDK считает это сбоем:
result.is_error # True
result.content # [TextContent(text="Error executing tool get_author")]
Вызов по-прежнему возвращает is_error=True, так что модель знает, что он не удался, и может двигаться дальше. Чего она не получает, так это текста исключения: KeyError из вашего кода или гора SQL из драйвера тремя библиотеками ниже могут описывать внутреннее устройство сервера, поэтому за его пределы этот текст не выходит.
Вместо модели его получаете вы. Сервер пишет сбой в лог на уровне ERROR с полной трассировкой — как Tool 'get_author' raised an unexpected exception. Поэтому в эксплуатации лог на уровне WARNING молчит при каждом ToolError и подаёт голос в тот момент, когда что-то действительно сломалось.
Ресурс, которого не существует
Ресурсы проводят ту же границу и для частого случая поставляются с одним именованным исключением.
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} — это шаблон. Он совпадает с любым названием, поэтому «URI корректен» и «книга существует» — два разных вопроса, и на второй может ответить только ваша функция.
Когда ответить она не может, выбрасывайте ResourceNotFoundError. SDK превращает его в ошибку протокола, которую спецификация назначает отсутствующему ресурсу: -32602 с запрошенным URI в data, чтобы клиент знал, какое именно чтение не удалось.
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog.",
"data": {"uri": "books://Nothing"}
}
Обратите внимание: здесь нет полурезультата с is_error=True. Чтение ресурса либо возвращает содержимое, либо завершается ошибкой — у ресурсов есть только протокольный путь. ResourceError — то же самое для сбоя, который не сводится к «не найдено» (-32603, ваше сообщение), и оба оставляют в вашем логе одну строку уровня INFO. Любое другое исключение, кроме MCPError, — это сбой: клиент получает -32603 с указанием одного лишь URI, а трассировка уходит в ваш лог на уровне ERROR. Шаблоны и всё остальное о ресурсах — на странице Ресурсы.
Ошибки, которые вы никогда не выбрасываете
Некорректный аргумент никогда не доходит до вашей функции.
Передайте get_author значение title, которое не является строкой, и SDK отклонит его по входной схеме до вызова функции — в виде такой же ошибки инструмента с is_error=True, которую модель может прочитать и исправить. На странице Инструменты показано такое же отклонение с ограничением Field(le=50).
Это целый класс операторов raise, которые писать не нужно: не проверяйте повторно собственные аннотации типов.
Info
Всё, что на этой странице видит клиент, видит и Client в памяти, с которым вы будете
писать тесты. Даже raise_exceptions=True не возвращает исключение упавшего
инструмента вызывающей стороне: к моменту, когда этот флаг мог бы сработать, ваше исключение уже стало
результатом с is_error=True. Проверяйте результат. Если нужна трассировка сбоя, она в логе
сервера, и caplog из pytest её перехватывает. Этот приём описан на странице Тестирование.
Итоги
- Выбрасываете
ToolErrorв инструменте -> вызов возвращаетis_error=Trueс вашим сообщением вcontent. Модель читает его и может повторить попытку. - Выбрасываете
MCPError-> сам вызов завершается ошибкой JSON-RPC. Модель ничего не видит; разбирается хост.code,messageиdataдоходят без изменений. - Решающий вопрос: могла бы более умная модель этого избежать? Да ->
ToolError. Нет ->MCPError. - Любое другое исключение — это сбой ->
is_error=True, где для модели толькоError executing tool <name>, а для вас — запись уровняERRORс трассировкой. ResourceNotFoundErrorиз обработчика ресурса -> протокольный-32602с URI вdata.- Некорректные аргументы отклоняются по схеме до запуска вашей функции;
raiseдля них не нужен. - Импорты:
from mcp import MCPError,from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError, а константы кодов ошибок — изmcp.types.
С ошибками разобрались. Это всё, что сервер предоставляет. Что каждый обработчик может прочитать и что сделать в сторону клиента во время выполнения — в следующем разделе: Внутри обработчика.
Точный текст ошибок SDK, которые встретятся чаще всего, смысл каждой и исправление в одно действие — на странице Устранение неполадок.