--- 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`. Перевіряйте результат через assert. Цей підхід описано на сторінці **[Тестування](../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)**.