Головна / Статті

ClickHouse MCP та обмеження лише для читання: структурований доступ для агентів

Режим доступу MCP лише для читання зупиняє записи, а не неправильні числа, витоки даних орендаря чи викриття схеми. Дізнайтеся, що показує тестування ClickHouse, та як інструменти для структурованих метрик зменшують ризик.

Видавець: REACTAPP.TOP8 хв читання1772 слів

Джерела: ClickHouse MCP: Why Read-Only Access Isn’t Enough

Майже кожен посібник з підключення агента ШІ до бази даних закінчується одним порадником: використовуйте з’єднання тільки для читання. Увімкніть параметр readonly=1, надайте право лише на виконання запитів типу SELECT, і агент зможе переглядати дані, але ніколи їх не змінювати. Це розумно, але це відповідь на запитання, яке насправді не було основною проблемою.

Компанія 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 та додайте квоту.

Що це не вирішує

Усе це не є чимось новим. У статті Саймона Віллісона про смертельну трійку ризиків та у демонстрації Supabase MCP 2025 обидва автори стверджували, що режим лише для читання не є бар’єром безпеки, а Cube та dbt вже давно просувають ідею семантичного шару для агентів.

Чесний компроміс — це гнучкість. Структурована поверхня відповідає лише на те, що було спроектовано, тож запитання поза каталогом не отримують відповіді, тоді як агент з SQL може імпровізувати щось корисне. Це також не закриває всі інші ризики. Мітки та описи наборів даних потрапляють до моделі та впливають на її поведінку — це вужча версія механізму отруєння інструментами, а не його повна відсутність, хоча ці рядки зберігаються у перевіреному TypeScript, а не на сервері сторонньої організації. Крім того, контрольовані показники не завадять моделі неправильно інтерпретувати правильне число у своєму огляді.

Початок з малого

Не обов’язково моделювати весь склад. Виберіть один показник, від якого залежить ваш продукт, той, для якого неправильна відповідь може завдати шкоди, визначте його один раз, зареєструйте та передайте агенту лише його. Потім порівняйте те, що повертає агент, з тим, що показує ваш панель керування. Якщо числа збігаються, значення тепер знаходиться в місці, з якого обидва читають його, і саме в цьому суть.