Skip to main content
Glama

mcp-dwh

MCP-сервер, который даёт агентам строить дашборды и отчёты по хранилищу на ClickHouse — и переживает изменения его структуры без правок кода.

Схема не вкладывается в промпт: агент добывает её вызовами интроспекции, поэтому добавление таблиц, переименование колонок и даже замена предметной области целиком не требуют ни правок сервера, ни обновления инструкций.

Документация архитектуры: Живая схема · Спек дашборда — откройте файлы в браузере.


Что это делает

Интроспекция

Агент ищет таблицы, читает типы, комментарии, ключ сортировки, профилирует колонки

Семантический слой

Метрики, join-граф, подсказки по значениям — в Postgres, отдельно от физической схемы

Дашборды

Декларативные спеки с версиями, уровнями поддержки и валидацией против актуальной схемы

Рендер

Push в Grafana (живой дашборд) или самодостаточный HTML-отчёт (срез на момент времени)

Реакция на миграции

Снимки схемы, дифф, автоматический перевод устаревшего в stale и orphaned

30 инструментов, 53 теста, четыре сервиса в compose.


Related MCP server: SLayer

Быстрый старт на Linux

Нужен Docker с плагином compose. Всё остальное скрипт сделает сам: сгенерирует секреты, поднимет ClickHouse, Postgres, Grafana и MCP, создаст слои хранилища и наполнит их тестовыми данными.

git clone <репозиторий> mcp-dwh && cd mcp-dwh
./scripts/setup.sh

Полный прогон с чистого листа — около двух минут. По окончании скрипт напечатает адреса и подскажет, где взять токен.

./scripts/setup.sh --no-seed             # только структура, без тестовых данных
./scripts/setup.sh --recreate            # снести тома и начать с нуля
./scripts/setup.sh --domain sales        # другой набор SQL (по умолчанию tutoring)

Скрипт идемпотентен: повторный запуск пересоздаёт слои, но не трогает уже созданные секреты.

Что поднимется

Сервис

Порт

Назначение

mcp-server

8765

MCP по HTTP, требует Bearer-токен

mcp-clickhouse

8123, 9000

Хранилище

mcp-postgres

5433

Семантический слой и дашборды

mcp-grafana

3000

Рендер живых дашбордов

Все порты публикуются только на петлевой интерфейс. Перед выставлением наружу поставьте обратный прокси с TLS: Bearer-токен без шифрования канала бессмысленен.

Установка Docker, если его нет

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker "$USER" && newgrp docker

Проверка: docker compose version должна вывести v2 или выше.


Быстрый старт на Windows

Docker Desktop, PowerShell 5.1 и уже установленный PostgreSQL.

powershell -ExecutionPolicy Bypass -File scripts\bootstrap.ps1
powershell -ExecutionPolicy Bypass -File scripts\init_postgres.ps1

Альтернатива — запустить scripts/setup.sh из Git Bash: он работает и там, поднимая Postgres в контейнере вместо установленного на хосте.

Если Docker не поднимается

На машине разработки встретились две независимые проблемы.

1. Служба WSL отключена (Wsl/0x80070422). Разово от администратора:

Set-Service -Name WSLService -StartupType Manual
Start-Service -Name WSLService
Start-Service -Name com.docker.service

2. Осиротевшие Unix-сокеты. Бэкенд падает с initializing Inference manager: ... The file cannot be accessed by the system. Здесь <HOME> в логе — маскировка домашнего каталога, а не незаполненный шаблон. Файл сокета остался от аварийного завершения, стал битым reparse point и не удаляется ничем — ни Remove-Item, ни fsutil reparsepoint delete. Лечится переименованием каталога, Docker создаёт его заново:

Move-Item "$env:LOCALAPPDATA\Docker\run" "$env:LOCALAPPDATA\Docker\run.broken"
Move-Item "$env:LOCALAPPDATA\docker-secrets-engine" "$env:LOCALAPPDATA\docker-secrets-engine.broken"

Каждая неудачная попытка старта плодит новый такой сокет, поэтому чистить надо непосредственно перед запуском.


Подключение клиента

Локально, режим stdio

Клиент сам запускает процесс, строки подключения не существует. Готовый конфиг — .cursor/mcp.json:

{
  "mcpServers": {
    "mcp-dwh": {
      "command": "/путь/к/.venv/bin/mcp-dwh",
      "args": []
    }
  }
}

Удалённо, режим HTTP

Шаблон — deploy/client-remote.json:

{
  "mcpServers": {
    "mcp-dwh": {
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer ВАШ_ТОКЕН" }
    }
  }
}

Путь именно /mcp. Обращение к /mcp/ даёт редирект 307, а часть клиентов теряет на нём заголовок Authorization.


Настройка

Только переменные окружения. Локально подхватываются из .env рядом с pyproject.toml, в контейнере передаются compose-ом.

ClickHouse

Переменная

По умолчанию

Назначение

CH_HOST

127.0.0.1

Адрес сервера

CH_HTTP_PORT

8123, при CH_SECURE8443

Порт HTTP-интерфейса

CH_DASHBOARD_USER

dashboard

Пользователь только для чтения

CH_SECURE

false

TLS. Обязателен, когда база на другом сервере

CH_VERIFY

true

Проверять сертификат

CH_ALLOWED_DATABASES

core,mart

Слои, видимые агенту

CH_DEFAULT_LIMIT / CH_MAX_LIMIT

200 / 5000

Обрезка выдачи

CH_POOL_SIZE

8

Размер пула клиентов

CH_ALLOWED_DATABASES — не про безопасность, её обеспечивают гранты на стороне ClickHouse. Список нужен, чтобы интроспекция не показывала агенту лишнего, и настраивается, потому что у заказчика слои могут называться иначе.

Postgres, Grafana, сервер

Переменная

По умолчанию

Назначение

PG_DSN

Обязательна

PG_POOL_MIN / PG_POOL_MAX

1 / 8

Размер пула

GF_URL

http://127.0.0.1:3000

Нужна только для dash_publish

MCP_TRANSPORT

stdio

stdio или http

MCP_PORT

8765

Порт HTTP-режима

MCP_REPORTS_DIR

корень проекта или /app/reports

Куда складывать отчёты

Секреты

Пароли и токены не передаются через переменные окружения: содержимое env видно в docker inspect и попадает в логи оркестратора. Вместо этого — файлы в каталоге secrets/ (в git не попадает).

secrets/
  ch_admin_password        пароль default в ClickHouse
  ch_dashboard_password    пароль пользователя дашбордов
  gf_admin_password        админ Grafana
  mcp_auth_token           Bearer-токен MCP-сервера
  pg_dsn                   строка Postgres для контейнера
  pg_dsn_local             то же для запуска с хоста
  pg_password              пароль Postgres

Компонент

Механизм

ClickHouse

CLICKHOUSE_PASSWORD_FILE — поддержка в entrypoint официального образа

Grafana (админ)

GF_SECURITY_ADMIN_PASSWORD__FILE — двойное подчёркивание, конвенция Grafana

Grafana (источник данных)

$__file{/run/secrets/...} прямо в файле провижининга

MCP-сервер

<ИМЯ>_FILE — та же идиома, реализована в config.py

.env содержит только несекретные настройки и указатели вида CH_DASHBOARD_PASSWORD_FILE=secrets/ch_dashboard_password.

Проверка: docker inspect --format '{{json .Config.Env}}' mcp-server — в выводе должны быть только пути *_FILE, ни одного значения.

Для Swarm или Kubernetes определения secrets: в compose меняются с file: на внешние. Код сервера при этом не трогается: он читает всё те же /run/secrets/*.

Изменили секрет — пересоздайте контейнер. Compose не отслеживает содержимое файлов: docker compose up -d --force-recreate mcp.

Диагностика

Инструмент mcp_diagnostics показывает, к каким адресам сервер обращается на самом деле, и проверяет связь со всеми тремя системами. Пароли скрыты. Вызывайте первым при ошибках подключения.


Слои хранилища

Слой

Назначение

Доступ пользователя dashboard

raw

Сырые выгрузки: всё строками, с дублями и разнобоем значений

нет

int

Типизация, дедупликация, нормализация

нет

core

Очищенные факты и измерения, минимум бизнес-логики

SELECT

mart

OBT и агрегаты под дашборды

SELECT

Гранты кодируют слоистость: агент физически не может построить дашборд на сыром или промежуточном слое — они не видны даже в system.tables. Это надёжнее, чем просить его об этом в промпте.

Тестовые данные

Оба домена вымышлены. Это синтетические стенды, а не выгрузка чьей-либо системы: структура придумана так, чтобы быть правдоподобной и содержать типовые сложности реального хранилища. Никаких настоящих данных, схем или бизнес-правил конкретной компании здесь нет.

По умолчанию разворачивается домен репетиторства: занятия с репетитором, домашние работы, экзамены. Около 219 тысяч занятий, 126 тысяч домашних работ, 3 тысячи учеников, 120 репетиторов за 2024-01-01 … 2025-08-31.

Данные не случайны: в них есть тренд роста, летняя просадка и связь между числом занятий и сдачей экзамена — от 0% при менее чем 20 занятиях до 100% при сотне. Это позволяет проверять, что дашборды показывают осмысленное.

Второй домен, продажи, остался в sql/ и разворачивается через --domain sales. Он существует не для полноты, а как инструмент проверки: переключение между двумя непохожими доменами показывает, что сервер действительно не знает предметной области.

Ловушки, заложенные намеренно

Тестовая база содержит те грабли, которые семантический слой обязан документировать:

  • статус приходит в разном регистре: ACTIVE, active, PAUSED, churned;

  • поле revenue уже обнулено для несостоявшихся занятий, а price — нет: суммировать нужно первое;

  • тестовые аккаунты отсекаются только на слое core — правило бизнесовое, из схемы не выводится;

  • у части учеников нет класса (взрослые), у части событий — идентификатора;

  • в raw есть дубли по ключу: брать нужно последнюю запись по _ingested_at.


Безопасность

  • readonly = 2 с потолками, а не readonly = 1. Значение 1 запрещает любой SET, и на этом падает плагин ClickHouse для Grafana: он выставляет max_execution_time на каждый запрос. Гарантия возвращена секцией constraints — настройку можно менять только в пределах потолка, а сами readonly и allow_ddl объявлены неизменяемыми.

  • Квоты: время выполнения, число прочитанных и возвращённых строк, память, часовая квота на пользователя.

  • Принудительный LIMIT на уровне MCP — только чтобы не топить контекст агента; защиту базы обеспечивает сервер.

  • HTTP-режим не стартует без MCP_AUTH_TOKEN.

Проверено семь запретов: поднять таймаут выше потолка (код 452), снять readonly (164), разрешить DDL (392), поднять лимит строк (452), запись, создание таблиц и чтение слоя raw (497).

Про монтирование конфигов

В compose монтируются отдельные файлы, а не каталоги config.d и users.d. Bind-mount каталога скрывает то, что кладёт в него образ:

  • в config.d образ ClickHouse кладёт docker_related_config.xml с listen_host = 0.0.0.0; если его затенить, сервер слушает только localhost внутри контейнера, и проброс портов перестаёт работать — при этом docker exec clickhouse-client продолжает отвечать, потому что выполняется внутри. Симптом обманчивый: порт на хосте слушается, но ответ пустой;

  • в users.d entrypoint дописывает default-user.xml; каталог, смонтированный как :ro, отправляет контейнер в цикл перезапуска.


Разработка

uv venv --python 3.10
uv pip install -e .
.venv/bin/python -m pytest tests -q

Тесты не знают предметной области: имена таблиц, колонок и метрик добываются интроспекцией через фикстуры в tests/conftest.py. Это не аккуратность ради аккуратности — при смене домена с продаж на репетиторство продуктовый код не потребовал правок, а 9 тестов упали именно потому, что ссылались на конкретные имена.

Интеграционные тесты пропускаются, если сервисы не подняты.

Структура

docs/                    архитектура и спек дашборда (HTML)
src/mcp_dwh/
  config.py              настройки, секреты из файлов
  clickhouse.py          доступ к ClickHouse через пул
  pool.py, db.py         пулы соединений
  introspect.py          интроспекция схемы
  semantic.py            семантический слой
  dashboards.py          хранение дашбордов, уровни поддержки
  validation.py          валидация виджетов
  grafana.py             адаптер рендера
  reports.py             HTML-отчёты
  schema_watch.py        снимки схемы, дифф, реакция
  http_server.py         HTTP-транспорт, Bearer-аутентификация
  server.py              регистрация инструментов MCP
sql/                     домен продаж + общие файлы
sql/tutoring/            домен репетиторства
sql/pg/                  схема семантического слоя (доменно-нейтральна)
scripts/setup.sh         развёртывание на Linux
scripts/bootstrap.ps1    развёртывание на Windows

Это MVP: как встраивается в реальный стек

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

Компонент

Здесь (MVP)

В боевом стеке

Загрузка в raw

SQL-генератор данных

dlt со своими _dlt_load_id и служебными таблицами

Преобразования слоёв

sql/*/60_transform.sql

dbt — мои скрипты не нужны целиком

Описания, линиж, допустимые значения

Руками в sem.*

Из manifest.json dbt: description, depends_on, тесты accepted_values и relationships

Расписание

Вручную

Prefect — нужен CLI или HTTP-эндпоинты, по MCP оркестратор не ходит

Рендер дашбордов

Адаптер Grafana

Datalens — заменяется один модуль, ради этого адаптер и делался сменным

HTML-отчёты

Без изменений: BI здесь не участвует

Ядро не зависит от краёв. Интроспекция, спеки дашбордов и валидатор обращаются к system.tables и system.columns, а не к тому, чем эти таблицы наполнены. Заменяются только края — источник описаний, планировщик и таргет рендера.

Главное следствие: семантический слой не должен дублировать dbt. Описания моделей и колонок команда уже поддерживает в schema.yml под ревью в PR; держать их ещё и в Postgres значит гарантированно получить расхождение. За MCP остаётся то, чего в dbt нет: профилирование фактических значений (accepted_values описывает намерение, профиль — что в таблице лежит на самом деле) и записи, дописанные агентом по ходу работы.

Подробнее, с диаграммой и разбором по компонентам — в Живой схеме, раздел «Интеграция с инфраструктурой заказчика». Там же перечислено, что нужно выяснить: используется ли dbt Semantic Layer, насколько полон API Datalens, есть ли доступ к манифесту.


Что сделано и что дальше

Полный статус с обоснованиями — в Живой схеме, разделы «Что реализовано» и «Возможности развития».

Работает: интроспекция, семантический слой, спеки дашбордов с уровнями поддержки, валидатор с четырьмя статусами виджета, адаптер Grafana, HTML-отчёты, снимки схемы и реакция на миграции, HTTP-транспорт с аутентификацией, пулы соединений, файловые секреты.

Направления развития, по убыванию отношения пользы к трудозатратам:

  1. Читатель manifest.json. Убирает дублирование описаний с dbt — наибольшая польза и снятие риска расхождения.

  2. Адаптер Datalens. Grafana здесь MVP-таргет; сначала разведка API.

  3. CLI для Prefect. Логика снимков и валидации готова, не хватает вызова извне MCP.

  4. Автообогащение черновиков. С dbt сильно упрощается: описания приходят из манифеста, агенту остаётся профилирование значений.

  5. Агент-починщик. dash_list_broken уже отдаёт ошибку и актуальную схему — не хватает цикла, который скармливает это агенту; естественное место для него — flow в Prefect.

  6. Разделение прав. Любой обладатель токена может заархивировать чужой дашборд. Молчаливое затирание уже закрыто проверкой занятого uid.

  7. Приоритет очереди ревью по частоте использования таблиц из system.query_log.

  8. Кросс-фильтрация и drill-down — только на собственном рендерере, между таргетами не портируется.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    A
    maintenance
    Production-ready MCP server designed to empower AI agents and LLMs to interact seamlessly with ClickHouse. It exposes your ClickHouse database as a set of standardized tools and resources that adhere to the MCP protocol, making it easy for agents built on OpenAI, Claude, or other platforms to query, explore, and analyse your data.
    37
  • A
    license
    Not graded
    quality
    A
    maintenance
    Agent-native semantic layer, letting AI agents query databases through specifying intent instead of writing SQL, then compiling structured queries into correct, dialect-aware SQL. Dynamic and expressive, supporting multi-stage queries, time-shifts, and complex join schemas.
    166
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to query and manage ClickHouse databases, supporting SELECT queries, DDL/DML statements, and metadata listing.
    5
    15
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to securely interact with multiple databases (MySQL, PostgreSQL) via natural language queries, with cross-database querying and enterprise-grade security.
    21
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ivanmandat/clickhouse-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server