Initial commit: gp-mcp server

MCP stdio server for Greenplum 6.x query plan evaluation:
- explain_sql / explain_dbt_model tools
- read-only session enforcement + statement_timeout
- dbt compile integration
- all settings via env vars (no hardcoded defaults)
This commit is contained in:
2026-05-31 14:06:21 +03:00
commit 7c9487e0f9
10 changed files with 843 additions and 0 deletions

301
README.md Normal file
View File

@@ -0,0 +1,301 @@
# gp-mcp
MCP-сервер для оценки плана запросов dbt-моделей в Greenplum 6.x.
Запускается локально по `stdio` рядом с AI-агентом, который рефакторит легаси PL/SQL
в dbt-модели. Сервер:
1. компилирует выбранную dbt-модель (`dbt compile --select <model>`);
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
},
"plan": [ /* raw EXPLAIN JSON */ ],
"statement_timeout_ms": 300000,
"compiled_sql": "select ...",
"model_name": "fct_orders"
}
```
## Установка
```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 <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-ы — единственная надёжная защита.
## Запуск
Локально (для отладки):
```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
```