Detects 7 common GP plan anti-patterns (broadcast of large table, redistribute of large table, hot node, nested loop with large outer, large sort, spill to disk, stale row estimates, planning-heavy queries) and surfaces them as summary.warnings[] with severity + evidence. Raw plan and raw summary metrics are preserved so the agent can verify each warning against the underlying numbers.
gp-mcp
MCP-сервер для оценки плана запросов dbt-моделей в Greenplum 6.x.
Запускается локально по stdio рядом с AI-агентом, который рефакторит легаси PL/SQL
в dbt-модели. Сервер:
- компилирует выбранную dbt-модель (
dbt compile --select <model>); - подключается к Greenplum под read-only пользователем
(
SET default_transaction_read_only = on,statement_timeout); - выполняет
EXPLAIN (ANALYZE, VERBOSE, FORMAT JSON); - возвращает 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:
{
"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
(ROWS_MISESTIMATION_FACTOR, BROADCAST_LARGE_ROWS, …), калибруются под кластер.
Установка
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
Сервер требует, чтобы доступ был ограничен на уровне БД. Минимум:
CREATE ROLE dbt_explain LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE <db> TO dbt_explain;
GRANT USAGE ON SCHEMA <schema> TO dbt_explain;
GRANT SELECT ON ALL TABLES IN SCHEMA <schema> TO dbt_explain;
ALTER DEFAULT PRIVILEGES IN SCHEMA <schema>
GRANT SELECT ON TABLES TO dbt_explain;
Сервер дополнительно ставит сессионный default_transaction_read_only = on,
но GRANT-ы — единственная надёжная защита.
Запуск
Локально (для отладки):
python -m gp_mcp.server
Сервер ничего не печатает в stdout (это канал MCP) — все логи идут в stderr.
Подключение к клиенту
Сервер общается по stdio, поэтому клиент должен сам его запускать.
Конфиг — стандартный MCP JSON: одинаковая форма для Claude Code и Cursor,
различаются только пути к файлам настроек.
Общий блок, который пригодится ниже:
{
"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 (быстрее всего)
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
{
"mcpServers": {
"gp-mcp": { /* см. общий блок выше */ }
}
}
3. Вручную, project-scope: .mcp.json в корне dbt-репозитория
{
"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"
}
}
}
}
Проверка:
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
Доступно во всех проектах.
{
"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