Перейти до змісту

Обробка помилок

Машинний переклад

Цю сторінку перекладено автоматично з англомовної документації, і основною версією є англомовна сторінка. Якщо щось читається неправильно, на сторінці Переклади пояснено, як про це повідомити.

Інструмент може завершитися невдачею трьома способами, і SDK обробляє кожен по-різному.

Викиньте ToolError — і ваше повідомлення побачить модель. Викиньте MCPError — і його побачить протокол. Викиньте будь-що інше — і це збій: модель дізнається лише, що виклик не вдався, а трасування потрапить у ваш лог.

Ця сторінка — про те, як вибрати.

Помилка, яку модель може виправити

Візьмімо інструмент, який щось шукає, і нехай пошук нічого не знайде:

server.py
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.

server.py
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 дослівно, не очищуючи його.

Будь-який інший виняток

Тепер приберіть перевірку й дайте пошуку в словнику завершитися невдачею самому по собі:

server.py
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 і озивається, щойно щось справді зламалося.

Ресурс, якого не існує

Ресурси проводять ту саму межу й мають один іменований виняток для типового випадку.

server.py
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. Перевіряйте результат через assert. Якщо потрібне трасування збою, воно в лозі сервера, і 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, з якими ви найімовірніше зіткнетеся, що кожна з них означає і як виправити кожну одним рухом, — на сторінці Усунення несправностей.