Strona główna / Artykuły

MCP ClickHouse i ograniczenia trybu tylko do odczytu: ustrukturyzowany dostęp dla agentów

Dostęp MCP tylko do odczytu zapobiega zapisom, a nie błędnym liczbom, wyciekom danych użytkowników ani ujawnieniu schematu. Zobacz, co pokazują testy wydajności ClickHouse oraz w jaki sposób narzędzia do mierzonych wskaźników zmniejszają ryzyko.

Wydawca: REACTAPP.TOP8 min czytania1772 słów

Źródła: ClickHouse MCP: Why Read-Only Access Isn’t Enough

Prawie każdy tutorial dotyczący połączenia agenta AI z bazą danych kończy się jednym zapewnieniem: należy używać połączenia tylko do odczytu. Włącz opcję readonly=1, przyznaj dostęp tylko do operacji SELECT, a agent będzie mógł oglądać dane, ale nigdy je zmieniać. To rozsądne rozwiązanie, jednak odpowiada ono na pytanie, które w rzeczywistości nie stanowiło prawdziwego problemu.

ClickHouse niedawno przeprowadził testy 28 modeli na 201 rzeczywistych pytaniach pochodzących z jego wewnętrznego agenta analitycznego. Najlepszy model udzielał poprawnych odpowiedzi w 76,6% przypadków. Nieliczne błędne odpowiedzi wynikały z nieudanych zapytań. Większość to zapytania, które uruchomiły się bez problemów, ale zwróciły błędne wyniki, a połączenie tylko do odczytu nie może zapobiec ani jednemu z takich przypadków.

Tryb tylko do odczytu określa, czy agent może pisać. Nie mówi nic o tym, o co agent może pytać. Przekazanie modelowi narzędzia SQL deleguje odpowiedzialność za tworzenie zapytań: agent wybiera tabele, operacje łączenia, poziom agregacji, filtry oraz liczbę zwracanych wierszy. Tryb tylko do odczytu nie wpływa na nic z tego. Zapobiega niszczeniu twoich danych, ale nie robi nic, by zapewnić ich dokładność. Kluczowym czynnikiem nie jest odczyt w porównaniu z pisaniem, lecz arbitralność w porównaniu ze strukturą.

Co robi oficjalny serwer MCP ClickHouse

Oficjalny serwer ClickHouse/mcp-clickhouse zawiera funkcje list_databases, list_tables oraz run_query. Domyślnie może jedynie czytać dane. Włączenie możliwości zapisu wymaga zmiany zmiennej środowiskowej, a niebezpieczne polecenia takie jak DROP wymagają kolejnej zmiennej. W przypadku transmisji HTTP i SSE autoryzacja jest obowiązkowa od razu po uruchomieniu (pracują zarówno tokeny typu bearer, jak i OAuth/OIDC), a uruchomienie serwera nie powiedzie się, dopóki nie zostanie to skonfigurowane. Dokumentacja zaleca traktowanie konta bazy danych MCP jak każdego zewnętrznego klienta o minimalnych uprawnieniach.

Dwa szczegóły zasługują na uwagę. Czas timeout dla zapytań jest egzekwowany po stronie klienta, więc serwer MCP przestaje czekać, ale zapytanie nadal jest wykonywane w Twoim klastrze. Ponadto flaga readonly=1 ma zastosowanie do każdego zapytania i niesie ze sobą ograniczone korzyści. Połącz się jako użytkownik ClickHouse o luźnym profilu i szerokich uprawnieniach – w takim przypadku ta flaga nie zapewnia żadnej ochrony.

Nawet przy idealnie skonfigurowanym wszystkim, zamknięte połączenie tylko do odczytu nadal pozwala agencie tworzyć dowolne zapytania SQL do Twojego magazynu. To jest właśnie prawdziwy problem.

Błąd, którego nie da się naprawić żadną ustawieniem

Prawidłowość jest najważniejsza, ponieważ żadna opcja ClickHouse nie pomaga w tym zakresie. Udostępnianie agencie możliwości pisania zapytań SQL zakłada, że modele stały się biegłe w ich tworzeniu. To częściowo prawda, ale ta fałszywa część jest najniebezpieczniejsza.

Najlepsze dowody pochodzą od samego ClickHouse. Jego Agentic Analytics Benchmark, opublikowany 1 września 2026 roku, ponownie przetworzył 201 zapytań z ruchu produkcyjnego jego wewnętrznego asystenta analitycznego przeciwko syntetycznej rekonstrukcji jego własnego magazynu. Najlepszy model osiągnął wynik 76,6%.

To pytania zadawali pracownicy firmy, a każdy wynik czytał ktoś znający ten obszar. Gdy około jednej czwartej odpowiedzi jest błędna, osoba zaangażowana w proces wykrywa większość powstałych w wyniku tego problemów. Gdy ten sam model zostanie zastosowany w funkcji skierowanej do klienta, nikt nie może sprawdzić poprawności wyników ani nawet podejrzewać, że mogą być błędne. Dokładność pozostaje taka sama, ale ryzyko jest znacznie większe.

Rozkład błędów mówi więcej niż najwyższa ocena. Błędy w trakcie działania modeli praktycznie zniknęły – tylko cztery z 28 modeli wykazało takie błędy. Największą przyczyną niepowodzeń we wszystkich modelach była strategia: wybór niewłaściwego podejścia tłumaczyło 53% do 82% błędów. Według samego ClickHouse problemem są już składnia i wykonywanie zadań, a teraz kluczowe są modelowanie danych oraz kontekst schematu.

Najlepszy wynik ilustruje to. Fable 5.1 wykazał najniższy odsetek błędnych planów wśród dziesięciu najlepszych modeli, ale jednocześnie najgorszy wskaźnik błędów wynikających z niewłaściwych danych. Tworzy on dobry plan, a następnie wybiera niewłaściwe pole lub zbyt drobny poziom szczegółowości. Żadne z tych problemów nie może zostać rozwiązane przez bardziej inteligentny model. Oba znikają, gdy dostępne wymiary, miary oraz poziomy czasowe są z góry określone.

Dlatego te błędy są niebezpieczne, a nie tylko irytujące. Błędny plan, który jest poprawnie wykonywany, generuje zapytanie, które się parsuje, działa, zwraca wiersze i daje wynik wyglądający na wiarygodny. Nie ma żadnych wyjątków ani czerwonej linii w dzienniku. Ktoś czyta ten wynik na panelu kontrolnym lub w podsumowaniu w Slacku i podejmuje na jego podstawie działania.

Sama ClickHouse wskazuje na pewne zastrzeżenia. Odpowiedzi referencyjne powstały w wyniku zgodności trzech modeli od różnych producentów, a nie dzięki audytowi przeprowadzonemu przez ludzi; jedynie około połowa tych zgodności była jednomyślna, a komisja oceniająca określana jest jako łagodna. Konsensus nie stanowi dowodu, więc wartość 76,6% jest niepewna i prawdopodobnie nieco zawyżona. Wynik zależy tutaj od charakteru błędów, a nie od dokładnego procentu.

ClickHouse również utrudnia pracę w porównaniu z wieloma bazami danych. Łączenie tabel jest kosztowne i skomplikowane, dokumentacja zaleca denormalizację oraz ograniczenie liczby używanych połączeń, a w przypadku większości algorytmów łączenia tabela po prawej stronie jest przechowywana w pamięci jako tablica haszowa. Przy łączeniu opartym na słowniku w relacji jeden-do-wielu, ClickHouse zachowuje tylko jedną pasującą wiersz i odrzuca resztę, dzięki czemu otrzymujemy mniejszą, błędną odpowiedź bez żadnego błędu. Agent badający Twoją strukturę danych z pewnością wpadnie w takie pułapki. Ani readonly=1, ani profil ustawień, ani kwota nie pomagają. Tylko odebranie modelowi możliwości tworzenia zapytań może pomóc.

Zakres użytkownika jako parametr, nigdy granica

Drugi typ błędu to taki, który prowadzi do ujawnienia wycieków danych. W produkcie wielu najemców tożsamość najemcy jest najważniejszym kryterium we każdym zapytaniu. Za pomocą dowolnego narzędzia SQL agent zapisuje to kryterium do klauzuli WHERE tworzonej w czasie wykonywania, dzięki czemu model może je pominąć, błędnie je zinterpretować lub zostać od tego odwieziony przez treść przeczytaną wcześniej w rozmowie.

Tutaj kryje się głębszy problem strukturalny. Gdy podasz serwerowi MCP ciąg połączenia, ten loguje się jako pojedynczy użytkownik ClickHouse związany z jednym zestawem polityk wierszy, bez żadnej informacji o tym, który klient stoi za danym zapytaniem. Ta wiedza znajduje się w twojej aplikacji, w autoryzowanej sesji, a generyczne narzędzie SQL traci ją jeszcze przed tym, jak agent napisze choćby jedną linię kodu.

Odkrywanie przekazuje schemat

Trzeci problem jest subtelniejszy. Aby zapytać magazyn, agent musi najpierw go zobaczyć, dlatego wywołuje funkcję list_tables, która zwraca każdą tabelę wraz z jej kolumnami, liczbą wierszy oraz pełnym poleceniem CREATE TABLE. Nazwy kolumn, wewnętrzna terminologia firmy oraz tabele, o których zapomniano, trafiają do kontekstu modelu, a następnie przechodzą do logów, śladów oraz zewnętrznych narzędzi analitycznych. Nie jest to teoretyczna wyciek informacji, ponieważ stanowi konieczny pierwszy krok. Zezwolenie na dowolne zapytania SQL oraz ujawnienie pełnego schematu to ta sama decyzja.

Zorganizowany dostęp

Nie lepszym rozwiązaniem jest bardziej zaawansowane narzędzie SQL – brak takiego narzędzia jest właściwą alternatywą. Zapisz raz swoją terminologię analityczną, mianowicie wymiary, miary, nazwane wskaźniki, zasady dotyczące użytkowników oraz sposób funkcjonowania czasu, i udostępnij agentowi wyłącznie to. Model wybiera z katalogu dozwolonych pytań i nigdy nie tworzy zapytań.

@hypequery/mcp, biblioteka służąca do tego celu, oferuje cztery narzędzia:

  • list_datasets: zarejestrowane zestawy danych wraz z liczbą ich wymiarów, miar i wskaźników.
  • get_dataset_schema: pola, nazwane wskaźniki, filtry i ograniczenia danego zestawu danych.
  • query_metric: uruchamia jeden nazwany wskaźnik.
  • query_dataset: zapytanie ad hoc oparte na wybranych wymiarach i miarach.

Nie ma funkcji run_sql, nawet w wersji ograniczonej. Wygląd wywołania jest następujący:

{
  "name": "query_metric",
  "arguments": {
    "dataset": "orders",
    "metric": "revenue",
    "dimensions": ["country"],
    "filters": [
      { "field": "status", "operator": "eq", "value": "completed" }
    ],
    "orderBy": [{ "field": "revenue", "direction": "desc" }],
  }
}

Agent wybrał metrykę, grupowanie oraz filtr. Nie zdecydował, co oznacza revenue. Ta definicja znajduje się w twojej bazie kodu, w TypeScript, jest sprawdzana i wersjonowana jak wszystko inne, a twoja API oraz panel sterowania używają tej samej. Operatorzy filtrów oraz interwały czasowe pochodzą z ustalonych zbiorów, a pola są weryfikowane przed uruchomieniem jakiejkolwiek operacji. Rejestr stanowi granicę:

export const datasets = {
  orders: {
    ...Orders,
    metrics: { revenue },
  },
};

Zbiór danych nieobecny w tym obiekcie nie może być wyświetlany, analizowany ani wyszukiwany, dlatego zestaw pytań, które może zadać agent, staje się obiektem dostępnym do przeglądania w twoim repozytorium. Zakres dostępu użytkownika również przestaje być czymś, co dostarcza model:

await createMCPServer({
  datasets,
  analytics,
  tenantId: 'tenant_123',
});

To wartość pochodzi z procesu hosta, który już wie, kto został uwierzytelniony. Jeśli spróbujesz zarejestrować zestaw danych oparty na kluczu tenant bez podania go, serwer odmówi uruchomienia:

MCP server tenantId is required for tenant-scoped datasets

Domyślnie generowane zapytania SQL oraz wyrażenia pól oparte na SQL są również usuwane z odpowiedzi, dzięki czemu model widzi tylko liczby i nazwy pól, a nie strukturę zapytania.

Kilka słów o kosztach

Koszt to kwestia drugorzędna. Oficjalny serwer przekazuje do klienta jedynie dane o typie readonly, bez ustawień max_rows_to_read, max_bytes_to_read czy max_memory_usage, a także bez czasu oczekiwania na wykonanie narzuconego przez serwer. W przypadku usług obliczeniowych opłacanych według zużycia, próby ponownego skanowania całej tabeli w pętli przez agenta stanowią problem finansowy, natomiast ograniczona liczba zapytań minimalizuje szkody. Jednakże LIMIT ogranicza liczbę zwracanych wierszy, a nie liczba przeskanowanych bajtów, a zapytanie grupowane w długim przedziale czasowym nadal odczytuje wszystko, co jest mu potrzebne. Strukturalne ograniczenia dostępu dotyczą tego, jakie zapytania są dozwolone, a nie tego, ile kosztują poszczególne z nich. Niezależnie od wyboru, należy ustalić rzeczywiste limity dla liczby wierszy, bajtów, pamięci oraz czasu wykonania w profilu ustawień użytkownika MCP i dodać kwotę limitową.

Czego to nie rozwiązuje

Nic z tego nie jest nowością. Zarówno śmiertelna trójka rozwiązań zaproponowana przez Simona Willisona, jak i demonstracja Supabase MCP z 2025 roku wskazywały, że dostęp tylko do odczytu nie stanowi bariery bezpieczeństwa, a Cube i dbt od dawna promują warstwę semantyczną przeznaczoną dla agentów.

Szczerym kompromisem jest elastyczność. Ustrukturyzowana powierzchnia danych odpowiada tylko na to, co zostało zmodelowane, więc pytanie poza katalogiem nie przynosi żadnych rezultatów, podczas gdy agent wykorzystujący SQL mógłby wymyślić coś przydatnego. Nie eliminuje to również wszystkich innych zagrożeń – etykiety i opisy zestawów danych docierają do modelu i kształtują jego zachowanie; jest to węższa wersja mechanizmu zatruwania narzędzi, a nie jego całkowite wyeliminowanie, chociaż te dane znajdują się w sprawdzonej wersji TypeScript, a nie na serwerze third-party. Ponadto kontrolowane metryki nie powstrzymają modelu przed błędną interpretacją poprawnej liczby w jego podsumowaniu.

Zaczynanie od małego

Nie jest konieczne modelowanie całego magazynu. Wybierz jedną miarę, od której zależy Twój produkt, taką, w której błędna odpowiedź może przynieść szkodę, zdefiniuj ją raz, zarejestruj i przekaż agentowi wyłącznie ją. Następnie porównaj to, co zwraca agent, z tym, co pokazuje Twój panel sterowania. Jeśli liczby się zgadzają, definicja znajduje się teraz w miejscu, z którego obie strony ją odczytują, i to jest cała idea.