Задача для инструмента
Чтение и практика ~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. Для каждого заполните таблицу контракта выше: имя, аргументы
с пределами, результат, пределы, отказы, меняет ли что-то. Для третьего (изменяющего) ответьте отдельно:
- как человек поймёт, что именно будет сделано, до того как это случится;
- что будет, если хост вызовет его дважды подряд — например, после обрыва связи;
- что будет, если между «посмотрел» и «сделал» журнал изменился.
Сравните с разбором. Совпадение имён не важно; важно, где проходят границы.
Проверка
Самопроверка — по разбору: у каждого инструмента есть предел объёма и хотя бы один отказ; изменяющий инструмент не выполняет действие по одному вызову без плана.
Источники
- Архитектура MCP — участники, слои,
server/discover,tools/list,tools/call. - Изменения ревизии 2026-07-28 — отказ
от
initialize, поле_metaв каждом запросе.
Разбор и ответы — откройте после своей попытки
Один из правильных ответов — инструменты, которые курс напишет к главе 7.
log_search | note_read | log_rotate_plan + log_rotate_apply | |
|---|---|---|---|
| Аргументы | text — подстрока 1–100 символов, не регулярное выражение; level — один из четырёх; limit 1–50 | name — относительный путь внутри 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()одним вызовом — нет момента, когда человек видит, что будет сделано, и нет защиты от повтора.
Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.