Перейти к содержимому
RootPoligon

Задача для инструмента

Чтение и практика ~15 мин

Ситуация

Приложение «Заметки» пишет журнал в app/logs/app.log и хранит заметки в app/data/notes/. Утром пользователи жаловались, что заметки не сохраняются. Вы хотите, чтобы агент помогал разбирать такие случаи: читал журнал, искал ошибки, открывал заметки, а в крайнем случае — ротировал журнал.

Самый короткий путь — дать агенту инструмент run_command(cmd: str). Он умеет всё. Именно поэтому он плох: всё, что можно сделать на сервере, теперь зависит от того, какую строку составит модель и насколько внимательно человек её прочитает.

Главная мысль

Инструмент — это контракт между программой и моделью. Его границы записаны в коде сервера, а не в просьбе к модели «будь аккуратна». Модель может ошибиться, прочитать в журнале чужую «инструкцию» или повторить вызов после обрыва связи. Хороший инструмент делает так, что эти ошибки не превращаются в ущерб.

Как устроен MCP

MCP (Model Context Protocol) — протокол, по которому AI-приложение получает инструменты и данные от отдельных программ. Участники:

  • Хост — AI-приложение, с которым работает человек (IDE, чат, ваша платформа). Он решает, какой инструмент вызвать по совету модели, и спрашивает подтверждение у человека.
  • Клиент — часть хоста, которая держит соединение с одним сервером. Один сервер — один клиент.
  • Сервер — программа, которая объявляет инструменты и выполняет их вызовы. Её вы и будете писать.

Слоёв два. Транспорт доставляет сообщения: для локального сервера это stdio — хост запускает сервер как процесс и пишет ему в stdin, читает из stdout. Уровень данных — сообщения JSON-RPC: tools/list (какие есть инструменты), tools/call (вызвать), server/discover (что умеет сервер и какие версии протокола понимает).

В курсе используется ревизия протокола 2026-07-28. В ней MCP не хранит состояния соединения: каждый запрос сам несёт версию протокола и возможности клиента в поле _meta, а рукопожатия initialize больше нет. В статьях и примерах до лета 2026 года вы увидите initialize — это прежние ревизии. SDK понимает и их, но смешивать два способа в одном проекте не нужно.

Кроме инструментов, сервер может отдавать ресурсы (данные по адресу) и подсказки-шаблоны (prompts). В этом курсе — только инструменты: именно они действуют, и именно им нужны границы.

Что входит в контракт инструмента

ЧастьВопросПример для «прочитать хвост журнала»
Имя и описаниеЧто делает и когда его звать?log_tail — последние строки журнала приложения
АргументыЧто можно передать и в каких пределах?lines от 1 до 200; level — один из четырёх уровней
РезультатЧто вернётся и в какой форме?список строк с номером, уровнем, текстом; признак обрезки
ПределыСколько максимум прочитает и вернёт?не больше 256 КиБ с конца файла, строка — до 500 символов
ОтказыКогда честно откажет?журнала нет — понятная ошибка, а не пустой «успех»
ДействиеМеняет ли что-то?только читает

Описание читает модель — оно помогает ей выбрать инструмент. Но ограничения в описании ничего не запрещают. Фраза «не читай файлы вне notes/» в описании — пожелание. Проверка пути в коде — граница.

Разобранный пример

Агенту нужно «посмотреть заметку». Два варианта:

  • run_command("cat app/data/notes/welcome.md") — модель может так же составить cat ~/.ssh/id_ed25519 или cat ../../outside/.... Человек должен заметить это в строке.
  • note_read(name: str) — сервер принимает только относительный путь внутри notes/, раскрывает ссылки и отказывает, если путь ведёт наружу; возвращает не больше 64 КиБ и говорит, если обрезал. Ошибка модели в аргументе приводит к отказу, а не к утечке.

Цена узкого инструмента — его нужно написать и проверить. Это и есть курс.

Самостоятельно

Пользователи жалуются: заметки не сохраняются. Агенту нужно: (1) понять, есть ли ошибки записи в журнале и когда они начались; (2) открыть заметку, которую пользователь пытался сохранить; (3) если журнал разросся — освободить место, не потеряв журнал.

Опишите три инструмента вместо run_command. Для каждого заполните таблицу контракта выше: имя, аргументы с пределами, результат, пределы, отказы, меняет ли что-то. Для третьего (изменяющего) ответьте отдельно:

  • как человек поймёт, что именно будет сделано, до того как это случится;
  • что будет, если хост вызовет его дважды подряд — например, после обрыва связи;
  • что будет, если между «посмотрел» и «сделал» журнал изменился.

Сравните с разбором. Совпадение имён не важно; важно, где проходят границы.

Проверка

Самопроверка — по разбору: у каждого инструмента есть предел объёма и хотя бы один отказ; изменяющий инструмент не выполняет действие по одному вызову без плана.

Источники

Разбор и ответы — откройте после своей попытки

Один из правильных ответов — инструменты, которые курс напишет к главе 7.

log_searchnote_readlog_rotate_plan + log_rotate_apply
Аргументыtext — подстрока 1–100 символов, не регулярное выражение; level — один из четырёх; limit 1–50name — относительный путь внутри notes/, до 200 символовплан — без аргументов; выполнение — plan_id (16 шестнадцатеричных символов)
Результатсовпадения с номером строки и уровнем; сколько строк просмотрено; complete и причина остановкиимя, размер, текст, признак обрезкиплан: размер журнала, имя архива, что будет сделано; выполнение: applied или already_applied
Пределыне больше 5 МБ и 2 с просмотра; строка до 500 символов64 КиБ текстане больше 5 архивов; план живёт 10 минут
Отказыжурнала нет — ошибкапуть наружу, ссылка наружу, нет файлаплан не найден, устарел, журнал изменился после плана, другая ротация идёт сейчас
Меняетнетнетплан — нет; выполнение — переименовывает журнал в архив

Ответы на три вопроса об изменяющем инструменте:

  • Что будет сделано — его называет план: «переименовать app.log (N байт) в app.log.2 и создать пустой». Хост показывает план человеку; выполняется только план с этим plan_id.
  • Повтор — сервер помнит выполненные планы и на повторный вызов возвращает прежний результат со статусом already_applied, не ротируя второй раз.
  • Изменение между планом и выполнением — план хранит «отпечаток» журнала (inode, размер, время изменения). Если он другой, выполнение отказывает: подтверждение относилось к другому состоянию.

Частые слабые места:

  • search_log(regex: str) без пределов — регулярное выражение может работать очень долго, а результат — занять весь контекст модели.
  • read_file(path) с проверкой только на .. — ссылка внутри папки всё равно ведёт наружу (глава 4).
  • rotate_log() одним вызовом — нет момента, когда человек видит, что будет сделано, и нет защиты от повтора.

Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.