python-sdk/i18n/ru/pages/servers/handling-errors.md

13 KiB
Raw Blame History

translation
sections tool
e33d441f12d50535
7099694c603e0f5f
c1df4cf9673433e6
c9cd294541422e6e
6cec073617bfd037
efa92b8f99e908c8
6a22a29e27fb4601
1

Обработка ошибок

Инструмент может завершиться неудачей двумя способами, и SDK обрабатывает их совершенно по-разному.

Выбросьте обычное исключение — и его увидит модель. Выбросьте MCPError — и его увидит протокол.

Эта страница о том, как выбрать.

Ошибка, которую модель может исправить

Возьмём инструмент, который что-то ищет, и пусть поиск ничего не найдёт:

--8<-- "docs_src/handling_errors/tutorial001.py"

В этих двух строках нет ничего специфичного для 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.

--8<-- "docs_src/handling_errors/tutorial002.py"

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 Тот же поиск, тот же промах, но теперь вызов на стороне клиента выбрасывает исключение вместо того, чтобы вернуть результат:

```text
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 дословно, не очищая его.

Ресурс, которого не существует

Ресурсы проводят ту же границу и для частого случая поставляются с одним именованным исключением.

--8<-- "docs_src/handling_errors/tutorial003.py"

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, которые встретятся чаще всего, смысл каждой и исправление в одно действие — на странице Устранение неполадок.