Обработка ошибок
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
Инструмент может завершиться неудачей двумя способами, и 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."""
if title not in CATALOG:
raise ValueError(f"No book titled {title!r} in the catalog.")
return CATALOG[title]
В этих двух строках нет ничего специфичного для MCP. get_author выбрасывает обычный ValueError, как любая функция на Python.
Вызовите его с названием, которого нет в каталоге, и посмотрите на результат:
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 и получили агента, который исправляет себя сам.
Tip
Никогда не возвращайте сообщение об ошибке из инструмента через return. У возвращённой строки
is_error=False, поэтому для модели (и для любого клиентского интерфейса) всё выглядит так, будто
инструмент сработал, а эта строка и есть ответ. Используйте raise. Сигналом служит флаг.
Ошибка, которую модель исправить не может
Теперь замените ValueError на 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 это однозначно хуже — о чём и следующий раздел.
Что выбрасывать
Два пути отвечают на два разных вопроса.
- Выбрасывайте любое исключение при сбое выполнения: то, что инструмент пытался сделать, не получилось. Вызов выбрала модель, значит, модель и должна увидеть последствия и получить шанс исправиться. Опечатка в названии, тайм-аут внешнего API, несуществующая строка в таблице — всё это ошибки инструмента.
- Выбрасывайте
MCPError, когда отклонить нужно сам запрос: у клиента нет возможности, от которой зависит инструмент, сервер не в состоянии обслуживать кого бы то ни было, вызывающая сторона пропустила обязательный шаг. Никакая повторная попытка модели ничего из этого не исправит, так что передавать ей сообщение бессмысленно.
Решает один вопрос: могла бы более умная модель этого избежать? Да -> обычное исключение. Нет -> MCPError.
По этому критерию вторая версия get_author выбрала неверно: правильное название всё исправляет, значит, модель заслуживала увидеть сообщение. Она здесь, чтобы показать механизм, а не чтобы его рекомендовать.
Info
MCPError импортируется как from mcp import MCPError и принимает code, message и необязательную
полезную нагрузку data. Что бы вы в них ни положили, именно это и получит клиент: SDK передаёт
выброшенный MCPError дословно, не очищая его.
Ресурс, которого не существует
Ресурсы проводят ту же границу и для частого случая поставляются с одним именованным исключением.
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. Чтение ресурса либо возвращает содержимое, либо завершается ошибкой — у ресурсов есть только протокольный путь. Шаблоны и всё остальное о ресурсах — на странице Ресурсы.
Ошибки, которые вы никогда не выбрасываете
Некорректный аргумент никогда не доходит до вашей функции.
Передайте get_author значение title, которое не является строкой, и SDK отклонит его по входной схеме до вызова функции — в виде такой же ошибки инструмента с is_error=True, которую модель может прочитать и исправить. На странице Инструменты показано такое же отклонение с ограничением Field(le=50).
Это целый класс операторов raise, которые писать не нужно: не проверяйте повторно собственные аннотации типов.
Info
Всё на этой странице — это то, что видит клиент, и Client в памяти, с которым вы будете
писать тесты, видит ровно то же самое. Даже raise_exceptions=True не превращает ошибку инструмента
обратно в трассировку: к моменту, когда этот флаг мог бы сработать, ваше исключение уже стало
результатом с is_error=True. Проверяйте результат. Этот приём описан на странице Тестирование.
Итоги
- Выбрасываете любое исключение в инструменте -> вызов возвращает
is_error=Trueс вашим сообщением вcontent. Модель читает его и может повторить попытку. Это поведение по умолчанию. - Выбрасываете
MCPError-> сам вызов завершается ошибкой JSON-RPC. Модель ничего не видит; разбирается хост.code,messageиdataдоходят без изменений. - Решающий вопрос: могла бы более умная модель этого избежать? Да -> исключение. Нет ->
MCPError. ResourceNotFoundErrorиз обработчика ресурса -> протокольный-32602с URI вdata.- Некорректные аргументы отклоняются по схеме до запуска вашей функции;
raiseдля них не нужен. from mcp import MCPError; константы кодов ошибок — изmcp.types.
С ошибками разобрались. Это всё, что сервер предоставляет. Что каждый обработчик может прочитать и что сделать в сторону клиента во время выполнения — в следующем разделе: Внутри обработчика.
Точный текст ошибок SDK, которые встретятся чаще всего, смысл каждой и исправление в одно действие — на странице Устранение неполадок.