Перейти к содержанию

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

Машинный перевод

Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.

Инструмент может завершиться неудачей двумя способами, и 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."""
    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.

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 это однозначно хуже — о чём и следующий раздел.

Что выбрасывать

Два пути отвечают на два разных вопроса.

  • Выбрасывайте любое исключение при сбое выполнения: то, что инструмент пытался сделать, не получилось. Вызов выбрала модель, значит, модель и должна увидеть последствия и получить шанс исправиться. Опечатка в названии, тайм-аут внешнего API, несуществующая строка в таблице — всё это ошибки инструмента.
  • Выбрасывайте MCPError, когда отклонить нужно сам запрос: у клиента нет возможности, от которой зависит инструмент, сервер не в состоянии обслуживать кого бы то ни было, вызывающая сторона пропустила обязательный шаг. Никакая повторная попытка модели ничего из этого не исправит, так что передавать ей сообщение бессмысленно.

Решает один вопрос: могла бы более умная модель этого избежать? Да -> обычное исключение. Нет -> MCPError.

По этому критерию вторая версия get_author выбрала неверно: правильное название всё исправляет, значит, модель заслуживала увидеть сообщение. Она здесь, чтобы показать механизм, а не чтобы его рекомендовать.

Info

MCPError импортируется как from mcp import MCPError и принимает code, message и необязательную полезную нагрузку data. Что бы вы в них ни положили, именно это и получит клиент: SDK передаёт выброшенный MCPError дословно, не очищая его.

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

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

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. Чтение ресурса либо возвращает содержимое, либо завершается ошибкой — у ресурсов есть только протокольный путь. Шаблоны и всё остальное о ресурсах — на странице Ресурсы.

Ошибки, которые вы никогда не выбрасываете

Некорректный аргумент никогда не доходит до вашей функции.

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