# gp-mcp MCP-сервер для оценки плана запросов dbt-моделей в Greenplum 6.x. Запускается локально по `stdio` рядом с AI-агентом, который рефакторит легаси PL/SQL в dbt-модели. Сервер: 1. компилирует выбранную dbt-модель (`dbt compile --select `); 2. подключается к Greenplum под read-only пользователем (`SET default_transaction_read_only = on`, `statement_timeout`); 3. выполняет `EXPLAIN (ANALYZE, VERBOSE, FORMAT JSON)`; 4. возвращает JSON-план + краткую сводку с GP-метриками (motion-узлы, самый медленный узел, ошибка оценки строк). ## Tools | Tool | Параметры | Что делает | |------|-----------|------------| | `explain_sql` | `sql: str`, `statement_timeout_ms?: int` | EXPLAIN ANALYZE для произвольного SQL | | `explain_dbt_model` | `model_name: str`, `statement_timeout_ms?: int` | `dbt compile` + EXPLAIN ANALYZE для модели | Возвращаемый JSON: ```json { "summary": { "total_cost": 12345.6, "plan_rows": 100000, "actual_rows": 98412, "execution_time_ms": 842.3, "planning_time_ms": 12.1, "slowest_node": { "node_type": "Seq Scan", "actual_total_time_ms": 700.2, "...": "..." }, "motion_nodes": [{ "node_type": "Redistribute Motion", "...": "..." }], "rows_misestimation_factor": 1.02, "warnings": [ { "code": "broadcast_large_table", "severity": "critical", "message": "Broadcast Motion ships 5,400,000 rows to every segment ...", "evidence": { "node_type": "Broadcast Motion", "actual_rows": 5400000, "...": "..." } } ] }, "plan": [ /* raw EXPLAIN JSON — пруф для каждого warning */ ], "statement_timeout_ms": 300000, "compiled_sql": "select ...", "model_name": "fct_orders" } ``` ### Warnings `summary.warnings[]` — это эвристический разбор плана для агента: какие узлы выглядят неоптимально и что с этим делать. Сырые числа из плана **остаются** в `summary.*` и `plan[]` — каждый warning несёт `evidence` со ссылкой на конкретные значения, чтобы агент мог перепроверить рекомендацию, а не доверять ей слепо. | `code` | `severity` | Триггер | |--------|------------|---------| | `rows_misestimation` | warning | plan vs actual rows расходятся в > 10× — статистика устарела | | `broadcast_large_table` | critical | `Broadcast Motion` гонит > 1M строк на все сегменты | | `redistribute_large_table` | warning | `Redistribute Motion` тасует > 10M строк — проверить distribution key | | `hot_node` | warning | Один узел занимает > 60% `execution_time_ms` | | `nested_loop_large` | critical | `Nested Loop` с > 10 000 итераций | | `large_sort` | warning | `Sort` по > 10M строк | | `spill_to_disk` | warning | Узел льётся на диск (`Disk Usage > 0`, `Sort Method: external merge Disk` и т.п.) | | `planning_heavy` | info | `planning_time_ms / execution_time_ms > 20%` | Пороги — константы в верхней части [src/gp_mcp/explain.py](src/gp_mcp/explain.py) (`ROWS_MISESTIMATION_FACTOR`, `BROADCAST_LARGE_ROWS`, …), калибруются под кластер. ## Установка ```bash cd /Users/admin/Projects/vpn python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` ## Конфигурация Все настройки — через переменные окружения. Скопируй `.env.example` в `.env` и заполни. | Переменная | Обязательная | Назначение | |------------|:-:|---| | `GP_HOST` | + | Хост Greenplum master | | `GP_PORT` | + | Порт | | `GP_USER` | + | Read-only пользователь (см. ниже) | | `GP_PASSWORD` | + | Пароль | | `GP_DATABASE` | + | Имя БД | | `GP_SCHEMA` | | `search_path`, можно через запятую | | `DBT_PROJECT_DIR` | + | Каталог dbt-проекта (содержит `dbt_project.yml`) | | `DBT_PROFILES_DIR` | + | Каталог с `profiles.yml` | | `DBT_TARGET` | + | Имя target из `profiles.yml` (напр. `dev`) | | `DBT_EXECUTABLE` | | Путь к `dbt`, по умолчанию `dbt` из PATH | | `STATEMENT_TIMEOUT_MS` | + | Дефолтный `statement_timeout` для EXPLAIN ANALYZE | | `MAX_STATEMENT_TIMEOUT_MS` | + | Верхняя граница, агент не сможет превысить | | `LOG_LEVEL` | | `DEBUG`/`INFO`/`WARNING`/`ERROR`, дефолт `INFO` | Если обязательная переменная не задана — сервер не стартует и пишет в stderr имя недостающей переменной. ## Read-only роль в Greenplum Сервер требует, чтобы доступ был ограничен на уровне БД. Минимум: ```sql CREATE ROLE dbt_explain LOGIN PASSWORD '...'; GRANT CONNECT ON DATABASE TO dbt_explain; GRANT USAGE ON SCHEMA TO dbt_explain; GRANT SELECT ON ALL TABLES IN SCHEMA TO dbt_explain; ALTER DEFAULT PRIVILEGES IN SCHEMA GRANT SELECT ON TABLES TO dbt_explain; ``` Сервер дополнительно ставит сессионный `default_transaction_read_only = on`, но GRANT-ы — единственная надёжная защита. ## Запуск Локально (для отладки): ```bash python -m gp_mcp.server ``` Сервер ничего не печатает в stdout (это канал MCP) — все логи идут в stderr. ## Подключение к клиенту Сервер общается по `stdio`, поэтому клиент должен сам его запускать. Конфиг — стандартный MCP JSON: одинаковая форма для Claude Code и Cursor, различаются только пути к файлам настроек. Общий блок, который пригодится ниже: ```json { "command": "/Users/admin/Projects/vpn/.venv/bin/python", "args": ["-m", "gp_mcp.server"], "cwd": "/Users/admin/Projects/vpn/src", "env": { "GP_HOST": "gp-master.internal", "GP_PORT": "5432", "GP_USER": "dbt_explain", "GP_PASSWORD": "REPLACE_ME", "GP_DATABASE": "analytics", "GP_SCHEMA": "analytics,public", "DBT_PROJECT_DIR": "/Users/admin/Projects/dbt-analytics", "DBT_PROFILES_DIR": "/Users/admin/.dbt", "DBT_TARGET": "dev", "STATEMENT_TIMEOUT_MS": "300000", "MAX_STATEMENT_TIMEOUT_MS": "900000", "LOG_LEVEL": "INFO" } } ``` Важно: - `command` — **абсолютный** путь к Python из venv проекта. Клиенты MCP обычно стартуют без активированного окружения, поэтому полагаться на `python` из PATH нельзя. - `cwd` указан на `src/`, чтобы Python нашёл пакет `gp_mcp` без установки (`pip install -e .` не делаем). - Секреты держим в `env` соответствующего конфига клиента, **не** в коде и **не** в репозитории. --- ### Claude Code Есть три способа добавить сервер — выбери один. **1. Через CLI (быстрее всего)** ```bash claude mcp add gp-mcp \ --scope user \ --env GP_HOST=gp-master.internal \ --env GP_PORT=5432 \ --env GP_USER=dbt_explain \ --env GP_PASSWORD=REPLACE_ME \ --env GP_DATABASE=analytics \ --env DBT_PROJECT_DIR=/Users/admin/Projects/dbt-analytics \ --env DBT_PROFILES_DIR=/Users/admin/.dbt \ --env DBT_TARGET=dev \ --env STATEMENT_TIMEOUT_MS=300000 \ --env MAX_STATEMENT_TIMEOUT_MS=900000 \ -- /Users/admin/Projects/vpn/.venv/bin/python -m gp_mcp.server ``` Флаг `--scope`: - `user` — для всех проектов (пишется в `~/.claude.json`); - `project` — общий для команды, кладётся в `.mcp.json` в корне проекта, его можно коммитить в git (секреты тогда задают через `${VAR}`-подстановку из окружения, а не хардкодом); - `local` — только в текущем проекте, только у тебя. **2. Вручную, user-scope: `~/.claude.json`** ```json { "mcpServers": { "gp-mcp": { /* см. общий блок выше */ } } } ``` **3. Вручную, project-scope: `.mcp.json` в корне dbt-репозитория** ```json { "mcpServers": { "gp-mcp": { "command": "/Users/admin/Projects/vpn/.venv/bin/python", "args": ["-m", "gp_mcp.server"], "cwd": "/Users/admin/Projects/vpn/src", "env": { "GP_HOST": "${GP_HOST}", "GP_PORT": "${GP_PORT}", "GP_USER": "${GP_USER}", "GP_PASSWORD": "${GP_PASSWORD}", "GP_DATABASE": "${GP_DATABASE}", "DBT_PROJECT_DIR": "${DBT_PROJECT_DIR}", "DBT_PROFILES_DIR": "${DBT_PROFILES_DIR}", "DBT_TARGET": "${DBT_TARGET}", "STATEMENT_TIMEOUT_MS": "300000", "MAX_STATEMENT_TIMEOUT_MS": "900000" } } } } ``` **Проверка:** ```bash claude mcp list # gp-mcp должен быть в списке claude mcp get gp-mcp # детали конфига ``` В сессии `/mcp` покажет статус подключения и список tool'ов. Если статус `failed`, посмотри `~/Library/Logs/Claude/` — сервер пишет ошибки запуска (включая отсутствующие env-переменные) в stderr. --- ### Cursor IDE Cursor использует тот же MCP-формат, но свой файл настроек. **1. Через UI** `Settings` → `Cursor Settings` → `MCP & Integrations` → `New MCP Server` → откроется `mcp.json` для редактирования. **2. Вручную, глобально: `~/.cursor/mcp.json`** Доступно во всех проектах. ```json { "mcpServers": { "gp-mcp": { "command": "/Users/admin/Projects/vpn/.venv/bin/python", "args": ["-m", "gp_mcp.server"], "cwd": "/Users/admin/Projects/vpn/src", "env": { "GP_HOST": "gp-master.internal", "GP_PORT": "5432", "GP_USER": "dbt_explain", "GP_PASSWORD": "REPLACE_ME", "GP_DATABASE": "analytics", "DBT_PROJECT_DIR": "/Users/admin/Projects/dbt-analytics", "DBT_PROFILES_DIR": "/Users/admin/.dbt", "DBT_TARGET": "dev", "STATEMENT_TIMEOUT_MS": "300000", "MAX_STATEMENT_TIMEOUT_MS": "900000" } } } } ``` **3. Вручную, для проекта: `.cursor/mcp.json` в корне dbt-репозитория** Видно только в этом проекте. Удобно, когда у разных dbt-проектов разные `DBT_PROJECT_DIR`/`DBT_TARGET`. **Проверка:** `Settings` → `MCP & Integrations` — справа от `gp-mcp` должен загореться зелёный индикатор и появиться список tool'ов (`explain_sql`, `explain_dbt_model`). В чате tools будут доступны Agent-режиму. Если индикатор красный — раскрой сервер в этом же окне, там показывается stderr запуска (включая `Configuration error: Required environment variable '...' is not set`). --- ### Общие проблемы при подключении | Симптом | Причина | |---------|---------| | `Configuration error: Required environment variable 'X' is not set` | Переменная `X` не задана в `env` конфига клиента | | `ModuleNotFoundError: No module named 'gp_mcp'` | Неверный `cwd` — должен указывать на `src/`, или Python не из venv | | `ModuleNotFoundError: No module named 'mcp'` | `command` указывает не на Python из venv, где установлены зависимости | | Сервер стартует, но tools не появляются | Клиент не перезапущен / нет permissions в Cursor для MCP | | `dbt: command not found` при вызове `explain_dbt_model` | Поставь `DBT_EXECUTABLE=/абсолютный/путь/к/dbt` в `env` | ## Структура ``` vpn/ ├── .env.example ├── .gitignore ├── requirements.txt ├── README.md └── src/ └── gp_mcp/ ├── __init__.py ├── config.py # загрузка и валидация env ├── db.py # psycopg2 + read-only + timeout ├── dbt_runner.py # subprocess dbt compile + чтение compiled SQL ├── explain.py # EXPLAIN ANALYZE + summary └── server.py # FastMCP, регистрация tools, stdio ```