В Дне 16 ты понял, что такое MCP концептуально. Сегодня — внутрь: как именно инструмент попадает в контекст модели и от чего зависит, будет ли модель им пользоваться правильно. Это поверхностно технический урок, но за ним — главный навык: писать описания инструментов.
Инструмент в контексте модели — это не код. Это описание на естественном языке: имя, краткое описание, схема параметров. Модель ничего не знает про внутренности инструмента — она видит только его «вывеску». Поэтому качество описания — это всё. Плохо описанный инструмент = модель его не использует, или использует не так. Хорошее описание — это уже промпт-инжиниринг, просто маленького масштаба.
Когда MCP-клиент подключается к серверу, он получает список инструментов. Каждый инструмент описан тремя полями:
name) — короткий идентификатор: send_email, list_filesdescription) — текст в свободной форме о том, что делает инструментinputSchema) — JSON Schema, описывающая аргументыЭти три поля собираются в стандартный формат tool calling и попадают в system prompt модели. Это всё, что модель «знает» про инструмент. Не больше, не меньше.
Вот как это выглядит в реальности — пример описания одного инструмента из MCP-сервера для GitHub:
Это и всё. Модель видит описание и схему — и на их основе решает: стоит ли вызвать этот инструмент сейчас, и с какими параметрами.
Описание инструмента — это микропромпт. Не «техническая документация». Каждое слово в нём попадёт в контекст модели и будет на неё влиять.
Хорошее описание инструмента отвечает на четыре вопроса. Не обязательно в этом порядке, но все четыре должны быть покрыты:
Действие, описанное глаголом + объектом. Не «утилита для PR», а «создаёт pull request». Чем короче и точнее — тем лучше.
Сигналы из запроса пользователя, по которым модель должна выбрать именно этот инструмент. «Когда нужно X». Без этого модель путается между похожими инструментами.
Опционально, но мощно. «Не используй для X — для этого есть Y». Помогает модели не выбирать инструмент в неподходящих случаях.
Какого вида результат модель получит. Это меняет, как она будет планировать следующие шаги. «Вернёт ID созданного PR» vs «вернёт ошибку, если PR уже существует».
JSON Schema у параметров — это не просто типы. Это дополнительный канал, чтобы объяснить модели нюансы каждого параметра. Что туда стоит класть:
"title": "string", а "title": "string, заголовок PR, 5-100 символов, в повелительном наклонении""example": "fix: handle null in user input". Особенно полезно для нетривиальных форматовenum). Модель учитывает эти ограниченияrequired и осмысленные дефолтыЧем больше осмысленных подсказок в схеме — тем меньше модель «галлюцинирует» неподходящие аргументы.
Возьмём один и тот же концептуальный инструмент — «послать email» — и сравним два описания:
«Endpoint POST /api/v2/emails с авторизацией Bearer. Принимает payload в формате RFC-5321...». Это полезно человеку, который пишет код вокруг инструмента. Модели — бесполезно: ей не нужно знать про endpoint, она не делает прямой HTTP-запрос.
Описание для модели должно быть о смысле: что делает, когда применять, что вернёт. Технические детали оставляй разработчику в README.
«manage_users: создаёт, редактирует, удаляет пользователей. Параметр action: create/update/delete». Модели плохо с такими «инструментами-комбайнами». Они путаются в значениях action, забывают передать нужные параметры для конкретной операции.
Лучше три отдельных инструмента: create_user, update_user, delete_user. Каждый с чёткой схемой только для нужных параметров. Тренд в индустрии: больше инструментов с узкими задачами, меньше «универсалов».
В описании написано «отправляет email», а на самом деле — добавляет письмо в очередь, которая иногда падает с retry, иногда нет, и через час сообщает по webhook. Модель планирует следующие шаги, исходя из «отправлено», а на самом деле могут быть задержки.
Описание должно отражать реальное поведение: «добавляет письмо в очередь отправки. Письмо обычно доставляется в течение 5 минут. При успехе ставит в очередь возвращает task_id».
process, handle, execute, run — это не имена инструментов, это слова без значения. Когда у тебя в контексте 10 инструментов и один из них называется process — модель часто его вызывает «на всякий случай», потому что слово подходит под любую задачу.
Имя должно быть специфичным: process_payment, handle_refund_request, execute_sql_query. Чем уникальнее имя — тем точнее модель его использует.
Когда стоит выбор «manage_files с операциями vs отдельные read_file, write_file, delete_file» — почти всегда выигрывают маленькие.
Маленькие инструменты дороже в плане числа токенов (больше описаний в контексте) — но сильно лучше в плане точности (модель выбирает конкретный, не путается в action). А точность — это то, что определяет, работает агент или нет.
Исключение: когда у тебя десятки похожих операций (например, «получить пользователя по 20 разным фильтрам»). Тогда комбайн с параметром filter может быть оправдан. Но это редкие кейсы.
Стандартный паттерн: все инструменты описаны в system prompt при старте задачи. Это просто и работает.
Альтернативный паттерн — динамическое включение: показывать модели только те инструменты, которые релевантны текущему шагу. На первом шаге — общие, на следующих — после понимания задачи. Это сэкономит токены и снизит «шум выбора».
Динамика стоит того, когда у тебя больше 15-20 инструментов. Для маленького набора — overkill.
«Имя send_email очевидное, описание ему не нужно». Модель понимает по имени примерно, но не точно. send_email — это «отправить любое письмо»? «отправить из этой системы»? «отправить от моего имени или от системы»? Без описания — гадание.
Имя задаёт категорию, описание уточняет контракт. Оба нужны.
Инструмент «прочитать файл» возвращает 50KB сырого текста. Это сразу падает в контекст модели и съедает токены. На следующих итерациях этот блок продолжает «весить».
Возвращаемся к Дням 8-10 (управление контекстом): что инструмент кладёт в контекст — это часть его дизайна. Большие результаты нужно сжимать, обрезать, давать структурированно. Иначе плохой контекст-инженерия.
Инструмент при ошибке возвращает {"error": true} или, ещё хуже, бросает исключение. Модель не понимает, что произошло, что попробовать дальше.
Ошибки — это тоже сообщения модели. Хорошая ошибка: {"error": "rate_limit", "message": "Превышен лимит 100 запросов в минуту. Попробуй через минуту или сократи количество вызовов"}. Модель сможет принять решение: подождать, выбрать другой путь, отказаться от подхода.
Зайди в репозиторий любого готового MCP-сервера (github.com/modelcontextprotocol/servers — официальный набор). Открой код одного из инструментов. Посмотри, как там описано: какое имя, какое description, какие поля в схеме, какие там описания. Сравни хороший пример (GitHub MCP) с менее удачным (какой-нибудь экспериментальный). Это лучше любой теории показывает, что работает.
Возьми твою идею AI-агента. Выбери один инструмент, который ему точно нужен. Распиши его в трёх полях: name, description, schema. По описанию — пройди по четырём вопросам (что/когда/когда нет/что вернёт). По схеме — добавь description к каждому параметру, не только тип. Это упражнение прокачивает важнейший микро-навык: умение писать описание, которое работает.
В Claude Desktop включи Developer Tools (есть в настройках). Подключи любой MCP-сервер. Открой DevTools и найди логи MCP-взаимодействия. Ты увидишь, какой именно JSON был отправлен модели, как описаны инструменты, какие параметры были переданы при вызове. Это даёт тебе физическое ощущение того, что именно «видит» модель — а это половина работы AI-инженера в работе с MCP.