--- translation: sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] tool: 1 --- # Обработка ошибок {#handling-errors} Инструмент может завершиться неудачей двумя способами, и SDK обрабатывает их совершенно по-разному. Выбросьте обычное исключение — и его увидит **модель**. Выбросьте `MCPError` — и его увидит **протокол**. Эта страница о том, как выбрать. ## Ошибка, которую модель может исправить {#an-error-the-model-can-fix} Возьмём инструмент, который что-то ищет, и пусть поиск ничего не найдёт: ```python title="server.py" hl_lines="11-12" --8<-- "docs_src/handling_errors/tutorial001.py" ``` В этих двух строках нет ничего специфичного для MCP. `get_author` выбрасывает обычный `ValueError`, как любая функция на Python. Вызовите его с названием, которого нет в каталоге, и посмотрите на результат: ```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`. Сигналом служит флаг. ## Ошибка, которую модель исправить не может {#an-error-the-model-cannot-fix} Теперь замените `ValueError` на `MCPError`. ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" ``` `MCPError` — это **ошибка протокола** в SDK. Это единственное исключение, которое обёртка инструмента *не* перехватывает: оно проходит дальше, и весь запрос `tools/call` завершается ошибкой JSON-RPC вместо результата. ```json { "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` это однозначно хуже — о чём и следующий раздел. ## Что выбрасывать {#which-one-to-raise} Два пути отвечают на два разных вопроса. * **Выбрасывайте любое исключение** при сбое *выполнения*: то, что инструмент пытался сделать, не получилось. Вызов выбрала модель, значит, модель и должна увидеть последствия и получить шанс исправиться. Опечатка в названии, тайм-аут внешнего API, несуществующая строка в таблице — всё это ошибки инструмента. * **Выбрасывайте `MCPError`**, когда отклонить нужно *сам запрос*: у клиента нет возможности, от которой зависит инструмент, сервер не в состоянии обслуживать кого бы то ни было, вызывающая сторона пропустила обязательный шаг. Никакая повторная попытка модели ничего из этого не исправит, так что передавать ей сообщение бессмысленно. Решает один вопрос: **могла бы более умная модель этого избежать?** Да -> обычное исключение. Нет -> `MCPError`. По этому критерию вторая версия `get_author` выбрала неверно: правильное название всё исправляет, значит, модель заслуживала увидеть сообщение. Она здесь, чтобы показать механизм, а не чтобы его рекомендовать. !!! info `MCPError` импортируется как `from mcp import MCPError` и принимает `code`, `message` и необязательную полезную нагрузку `data`. Что бы вы в них ни положили, именно это и получит клиент: SDK передаёт выброшенный `MCPError` дословно, не очищая его. ## Ресурс, которого не существует {#a-resource-that-doesnt-exist} Ресурсы проводят ту же границу и для частого случая поставляются с одним именованным исключением. ```python title="server.py" hl_lines="2 13" --8<-- "docs_src/handling_errors/tutorial003.py" ``` `books://{title}` — это **шаблон**. Он совпадает с *любым* названием, поэтому «URI корректен» и «книга существует» — два разных вопроса, и на второй может ответить только ваша функция. Когда ответить она не может, выбрасывайте `ResourceNotFoundError`. SDK превращает его в ошибку протокола, которую спецификация назначает отсутствующему ресурсу: `-32602` с запрошенным URI в `data`, чтобы клиент знал, *какое именно* чтение не удалось. ```json { "code": -32602, "message": "No book titled 'Nothing' in the catalog.", "data": {"uri": "books://Nothing"} } ``` Обратите внимание: здесь нет полурезультата с `is_error=True`. Чтение ресурса либо возвращает содержимое, либо завершается ошибкой — у ресурсов есть только протокольный путь. Шаблоны и всё остальное о ресурсах — на странице **[Ресурсы](resources.md)**. ## Ошибки, которые вы никогда не выбрасываете {#errors-you-never-raise} Некорректный аргумент никогда не доходит до вашей функции. Передайте `get_author` значение `title`, которое не является строкой, и SDK отклонит его по входной схеме **до** вызова функции — в виде такой же ошибки инструмента с `is_error=True`, которую модель может прочитать и исправить. На странице **[Инструменты](tools.md)** показано такое же отклонение с ограничением `Field(le=50)`. Это целый класс операторов `raise`, которые писать не нужно: не проверяйте повторно собственные аннотации типов. !!! info Всё на этой странице — это то, что видит **клиент**, и `Client` в памяти, с которым вы будете писать тесты, видит ровно то же самое. Даже `raise_exceptions=True` не превращает ошибку инструмента обратно в трассировку: к моменту, когда этот флаг мог бы сработать, ваше исключение уже стало результатом с `is_error=True`. Проверяйте результат. Этот приём описан на странице **[Тестирование](../get-started/testing.md)**. ## Итоги {#recap} * Выбрасываете **любое исключение** в инструменте -> вызов возвращает `is_error=True` с вашим сообщением в `content`. Модель читает его и может повторить попытку. Это поведение по умолчанию. * Выбрасываете **`MCPError`** -> сам вызов завершается ошибкой JSON-RPC. Модель ничего не видит; разбирается хост. `code`, `message` и `data` доходят без изменений. * Решающий вопрос: *могла бы более умная модель этого избежать?* Да -> исключение. Нет -> `MCPError`. * `ResourceNotFoundError` из обработчика ресурса -> протокольный `-32602` с URI в `data`. * Некорректные аргументы отклоняются по схеме до запуска вашей функции; `raise` для них не нужен. * `from mcp import MCPError`; константы кодов ошибок — из `mcp.types`. С ошибками разобрались. Это всё, что сервер *предоставляет*. Что каждый обработчик может прочитать и что сделать в сторону клиента во время выполнения — в следующем разделе: **[Внутри обработчика](../handlers/index.md)**. Точный текст ошибок SDK, которые встретятся чаще всего, смысл каждой и исправление в одно действие — на странице **[Устранение неполадок](../troubleshooting.md)**.