Перейти к содержимому
Экосистема AI Vibe
← Назад к AI Vibe News

Редакция 19 августа 2026 г.

Разборы

Три отказа MCP-инструмента в Claude Code: ошибка, зависание и пустой успех

Три отказа MCP-инструмента в Claude Code: ошибка, зависание и пустой успех

Соло-разработчик, который подключает MCP-серверы в Claude Code, редко задумывается о том, как агент переживает сбой инструмента — пока сессия не уходит в бесконечное ожидание или модель уверенно строит план на пустом ответе. В разборе @rulestack разложил три режима отказа MCP и показал, что настройки .mcp.json и формулировки isError напрямую определяют, сколько ходов агент потратит впустую.

Всё началось с короткой записи в Bluesky: «an MCP tool that errors teaches the model something, one that hangs teaches it nothing». Ответ в ленте добавил третий кейс — инструмент, который возвращает успешный, но пустой результат: текущий ход не сгорает, зато «сжигает следующие шесть», потому что модель продолжает рассуждать поверх ответа, которого по сути не было.

Явная ошибка — единственный честный канал обратной связи

В спецификации MCP, как пересказывает автор, два разных канала сбоя. Protocol errors — JSON-RPC-ошибки вроде неизвестного инструмента или невалидных аргументов: «сломанный вызов», его читает клиентская обвязка. Tool execution errors — сбой внутри результата с isError: true: API упал, данные невалидны, бизнес-логика отказала; вызов формально прошёл, работа — нет. Этот текст модель видит в диалоге.

Поле content при isError: true — единственная телеметрия, которую агент получает от инструмента. Сообщение "Date must be YYYY-MM-DD, got '19/08/2026'" учит модель формату лучше, чем безликое "Error 500". Текст ошибки стоит писать для модели, а не для человеческого лога: что упало, почему, как выглядит валидный retry.

Честный execution error дешевле зависания и пустого успеха — агент хотя бы знает, где сломалось.

Зависание: стек таймеров Claude Code

Второй режим — зависание. Здесь решают не только ваш код, но и таймеры Claude Code и запись сервера в .mcp.json.

Механизм Поведение
Старт сервера Ограничен MCP_TIMEOUT — например, MCP_TIMEOUT=10000 claude даёт 10 секунд на подъём
Вызов инструмента Поле timeout (мс) в .mcp.json — пример "timeout": 600000 (10 минут); переопределяет MCP_TOOL_TIMEOUT для этого сервера; значения < 1000 игнорируются
Дефолт без настроек MCP_TOOL_TIMEOUT по умолчанию — около 28 часов, не секунд
Idle timeout Вызов без ответа и без progress notification прерывается: 5 мин для HTTP/SSE/WebSocket, 30 мин для stdio (Claude Code v2.1.187+, stdio с v2.1.203); настройка CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT (мс), 0 отключает
Долгий вызов в чате После 2 минут уходит в background task — «висит 10 минут» ≠ «сессия заблокирована 10 минут»

Уведомления о прогрессе сбрасывают idle-таймер, но не продлевают лимит timeout по wall-clock. Для легитимно медленной работы автор рекомендует progress notifications; при риске зависания — быстрый отказ, потому что явная ошибка обходится дешевле молчаливого ожидания.

MCP_TIMEOUT=10000 claude
"timeout": 600000

Пустой успех: ловушка на несколько ходов вперёд

Третий режим — самый коварный для агентного цикла. Инструмент возвращает корректный успешный результат без содержимого: [], {"results": []}, пустая строка — без isError, без таймаута. Модель не отличает «искал и ничего не нашёл» от «не смог искать» и верит пустому ответу.

В примерах автора сбой всплывает через 3–6 ходов — дубликат пользователя, перезапись конфига — далеко от сервера, который молча вернул пустоту. Этот режим хуже явной ошибки и зависания: те хотя бы помечают место поломки.

Что менять на стороне MCP-сервера:

  • Вместо голого [] — явное описание: сколько строк совпало, сколько всего в таблице.
  • Три состояния: найдено N; ничего не найдено (запрос точно выполнен); запрос не выполнен — последнее через isError: true.
  • Fallback при сбое не маскировать под успех: «lookup defaults to 0» превращает ошибку в ложный ноль.

Чеклист для тех, кто пишет MCP-инструменты

Общий принцип: строка результата — единственная телеметрия модели. Шесть практических пунктов:

  1. Execution failures → isError: true + что / почему / как retry.
  2. Protocol-level проблемы → protocol errors, не прятать в «успешный» результат.
  3. Медленная работа → progress notifications; не полагаться на дефолт ~28 ч.
  4. Реалистичный per-server timeout в .mcp.json.
  5. Пустые результаты с объяснением запроса и смысла «пусто»; не голый [].
  6. Без тихих fallback на failure path.

Для соло-разработчика с MCP в Claude Code это не абстрактная гигиена API, а способ не тратить половину сессии на отладку агента, который честно поверил пустому ответу.

Источники