MCP или CLI: неверный вопрос. Спросите, где живёт состояние.

Тоби Лютке говорит, что спор MCP против CLI ведётся не на том уровне: оба работают, если состояние сохраняется между шагами. Согласны. Для синхронизации это состояние составляют собственные записи интеграции, а не куча (heap) REPL, и поэтому MCP-инструменты Tajo ничего не хранят в сессии.

mcp или cli

Что использовать AI-агенту: MCP или CLI?

Лютке прав, что MCP против CLI находится не на том уровне: оба работают, если состояние сохраняется между шагами, а bash выигрывает только потому, что файловая система и есть это состояние. Но для синхронизации состояние не является кучей REPL. Это собственные записи интеграции: курсор, ожидающее предложение, аренда, план. Поэтому MCP-инструменты Tajo ничего не хранят в сессии: любой агент, из любого клиента, продолжает работу по записям. Сегодня Tajo не выполняет код клиентов и не имеет REPL, и ниже мы прямо об этом говорим.

Узнать больше
На этой странице

21 сентября 2026 г., в 00:33 UTC, Тоби Лютке (Tobi Lütke) написал: «MCP против CLI для LLM: это неверная дискуссия. Люди спорят не на том уровне». Затем, 23 октября, он указал на новый репозиторий под названием dquickts. Это форк QuickJS, который умеет приостановить программу посреди цикла for, записать всю её кучу в байты и возобновить её в другом процессе. «Отличная среда для code mode», написал он.

Насчёт уровня он прав. Я хочу сначала согласиться с ним, его же словами, а затем сузить утверждение до того, чтобы оно говорило что-то полезное для синхронизации.

Уровень, который он имеет в виду

Вот аргумент в его формулировке, потому что только так честно цитировать аргумент, который собираешься согнуть:

Оба работают невероятно хорошо, пока они запускаются через REPL-подобную среду, способную сохранять состояние. Забавно в CLI то, что к CLI-инструментам обычно обращаются через BASH, который как раз является REPL с постоянным состоянием (файловой системой), поэтому CLI работает гораздо лучше, чем MCP. Но это даже близко не внутреннее свойство MCP. Это лишь значит, что нам нужны более хорошие среды выполнения.

Это верно, и это объясняет наблюдение, которое многие делали, не объясняя его. Агент, получивший CLI, пишет файл, читает его обратно через три шага, ищет в нём grep, передаёт через pipe. Агент, получивший голый MCP-сервер, получает один результат на вызов и должен носить всё остальное в своём контекстном окне. CLI выиграл не за счёт протокола. Он выиграл, потому что bash шёл в комплекте с местом, куда можно что-то положить.

Из этого следует его ставка. Кто-то построит встраиваемый движок в стиле SQLite, который превращает bash, TypeScript или вызовы инструментов в один план, проверенный до запуска и рассчитанный на устойчивое выполнение. dquickts стал шагом в эту сторону. Его README описывает REPL, «чьи функции и переменные переживают перезапуски». Там же описан устойчивый вызов хоста: «скрипт вызывает функцию хоста, ответ на которую приходит через несколько часов. Процесс сохраняет скрипт и завершается».

До этого места полное согласие. Теперь сужение.

Какое состояние и для какой задачи

«Сохранять состояние» скрывает вопрос: чьё состояние и в какой форме? Для агента, пишущего код, состояние составляют репозиторий и оболочка, и REPL, переживающий перезапуски, будет хорошим ответом. Для агента, строящего синхронизацию между двумя SaaS-системами, состояние, нужное ему между шагами, уже определено самой задачей. Его несут четыре записи, и ни одна из них не является переменной в скрипте.

Курсор. Синхронизация читает источник окнами и должна знать, где закончилось последнее окно. В движке синхронизации Tajo это сохранённый курсор для каждой синхронизации и области, записываемый после каждого пакета без единой неудачной записи, и следующий запуск продолжает с него. Запланированный запуск сохраняет свою начальную точку прежде всего остального, так что если первый запуск завершится ошибкой, следующий будет читать с той же точки. Принцип, лежащий в основе (отправляйте дельту и никогда не всю таблицу), разобран в статье о reverse ETL. Курсор я там и оставлю.

Ожидающее предложение. В MCP-инструментах коннекторов Tajo запись состоит из двух вызовов. brevo_propose проверяет входные данные по объявленному действию и регистрирует предложение. Ничего не отправляется. Результат содержит идентификатор предложения и, когда решение должен принять человек, страницу, на которой он его одобряет. brevo_apply выполняет одобренные записи этого подключения по порядку и останавливается на первой, которая всё ещё ждёт человека. Запись между ними и есть состояние. Она содержит входные данные и их дайджест, так что одобренную запись нельзя изменить между решением и применением. Она не содержит значений из ответа провайдера, потому что запись об одобрении, работающая только на добавление, не должна превращаться в хранилище персональных данных.

Аренда (lease). Запуск синхронизации удерживает одну эксклюзивную аренду на всё время своей жизни, с токеном владельца, лимитом времени по умолчанию в пятнадцать минут и продлением. Устаревший владелец не может освободить более новую аренду. Если хранилище аренд недоступно, запуск отказывается стартовать, а не гадает. Какой воркер владеет запуском, зафиксировано в аренде, а не в памяти какого-либо процесса.

План. syncs.dryRun читает следующее окно от курсора, определяет для каждой записи вставку, обновление, удаление или отсутствие изменений и записывает ровно одну строку: план, который хранится один час. Он не сдвигает курсор и ничего не пишет ни в одну из систем. syncs.apply записывает этот план один раз. Он отказывается, если курсор сдвинулся или если за это время изменилось больше допустимой доли записей. Повторный вызов возвращает первый результат, а не пишет снова.

Вот состояние между шагами: четыре записи с идентификаторами, доступные для чтения любому агенту, которому разрешено их видеть.

Почему MCP-сторона ничего не хранит в сессии

Когда состояние находится в записях, сессии нечего хранить. Поэтому MCP-эндпоинты Tajo сессию не держат. Каждый запрос получает новый сервер и транспорт без идентификатора сессии. Чтение: один запрос на вызов, с ограничением размера результатов, чтобы один ответ не мог затопить ход. Запись: сначала предложение, затем применение, и с собой Вы несёте идентификатор предложения.

Протокол двигался в ту же сторону. Редакция спецификации MCP 2026-07-28 убрала сессии на уровне протокола и заголовок Mcp-Session-Id из транспорта Streamable HTTP. Сервер, которому нужна непрерывность, теперь сам выпускает свой идентификатор и получает его обратно как обычный аргумент. Именно этим и является идентификатор предложения. Статья Что такое MCP-сервер рассказывает об этой редакции и о том, чем вызов инструмента отличается от устойчивой синхронизации; этот текст только о том, куда девается состояние.

Важно следствие для работы с интеграциями. Допустим, агент в Claude Code предлагает запись, и его сессия заканчивается. Человек одобряет её через час. Агент в другом клиенте, или тот же на следующий день, вызывает apply с идентификатором, и запись выполняется один раз. Упавший агент теряет своё контекстное окно и больше ничего. Ничто не приостанавливалось, поэтому ничто не нужно было возобновлять.

Чего у нас нет и чего это стоит

Честное возражение приходит со стороны REPL и звучит так: «Хорошо, ваши записи выживают. Но каждый вызов заново читает то, что у предыдущего вызова уже было. Скрипт получил бы контакты один раз, объединил бы их, отфильтровал и вернул одно число. Ваш агент делает пять вызовов и платит за каждый». Это возражение верно в отношении механизма, а цену мы не измеряли. Оценка, которая подсчитала бы вызовы на задачу с переносом состояния и без него, не построена. Пока её нет, утверждение, что режим вызовов стоит больше токенов, остаётся без ответа.

Ещё три вещи нужно сказать прямо, потому что читатель, дочитавший эту статью, не должен их себе вообразить:

  • Сегодня код, написанный клиентами, не выполняется. Чтение через коннекторы: один запрос на вызов, без песочницы на пути. В нашем стеке есть исполнитель кода, и это внутренний инструмент. В продакшене он выключен, если не установлены два отдельных флага, и отвечает только администраторам. Каждый вызов получает новый контекст, привязки, разрешающие только чтение, и бюджет в две секунды на синхронный код. Его собственный заголовок прописными буквами говорит, что это граница корректности, а не безопасности.
  • Нет REPL. Ничто из вычисленного агентом в одном вызове не доступно в следующем, если только оно не было записано как запись с идентификатором.
  • Нет постоянной сессии агента. Клиент хранит свой разговор. Tajo хранит предложения, планы, курсоры и аренды. Третьего нет.

Для чего нужен устойчивый движок

Ничто из этого не аргумент против dquickts. Его README перечисляет, для чего нужны снапшоты, и список хороший. Перевести простаивающий скрипт в спящий режим и освободить его память. Выполнять долгое вычисление частями на любом свободном воркере. Делать тёплый старт каждого экземпляра из снапшота вместо повторной настройки. Парковать скрипт на вызове хоста, ответ на который придёт позже. Для рабочего пространства агента, пишущего код, или для блокнота, который должен пережить перезапуск, это правильный инструмент.

И всё же синхронизация не должна от него зависеть, по трём причинам, которые мне даёт сам README. Первая: «Файл сессии загружается только в ту сборку qjs, которая его записала». Куча, записанная одной версией движка, после обновления становится мёртвым файлом; курсор же просто строка. Вторая: «Снапшоты являются доверенными входными данными, как байт-код», и «вредоносные не обнаруживаются». Сохранённую кучу нужно защищать, а не проверять; предложение же является строкой, которую человек читает перед одобрением. Третья: снапшот содержит всё, что прочитал скрипт, включая записи клиентов. Записи, описанные выше, устроены наоборот: план хранит дайджесты, а не значения, и запись предложения никогда не хранит ответ провайдера.

Одно из правил, по которым мы строим: детерминированное лучше хитроумного. Куча, которая возобновляется посреди цикла, хитроумна. Курсор, предложение, аренда и план скучны, проверяемы и одинаковы на любом движке, в любом клиенте, после любого сбоя.

Лютке задал правильный вопрос. Где живёт состояние? Для синхронизации ответ никогда не мог находиться в процессе. Он всегда должен был находиться в записях, потому что именно там его должен иметь возможность прочитать человек, который одобряет запись.

Часто Задаваемые Вопросы

Что лучше для AI-агентов: MCP или CLI?

Ни то, ни другое само по себе. Мысль Тоби Лютке от 21 сентября 2026 г. остаётся верной: оба работают, если вызовы проходят через что-то, что сохраняет состояние между шагами. CLI сегодня кажется лучше, потому что bash хранит состояние в файловой системе. Разница не в протоколе, а в среде выполнения (harness).

Какое состояние нужно агенту между шагами синхронизации?

Собственные записи интеграции: курсор, который говорит, где остановился последний запуск, предложение, ожидающее одобрения человека, аренда (lease), которая говорит, какой воркер владеет запуском, и план пробного прогона (dry-run), которому должно соответствовать применение. Ничто из этого не является переменной в скрипте. Это строки с идентификаторами.

Почему MCP-сервер Tajo ничего не хранит в сессии?

Каждый запрос получает новый сервер и транспорт без идентификатора сессии. Всё, что нужно следующему вызову, является записью: идентификатор предложения, идентификатор плана, курсор. Поэтому второй агент, в другом клиенте, может продолжить с того места, где остановился первый, а упавший агент не теряет ничего, кроме собственного контекстного окна.

Выполняет ли Tajo код, написанный агентами?

Не для клиентов. Чтение через инструменты коннекторов: один запрос на вызов, без песочницы. Существующий исполнитель кода является внутренним инструментом за двумя флагами, доступен только администраторам, с привязками только для чтения и новым контекстом на каждый вызов. О себе он говорит, что не является границей безопасности. Нет ни REPL, ни постоянной сессии.

Для чего нужен устойчивый JavaScript-движок вроде dquickts?

Его README перечисляет применения: перевести простаивающий скрипт в спящий режим, разбить долгое вычисление на части между воркерами, выполнить тёплый старт из снапшота и припарковать скрипт на вызове хоста, ответ на который придёт через несколько часов. Всё это реально. И всё же синхронизация не должна от него зависеть: снапшот загружается только в ту сборку движка, которая его записала, непрозрачен для проверяющего и содержит все данные клиентов, которые прочитал скрипт.

Подайте заявку на ранний доступ

Укажите имя и адрес электронной почты. Мы отправим ссылку для подтверждения запроса на доступ к Tajo.

Получить Brevo

Эта страница в Markdown: mcp-or-cli-is-the-wrong-question.md

Загрузка Markdown…
Открыть в Claude Открыть в ChatGPT