MCP ClickHouse и ограничения режима только для чтения: структурированный доступ для агентов
Режим доступа MCP только для чтения препятствует записи данных, а не ошибкам в числах, утечкам информации о арендаторах или обнаружению структуры данных. Узнайте, что показывают тесты производительности ClickHouse, и как инструменты для структурированных метрик снижают риски.
Источники: ClickHouse MCP: Why Read-Only Access Isn’t Enough
Почти каждый учебник по подключению ИИ-агента к базе данных заканчивается одним утешением: используйте только читаемое соединение. Включите параметр readonly=1, предоставьте права только на выполнение операций SELECT, и агент сможет просматривать данные, но никогда не изменять их. Это разумно, но это ответ на вопрос, который на самом деле не был главной проблемой.
Nearly recently, ClickHouse провел тестирование 28 моделей на 201 реальном вопросе, взятом из его внутреннего аналитического агента. Лучшая модель давала правильный ответ в 76,6% случаев. Среди неправильных ответов было мало случаев с ошибками запросов; большинство составляли запросы, которые выполнялись без проблем, но возвращали неверные данные, и читаемое соединение не может предотвратить ни один из таких случаев.
Режим «только для чтения» определяет, может ли агент выполнять запись. Он ничего не говорит о том, что может запрашивать агент. Передача SQL-инструмента модели делегирует задачу формирования запросов: агент выбирает таблицы, способы объединения данных, уровень агрегации, фильтры и количество возвращаемых строк. Режим «только для чтения» не влияет ни на что из этого. Он предотвращает уничтожение ваших данных, но ничего не делает для того, чтобы они не содержали ошибок. Важным критерием является не режим чтения против записи, а произвольность против структурированности.
Что делает официальный сервер MCP ClickHouse
Официальный сервер ClickHouse/mcp-clickhouse включает в себя функции list_databases, list_tables и run_query. По умолчанию он позволяет только чтение. Для включения возможности записи требуется переменная среды, а для выполнения опасных команд, таких как DROP, нужна ещё одна переменная. Для протоколов HTTP и SSE аутентификация обязательна с момента запуска (работают как токены-носители, так и OAuth/OIDC), и запуск сервера не будет успешным до её настройки. В документации рекомендуется относиться к учётной записи базы данных MCP как к любому внешнему клиенту с минимальными привилегиями.
Следует обратить внимание на два момента. Тайм-аут запроса реализуется на стороне клиента, поэтому сервер MCP прекращает ожидание, но запрос продолжает выполняться в вашем кластере. Кроме того, параметр readonly=1 применяется к каждому запросу и не обеспечивает значительной защиты. Если подключаться как пользователь ClickHouse с широкими правами, этот флаг вообще не будет эффективен.
Даже при идеальной настройке заблокированное только для чтения подключение всё равно позволяет агенту формировать произвольные SQL-запросы к вашему хранилищу. В этом и заключается настоящая проблема.
Ситуация, которую нельзя исправить никакими настройками
Правильность кода имеет первостепенное значение, поскольку ни один параметр ClickHouse не может помочь с этим. Предоставление агенту возможности писать SQL подразумевает, что модели уже хорошо умеют это делать. Это частично верно, но именно ложная часть является опасной.
Лучшие доказательства исходят от самого ClickHouse. В его тесте Agentic Analytics Benchmark, опубликованном 1 сентября 2026 года, было переиспользовано 201 запрос из реального трафика его внутреннего аналитического помощника против синтетической копии его собственного хранилища. Лучшая модель показала результат 76,6%.
Эти вопросы задавали сотрудники компании, причем каждый результат читал специалист, разбирающийся в данной области. Когда примерно четверть ответов оказывается неверной, человек, участвующий в процессе проверки, обнаруживает большинство связанных с этим проблем. Если использовать ту же модель для функции, ориентированной на клиентов, никто не сможет проверить её результаты и даже не заподозрит, что они могут быть неверными. Точность остаётся прежней, но риск значительно возрастает.
Распределение сбоев говорит о большем, чем самый высокий результат. Ошибки во время выполнения практически исчезли — всего четыре из 28 моделей показали такие ошибки. Среди всех моделей основной причиной сбоев был выбор подхода: неправильный способ объяснял от 53% до 82% всех ошибок. По мнению самой ClickHouse, проблемы синтаксиса и выполнения уже решены, и теперь важны моделирование данных и контекст схемы.
Лучший пример — это Fable 5.1. Среди десяти лидеров у него была самая низкая доля ошибок планирования, но при этом самый высокий уровень сбоев из-за неверных данных. Он формирует хороший план, но затем выбирает неверное поле или неподходящую степень детализации. Ни то, ни другое не может исправить более умный модель. Оба этих проблемы исчезают, когда доступные размерности, показатели и интервалы времени заранее определены.
Именно поэтому эти ошибки опасны, а не просто раздражают. Неверный план, который выполняется корректно, приводит к запросу, который парсится, выполняется, возвращает строки и дает внешне правдоподобное число. В логах нет никаких исключений или предупреждающих сигналов. Кто-то читает эту информацию на панели управления или в кратком отчете в Slack и принимает на ее основе решения.
Сама ClickHouse указывает на определенные ограничения. Ответы, служащие эталоном, были сформированы в результате согласия трех моделей от разных производителей, а не в результате человеческой проверки; лишь примерно половина из этих согласий была единодушной, причем жюри считается снисходительным. Консенсус не является доказательством, поэтому показатель в 76,6% является неопределенным и, вероятно, немного завышенным. В данном случае решение зависит от характера ошибок, а не от точного процентного значения.
ClickHouse также усложняет работу по сравнению со многими базами данных. Объединение таблиц стоит дорого и сопряжено с трудностями; в его документации рекомендуется денормализовать данные и ограничивать количество используемых объединений, причем для большинства алгоритмов объединения таблица с правой стороны хранится в памяти в виде хеш-таблицы. При объединении на основе словаря в одно-к многим отношениях ClickHouse тихо сохраняет одну соответствующую строку и удаляет остальные, в результате чего получается более мелкий неверный ответ без возникновения ошибок. Агент, исследующий вашу схему, с уверенностью попадет в такие ловушки. Ни параметр readonly=1, ни профиль настроек, ни квоты не помогают. Поможет только лишение модели возможности создавать запросы.
Область применения тенанта как параметр, а не граница
Второй тип сбоя связан с утечкой информации. В продуктах с несколькими арендаторами идентификатор арендатора является самым важным критерием при выполнении любого запроса. С помощью произвольного инструмента SQL агент вводит этот критерий в условие WHERE, формируемое во время выполнения запроса, поэтому модель может его проигнорировать, ошибиться или изменить свое решение под влиянием информации, прочитанной ранее в ходе общения.
Здесь скрывается более глубокая структурная проблема. Если предоставить серверу MCP строку подключения, он войдет под именем одного пользователя ClickHouse с определенными правилами доступа к данным, не имея ни малейшего представления о том, какой клиент стоит за каждым запросом. Эта информация хранится в вашем приложении, в авторизованной сессии, а произвольный инструмент SQL теряет ее еще до того, как агент успевает что-либо записать.
Процесс обнаружения передает схему
Третья проблема более тонкая. Чтобы запросить информацию о складе, агент сначала должен его увидеть, поэтому он вызывает функцию list_tables, которая возвращает информацию обо всех таблицах с их столбцами, количеством строк и полным текстом команды CREATE TABLE. Названия столбцов, внутренняя терминология бизнеса, а также таблицы, о существовании которых забыли, попадают в контекст модели, а затем передаются в логи, трейсы и внешние сервисы для анализа. Это не теоретическая утечка информации, поскольку это обязательный первый шаг. Разрешение использования произвольных SQL-запросов и раскрытие полной структуры базы данных — это одно и то же решение.
Структурированный доступ
Более мощный инструмент SQL — это не альтернатива. Настоящая альтернатива — отсутствие такого инструмента. Однажды запишите свой словарь аналитических терминов: размерности, показатели, именованные метрики, правила для разных пользователей и способ работы с временем, и предоставляйте агенту только это. Модель выбирает вопросы из каталога разрешенных вариантов и никогда не формирует запросы.
@hypequery/mcp, библиотека для этой цели, предлагает четыре инструмента:
list_datasets: зарегистрированные наборы данных с указанием количества их размерностей, показателей и метрик.get_dataset_schema: поля, именованные метрики, фильтры и ограничения конкретного набора данных.query_metric: выполнение одной именованной метрики.query_dataset: специальный запрос с использованием выбранных размерностей и показателей.
Функции вроде run_sql отсутствуют, даже ограниченной версии. Вызов выглядит следующим образом:
{
"name": "query_metric",
"arguments": {
"dataset": "orders",
"metric": "revenue",
"dimensions": ["country"],
"filters": [
{ "field": "status", "operator": "eq", "value": "completed" }
],
"orderBy": [{ "field": "revenue", "direction": "desc" }],
}
}
Агент выбрал показатель, группировку и фильтр. Он не определил, что означает revenue. Это определение находится в вашей базе кода, в TypeScript, где оно проходит проверку и имеет версионирование, как и все остальное; ваш API и панель управления используют то же определение. Операторы фильтрации и интервалы времени берутся из фиксированных наборов, а поля проверяются перед запуском любых операций. Реестр служит границей:
export const datasets = {
orders: {
...Orders,
metrics: { revenue },
},
};
Набор данных, отсутствующий в данном объекте, нельзя перечислить, исследовать или запросить, поэтому набор вопросов, которые может задать агент, становится объектом для просмотра в вашем репозитории. Диапазон доступа арендатора также больше не является чем-то, что предоставляет модель:
await createMCPServer({
datasets,
analytics,
tenantId: 'tenant_123',
});
Это значение поступает из процесса хоста, который уже знает, кто аутентифицирован. Если попытаться зарегистрировать набор данных с ключом арендатора, не указав его, сервер откажется запускаться:
MCP server tenantId is required for tenant-scoped datasets
По умолчанию из ответов также удаляются генерируемые SQL-запросы и выражения полей, основанные на SQL, поэтому модель видит только числа и названия полей, а не структуру вашего запроса.
Несколько слов о затратах
Цена — это второстепенный момент. Официальный сервер передаёт на клиент только данные с опцией readonly, без параметров max_rows_to_read, max_bytes_to_read и max_memory_usage, а также без ограничений по времени выполнения, установленных сервером. При расчёте стоимости по факту использования повторные попытки агента сканировать всю таблицу в цикле считаются инцидентом, влияющим на счёт, причём ограниченный набор запросов снижает ущерб. Однако параметр LIMIT ограничивает количество возвращаемых строк, а не количество просканированных байтов, и запрос с группировкой за длительный период времени всё равно считает все необходимые данные. Структурированные ограничения доступа регулируют наличие запросов, а не их стоимость. Как бы вы ни решили, установите реальные лимиты на количество строк, байтов, памяти и время выполнения в профиле настроек пользователя MCP, а также добавьте квоту.
Чего это не решает
Всё это не ново. В своей статье о смертельной тройке рисков Саймон Уиллисон, а также в демонстрации MCP Supabase 2025 утверждали, что режим только для чтения не является барьером безопасности, причём Cube и dbt уже давно продвигают использование семантического слоя для агентов.
Честный компромисс — это гибкость. Структурированный интерфейс отвечает только на те вопросы, которые были заложены при моделировании, поэтому на вопросы, выходящие за рамки каталога, ответов нет, тогда как агент с SQL может импровизировать что-то полезное. Однако это не исключает всех остальных рисков. Метки и описания наборов данных доходят до модели и влияют на её поведение — это усечённая версия механизма воздействия на инструменты, а не его полное исключение, хотя эти строки находятся в проверенном TypeScript, а не на сервере сторонней организации. Кроме того, контролируемые показатели не могут помешать модели неправильно интерпретировать корректное число в своём резюме.
Начинать с малого
Не требуется моделировать весь склад. Выберите один показатель, от которого зависит ваш продукт, тот, где неверный ответ может привести к проблемам, определите его один раз, зарегистрируйте и передайте агенту только его. Затем сравните результаты, возвращаемые агентом, с данными на вашей панели управления. Если цифры совпадают, значит определение теперь находится в месте, откуда они оба зачитываются, и в этом вся суть.