Проектирование схемы MCP-инструментов для надежных действий ассистента
Проектирование схемы MCP-инструментов помогает ассистентам выбирать правильное действие, точнее заполнять аргументы и возвращать результаты, которые командам легко отлаживать и отслеживать.

Почему вызовы инструментов идут не так
Модель не понимает инструмент так, как понимает его человек из вашей команды. Она делает выводы по названию, аргументам и форме ответа. Если сигналы расплывчатые, модель выбирает не то действие, заполняет поля неудачными догадками или вообще пропускает инструмент, который должна была использовать.
Нечеткие схемы быстро создают проблемы. Инструмент под названием manage_customer может означать обновление профиля, блокировку аккаунта, выдачу кредита или получение данных. Даже если описание все объясняет, само название уже направило модель в сторону слишком широкого предположения. А широкие предположения ведут к плохим вызовам.
Похожие названия только ухудшают ситуацию. Если в системе есть get_user, find_user, lookup_customer и fetch_account, модели приходится различать мелкие нюансы по слабым формулировкам. Людям тоже непросто с таким справляться. Модели ошибаются еще чаще, потому что у них нет контекста вашей команды.
Та же проблема появляется и в аргументах. Если инструмент просит id, модели нужно понять, какой именно ID имеется в виду. ID пользователя, заказа или сессии? Иногда вызов срабатывает просто по удаче. В других случаях система находит не ту запись, запускает повторную попытку или падает с ошибкой, из которой никто не понимает, что произошло на самом деле.
Слабые возвращаемые значения создают второй слой проблем. Ответ вида {"success": true} говорит модели очень мало и почти не помогает тем, кто разбирает инциденты. Поддержке все равно приходится спрашивать, что именно изменилось, какая запись изменилась, кто это одобрил и почему предыдущий вызов не сработал. Когда ответ скрывает такие детали, любой инцидент затягивается.
Команды обычно расплачиваются за плохой дизайн схемы в одних и тех же местах: ассистент чаще делает повторы, поддержка дольше разбирает инциденты, логи становятся шумными, а задачи зависают, пока не вмешается человек. Маленькая проблема с названием может превратиться в реальную операционную проблему. Один расплывчатый вызов инструмента может породить лишние логи, две или три повторные попытки и тикет в поддержку без понятной причины.
Чистые схемы уменьшают количество догадок еще до того, как что-то сломается. А если ошибка все же случится, они оставят понятный след.
Что должна делать хорошая схема
Хорошая схема снижает путаницу еще до первого вызова модели. Если название инструмента, входные данные и выходные данные читаются как простая инструкция, модель меньше гадает, а оператор меньше времени тратит на разбор логов.
Начинайте с одного инструмента и одной задачи. Инструмент, который ищет, редактирует, одобряет и отправляет уведомления, провоцирует плохие вызовы, потому что модели приходится выбирать между несколькими действиями внутри одного интерфейса. Узкие инструменты работают лучше. Модель быстрее выбирает нужный, а человеку проще понять, что произошло, не продираясь через стену контекста.
Входные данные должны быть очевидны с первого взгляда. Обязательные поля нуждаются в понятных названиях, простых описаниях и простых типах. customer_id, refund_amount_cents и reason_code использовать проще, чем расплывчатые поля вроде data, payload или details. Если поле важно, сделайте это явным. Не прячьте важный выбор в свободный текст, если достаточно короткого перечисления или булева значения.
Возвращаемые значения тоже должны быть предсказуемыми. Когда каждый инструмент отвечает примерно в одинаковой форме, операторы быстрее просматривают результаты и раньше замечают сбои. Хороший ответ обычно включает итоговый статус, основной результат, понятное сообщение об ошибке, если что-то пошло не так, и request или trace ID для дальнейшего поиска.
Такая стабильность помогает всем. Модель понимает, чего ждать от каждого инструмента. Операторы могут сравнивать вызовы рядом. Логирование и тестирование требуют меньше ручной настройки. Ошибки проще классифицировать и исправлять.
Читаемость важнее изобретательности. Короткие названия полей хороши, если они остаются точными. Описания должны сразу отвечать на три простых вопроса: что нужно передать, что вернется и что может пойти не так.
Это особенно важно в небольших командах, которые каждый день опираются на автоматизацию. Oleg Sotnikov часто работает именно в такой среде, где одна неясная схема может съесть реальное время у поддержки, инженеров и дежурных. Когда схема остается понятной под давлением, модель делает меньше ошибок, а человек на дежурстве понимает проблему за считаные секунды.
Называйте инструменты так, чтобы смысл был очевиден
Название инструмента должно подсказывать модели, что произойдет, еще до чтения схемы. Если название расплывчатое, модель сама достраивает смысл и обычно ошибается хотя бы в чем-то. Люди делают то же самое, когда читают логи в 2 часа ночи.
Начинайте с простого глагола и простого объекта. Названия вроде get_customer, create_invoice и cancel_subscription оставляют меньше пространства для догадок, чем customer_manager или process_task. Модель видит действие. Оператор тоже его видит.
Сопоставляйте название с реальным действием, а не с внутренним жаргоном команды. Если инструмент только отправляет запрос на возврат на проверку, назовите его create_refund_request, а не approve_refund. Эта маленькая разница важна. Обманчивое название ведет к неправильным вызовам, неверным ожиданиям и путаным аудит-следам.
Также полезно разделять чтение, создание, обновление и удаление на отдельные инструменты. Один широкий инструмент вроде manage_user выглядит гибким, но перекладывает слишком много размышлений на модель. Отдельные инструменты делают смысл видимым и снижают шанс ошибочного вызова. На практике названия вроде get_user, list_users, create_user, update_user_role и delete_user легко различать и потом легко отлаживать.
Делайте похожие инструменты по-настоящему разными. get_invoice и list_invoices — понятные названия. fetch_invoice_data, retrieve_invoice и load_invoice_info сливаются друг с другом, и модель начинает гадать.
Точные названия также упрощают просмотр логов с ошибками. В продакшене понятные названия инструментов сильно снижают шум, потому что сразу видно, выбрал ли ассистент не то действие или передал не те данные. Исправить это намного проще, чем разбирать лог, полный вызовов handle_request.
Если название инструмента требует длинного объяснения, значит, оно, скорее всего, делает слишком мало. Переименуйте его так, чтобы новый коллега мог угадать действие с первого взгляда.
Формируйте аргументы так, чтобы модель меньше гадала
Большинство плохих вызовов инструментов начинается еще до того, как модель успевает написать хотя бы один токен. Если схема оставляет пространство для трактовки, модель заполняет пробел догадкой.
Используйте короткие названия полей, но у каждого поля должен быть только один смысл. user_id — понятно. user, target или ref — уже нет. Если в одно поле можно положить имя, email или внутренний ID, модель рано или поздно отправит не то.
Разделяйте ID и понятные человеку названия. Если инструменту нужны оба значения, просите их в разных полях, например project_id и project_name. Так логи проще читать, а бэкенд может доверять идентификатору, не пытаясь распарсить отображаемое имя.
Всегда указывайте единицы в названии поля. Модель не должна гадать, означает ли timeout миллисекунды, секунды или минуты. timeout_seconds, amount_usd и file_size_bytes выглядят чуть длиннее, но не дают мелким ошибкам превращаться в дорогие.
С необязательными полями нужна дисциплина. Если поле меняет смысл действия, сделайте его обязательным. Оставляйте необязательные поля только для реальных исключений, например dry_run или notes, а не для данных, которые инструмент не может безопасно вывести сам.
Вложенные объекты тоже сбивают модели с толку. Глубокие деревья заставляют модель одновременно следить за структурой, написанием и порядком. Плоские аргументы проще передавать и проще проверять потом. Если вложенность все же нужна, не уходите глубже одного уровня и оставляйте названия объектов простыми.
Хороший пример — инструмент для разворачивания. service_id, environment и timeout_seconds дают модели понятные ячейки, которые нужно заполнить. Один объект deployment, в котором смешаны метки, ID и правила времени, провоцирует ошибки.
Хорошая схема почти скучная. В этом и смысл. Каждый аргумент должен отвечать на один простой вопрос: что нужно инструменту, в каком формате и в каких единицах?
Возвращаемые значения, которые помогают отладке
Результат работы инструмента должен говорить не только «готово» или «ошибка». Форма ответа определяет, как быстро модель сможет восстановиться и как быстро человек найдет проблему. Если инструмент возвращает только success: false, всем приходится гадать, что именно сломалось.
Начинайте с понятного флага успеха. Пусть он будет прямым: true или false. Не прячьте результат в фразах вроде «запрос, возможно, был выполнен» или «операция обработана с замечаниями». Модель читает это расплывчато, а операторы тратят время на логи.
В ответе также нужна простая причина. Одного короткого предложения достаточно. Хорошие формулировки звучат как «customer not found», «approval limit exceeded» или «calendar token expired». Они объясняют ассистенту, что случилось, без необходимости разбирать сырые отладочные данные.
Полезно разделять то, что видит конечный пользователь, и то, что видит команда. Сообщение для клиента может звучать так: «Пока не получилось отправить счет». Текст отладки должен объяснять, что именно не сработало, где это произошло и какой вход вызвал проблему. Держите эти поля раздельно, чтобы ассистент не выносил внутренние детали в чат.
Небольшая форма ответа обычно работает лучше всего: success для итогового результата, reason для краткой причины, user_message для безопасного текста для ассистента, debug_message для внутренних деталей, а также trace_id и error_code для дальнейшего разбора.
Стабильные ID важнее, чем многим кажется. Если каждый вызов инструмента возвращает один и тот же trace_id, который потом виден в логах, очередях и мониторинге, оператор может за секунды перейти от неудачного сообщения в чате к точному событию в бэкенде.
Коды ошибок должны быть короткими и означать только одно. AUTH_EXPIRED — хорошо. REQUEST_ERROR — слишком расплывчато. Как только вы публикуете код, его смысл нужно сохранять неизменным. Если смысл начинает плавать, дашборды и инструкции перестают помогать.
Чистый результат упрощает восстановление после ошибки. Модель понимает, что сказать дальше, а команда понимает, куда смотреть.
Простой способ набросать схему
Хороший дизайн схемы обычно начинается с одного реального запроса, а не с пустой формы. Возьмите что-то, что человек действительно может попросить, например: «Перешлите Марию счет за апрель». Одна такая фраза помогает держать инструмент привязанным к реальной задаче.
Потом задайте простой вопрос: что нужно инструменту, чтобы безопасно выполнить эту задачу? В этом случае, возможно, нужен ID клиента, месяц счета и способ доставки. Скорее всего, не нужны десять необязательных полей, свободный комментарий или оценка приоритета.
Полезен простой процесс наброска. Напишите один реальный пользовательский запрос обычным языком. Выпишите минимальный набор входных данных, которые инструмент не может безопасно вывести сам. Придумайте название инструмента и аргументов до того, как писать описания. Затем протестируйте несколько расплывчатых запросов и посмотрите, где модель ошибается. Если какие-то поля остаются пустыми, получают неверные значения или вообще не меняют результат, убирайте их.
Названия важны с самого начала. Если инструмент называется invoice_resend, у модели есть реальный шанс выбрать его правильно. Если он называется message_dispatch_v2, модели приходится угадывать, что он делает, еще до чтения описаний аргументов.
То же правило относится к аргументам. customer_id лучше, чем target. invoice_month лучше, чем period. Понятные названия снижают риск, что модель заполнит правильную форму неправильным смыслом.
После этого тестируйте черновик на небрежных запросах, а не на идеально вылизанных. Попробуйте: «отправь Марии счет еще раз», «отправь счет за прошлый месяц» или «мне нужен тот счет повторно». Такие формулировки показывают, где схема по-прежнему оставляет слишком много пространства для догадок.
Большинство первых версий слишком большие. Команды добавляют поля на случай, если они когда-нибудь пригодятся. На практике лишние поля создают больше неверных вызовов, больше пустых значений и больше путаницы при проверке. Если модель никогда не использует reason_code или постоянно придумывает delivery_priority, убирайте их.
Лаконичную схему модели проще вызвать, а человеку — проще проверить. Обычно это лучший обмен.
Пример: инструмент для одобрения возврата
Расплывчатое название инструмента провоцирует неверные догадки. Если инструмент называется refund, модели приходится угадывать, нужно ли ей проверять право на возврат, считать комиссии, одобрять выплату или отправлять деньги. Эта догадка и превращается в ошибки. approve_refund уже точнее. Оно говорит модели об одном действии и подсказывает операторам, что именно инструмент пытался сделать, когда они читают логи.
{ "tool": "refund", "arguments": { "amount": 49.99, "order": "A1842" } }
В этом вызове слишком много места для ошибки. amount — это доллары или центы? order — это ID заказа, платежа или счета? Модель может угадать. Сотрудник поддержки тоже может угадать, а это еще хуже, потому что ошибка выглядит правдоподобно.
Более точная версия убирает двусмысленность:
{
"tool": "approve_refund",
"arguments": {
"order_id": "A1842",
"amount_cents": 4999,
"reason": "duplicate charge"
}
}
amount_cents лучше, чем amount, по одной простой причине: никому не нужно гадать про единицы. Одно более точное название поля убирает целый класс частых багов. Вы избегаете ситуации, когда 49.99 превращается в 49, 4999 или округленное число с плавающей точкой после перехода между системами.
Возвращаемое значение должно помогать и модели, и команде поддержки. Одного голого success: false мало. Если возврат отклонен, верните approved: false и объясните почему. Это позволит ассистенту сообщить результат, не придумывая причину, а оператору даст конкретную точку для проверки.
{
"approved": false,
"denial_reason": "refund window expired",
"request_id": "req_01HV8K7M6X"
}
request_id важнее, чем многие думают. Когда клиент отвечает через два часа, поддержка может найти этот ID в логах и увидеть точный путь решения. Никому не нужно собирать историю по кусочкам из времени и неполных номеров заказа.
Такой шаблон держит инструмент честным. approved показывает решение. denial_reason объясняет отказ. request_id дает опору для дальнейшего разбора. У каждого поля своя задача.
Ошибки, которые тормозят команды
Большинство проблем со схемой сначала не выглядят серьезно. Они проявляются как небольшие задержки: модель выбирает не тот инструмент, сотрудник поддержки не может понять, почему вызов упал, или старый промпт продолжает отправлять аргументы, которые уже не подходят. Через несколько недель такие мелочи превращаются в реальную потерю времени.
Один из худших паттернов — один инструмент, который пытается делать несколько разных дел. Инструмент под названием manage_refund может отправлять, одобрять, отклонять или отменять возврат в зависимости от поля mode. В спецификации это выглядит аккуратно, но на деле заставляет модель угадывать смысл по слабым сигналам. Обычно чище разделить это на отдельные инструменты. submit_refund_request и approve_refund делают действие очевидным.
Команды также прячут обязательные поля в глубоких объектах. Тогда модели приходится собирать вложенный payload с идеальной структурой, прежде чем что-то заработает. Это хрупко. Если поле важно всегда, держите его ближе к верхнему уровню и дайте ему простое название.
Формы ответа создают не меньше проблем. Один ответ говорит status: ok, другой — approved: true, а третий возвращает целый объект без короткого итога. Промпты, написанные под одну форму, начинают ломаться в другом случае. Хорошие схемы сохраняют стабильную форму ответа даже тогда, когда у самого действия несколько исходов.
Обработка ошибок — еще одна медленная утечка. Ответ, который говорит только failed, почти бесполезен, когда кто-то дежурит. Ошибка должна назвать шаг, на котором произошел сбой, указать на неверный аргумент или отсутствующее предварительное условие, сказать, имеет ли смысл повторная попытка, и вернуть стабильный код ошибки.
Ошибки версионирования только усугубляют ситуацию. Команда переименовывает аргумент, удаляет поле или меняет тип ответа без предупреждения. Старые промпты еще как-то работают, но только настолько, чтобы создавать путаницу. А это часто хуже, чем честный разрыв. Если нужна новая форма, версионируйте ее явно и оставляйте старый контракт живым, пока промпты не переедут.
Короткий чек-лист для проверки
Проверка схемы должна занимать минуты, а не встречу. Перед запуском нового инструмента или изменением существующего пройдитесь по нескольким простым пунктам:
- Попросите нового коллегу прочитать только название инструмента. Если он не может угадать, что инструмент делает, переименуйте его.
- Прочитайте несколько обычных пользовательских запросов и проверьте, сможет ли модель заполнить все обязательные аргументы без догадок.
- Убедитесь, что поддержка может найти один неудачный вызов по одному поиску с названием инструмента, request ID и коротким статусным полем.
- Прочитайте каждое сообщение об ошибке так, будто вы следующий человек на дежурстве. Оно должно объяснять, что сломалось и что делать дальше.
- Тестируйте не только идеальные запросы, но и почти подходящие. Реальные пользователи говорят неидеально.
Одна небольшая привычка очень помогает: храните короткую заметку по каждому инструменту с одним успешным сценарием, двумя небрежными пользовательскими запросами, одной ожидаемой ошибкой и точной формой ответа. Это дает инженерии, поддержке и операторам один и тот же ориентир.
Если инструмент не проходит этот чек-лист, сначала исправьте схему. Не пытайтесь обойти проблему трюками в промпте. Понятные названия, заполняемые аргументы, поискуемые ответы и полезные ошибки делают действия ассистента гораздо надежнее, когда реальные пользователи перестают говорить идеальными фразами.
Что делать дальше
Не начинайте с полной переработки. Начните с тех инструментов, которые уже приносят боль. В большинстве команд два или три инструмента создают основную массу неудачных вызовов, странных повторов и сообщений в поддержку, которые потом приходится час распутывать.
Практический разбор можно уложить в одну короткую сессию, если держать объем небольшим. Возьмите недавние сбои, прочитайте исходный пользовательский запрос, а затем сравните его с названием инструмента, именами аргументов и формой ответа. Обычно проблема становится видна быстро, потому что модель гадала там, где схема оставила неясность.
Хороший первый проход довольно прост. Возьмите три инструмента с наибольшим числом ошибок или с наибольшей путаницей у операторов. Переименуйте одно поле, которое люди постоянно читают неправильно. Добавьте trace_id в каждый ответ, чтобы логи, дашборды и ручная проверка указывали на одно и то же событие. Напишите несколько тестов на небрежные запросы, а не на чистые промпты.
Такая чистка быстро окупается. Более понятное название поля может убрать десятки неверных догадок за неделю. trace_id может сократить время отладки с 20 минут до 2, потому что команда сможет проследить один вызов через вывод модели, запуск инструмента и финальный ответ.
Если вы руководите небольшой командой, дайте одному человеку ответственность за чистку схем и поставьте фиксированный лимит, например пять изменений в неделю. Так работа будет двигаться, но не превратится в побочный проект.
Если вам нужен второй взгляд, Oleg Sotnikov занимается такой работой через oleg.is в рамках Fractional CTO и AI automation advisory. Для небольших команд внешний аудит часто помогает, потому что к путаным названиям привыкают и перестают их замечать.