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:
301
README.md
Normal file
301
README.md
Normal 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
|
||||
```
|
||||
Reference in New Issue
Block a user