Глава 4 — Клиентские примитивы: агентное поведение и контроль
Четвёртый пост поглавного разбора LLM Primer IV: Designing AI Cognition with MCP. Серверные примитивы экспонируют то, что сервер может предложить; клиентские — то, что хост готов одолжить взамен, и каждое одолжение — это возможность, которую даёт пользователь, и риск, который принимает хост.
Почему существует эта глава
Сервер, выставляющий только ресурсы, prompts и инструменты, не знает о своём хосте ничего: ни какую модель он запускает, ни какие файлы он видит, ни сидит ли пользователь у клавиатуры. Для многих интеграций эта стена намеренна. Для целых категорий полезного поведения она слишком высока. Серверу, которому нужно резюмировать длинный документ, не должно приходиться поставлять собственную LLM. Серверу, которому нужно работать над проектом, нужно знать, над каким именно. Сервер, которому нужно разрешение пользователя на разрушительный шаг, должен иметь возможность спросить. Клиентские примитивы — это то, как MCP пробивает в стене маленькие, контролируемые отверстия.
Три примитива — Sampling, Roots, Elicitation — каждое расширяет дотягивание сервера на территорию хоста точным, согласованным образом. Каждое также является поверхностью безопасности, которую хост принимает от имени пользователя, и каждое сочетается с другими, производя поведение более агентное, чем сумма частей.
4.1 Sampling: одолжить мозг хоста
Со включённым sampling сервер может попросить хост запустить инференс-вызов и вернуть результат. Он не поставляет модель, не держит API-ключ и никогда не узнаёт, какой именно моделью воспользовался хост — он отправляет сообщения плюс мягкие предпочтения (стоимость, скорость, интеллект), а хост запускает вызов. С точки зрения сервера хост стал универсальной конечной точкой LLM.
Рычаг реален. Сервер хранилища документов может запускать небольшие шаги рассуждения внутри своей логики — поиск, ранжирование, сравнение — не раскрывая хосту доменного знания и не таская инференс самостоятельно. Риск одинаково реален. Злонамеренный сервер может использовать модель хоста — и бюджет пользователя — для работы, на которую пользователь не давал согласия. Классическая атака — sampling-payload, замаскированный под инструкцию пользователя. Хост — единственная линия обороны, поэтому зрелые хосты по умолчанию отказывают в sampling, пока пользователь явно не разрешил его для конкретного сервера, показывают промпт каждого вызова для проверки, ограничивают число вызовов на сессию и метрятся стоимостью против кошелька пользователя, а не сервера.
Более тонкая забота: sampling может инкапсулировать внутреннюю агентную петлю. Для хоста это выглядит как один sampling-вызов; внутри сервера прокрутился целый под-агент. Паттерн, который стоит назвать, — ограниченный под-агент: пользователь выдаёт бюджет в N вызовов, T секунд, X долларов; сервер делает внутри этого конверта что пожелает и возвращает либо результат, либо аккуратный частичный. Sampling также не даёт серверу доступа к разговору; он видит только то, что отправил, и хост, протаскивающий контекст разговора в sampling-payload-ы, ослабляет границу доверия вне зависимости от намерений.
4.2 Roots: границы файловой системы и скоуп проекта
Root — это URI (обычно file://, хотя спека общая), который хост объявил в скоупе. Сервер вызывает roots/list и спрашивает «с чем я работаю?» Хост объявляет возможность roots при инициализации и шлёт notifications/roots/list_changed, когда пользователь переключает проект. Список — это просто список; никакого вложенного языка разрешений, ни глоб-паттернов, ни правил include/exclude.
Грубость намеренна и отражает MCP-широкий проектный выбор, который стоит понять. Мелкозернистый язык разрешений на уровне протокола создаёт ложное чувство безопасности: пользователи читают длинный список скоупов и предполагают, что протокол их обеспечивает, тогда как протокол не может помешать серверу вести себя плохо способами, которые язык никогда не предусмотрел. Грубый примитив, подкреплённый внешней изоляцией — границами процесса, монтированиями контейнера, контролем доступа на уровне ОС, — честен относительно того, где реально живёт обеспечение. Roots — это «честное слово» на уровне протокола, обеспечиваемое на уровне рантайма, когда работает песочница.
Два практических следствия. Во-первых, когда roots меняются, благовоспитанные серверы сбрасывают кэшированное состояние, привязанное к старому root — индексы, разобранные AST-ы, подписки на наблюдение за файлами — иначе они утекают состоянием через границы проектов. Во-вторых, roots ограничивают на что серверу смотреть, а не что ему разрешено делать. Чувствительный файл внутри предоставленного root — дамп учётных данных, env-файл, личная переписка — остаётся чувствительным, и вежливый сервер всё равно проявляет рассудительность, что именно поднимать наверх.
4.3 Elicitation: дать серверу задать вопрос
Elicitation — самый новый из трёх и отражает то, чему дизайнеры научились на первой волне развёртываний агентов. Сервер шлёт elicitation/create с сообщением (вопрос) и requestedSchema (форма ожидаемого ответа). Хост рендерит вопрос в своём UI, собирает ответ, валидирует и возвращает. Сервер возобновляет работу с ответом на руках.
Схема — это то, что делает elicitation безопаснее свободных промптов. На булево нельзя ответить прозой; на enum нельзя ответить четвёртым вариантом. Хост может ясным UI показать пользователю, что у него спрашивают «да/нет», и отказать всему другому. Спецификация намеренно ограничивает схемы плоскими объектами из примитивов — для настоящих форм используйте инструмент, чья схема аргументов есть форма, где пользователь может проверить заполненные моделью значения до вызова. Elicitation — для случаев с одним-двумя полями.
Профиль риска фишингового вида: сервер, выводящий «системе требуется ваш AWS access key для продолжения», пытается заставить пользователя ввести секрет не в том месте. Хосты смягчают это явной атрибуцией — каждый elicitation-промпт помечается происхождением сервера, отдельно от голоса ассистента — и отказом от схем, выглядящих как поля для учётных данных. Положительная оборотная сторона — паттерн подтверждения разрушительного инструмента: первая работа инструмента — спросить elicitation-ом «вы уверены?», и только после подтверждения он действует. Это одна из самых эффективных практик закаливания MCP в продакшене.
4.4 Композиция трёх
Примитивы проектировались для композиции. Сервер со всеми тремя по сути имеет полноценный агентный рантайм, делегированный хосту: он может читать скоуп (roots), рассуждать о нём (sampling), задавать уточняющие вопросы (elicitation) и действовать через собственные инструменты. Сочетания имеют значение. Sampling плюс roots — автономный агентный сервер; sampling плюс elicitation — разговорный сервер без доступа к файловой системе; roots плюс elicitation — детерминированный помощник. Каждое по отдельности ограничено; вместе они умножаются, и UI согласия хоста на высоте, когда может назвать сочетание, которое пользователь предоставляет, а не просить разрешение на каждое в изоляции.
Что подготавливает глава 4
Серверные и клиентские примитивы вместе описывают всё, что хост и сервер могут сделать друг другу. Чего мы пока не затронули — как всё это путешествует по проводу. tools/list и sampling/createMessage — не просто абстрактные сообщения; они едут на транспорте, и выбор транспорта тихо решает почти все операционные свойства MCP-интеграции. Серверы также должны быть находимы: хосту, желающему пользоваться сервером, нужно знать, что тот существует, где он живёт и стоит ли доверять заявлению. Глава 5 берётся за оба вопроса.
Дальше — Глава 5: Транспортные протоколы и обнаружение. Три транспорта, которые поддерживает MCP — stdio, SSE, Streamable HTTP — сравниваются честно, и слой .well-known/mcp.json плюс Server Card, превращающий точечные интеграции в нечто, похожее на экосистему.