Прямое подключение OpenClaw к базе данных — один из мощнейших сценариев: агент пишет SQL-запросы, анализирует данные, генерирует отчёты и помогает с миграциями. При правильной настройке это безопасно даже для production.
Выбор подхода
Есть два способа дать OpenClaw доступ к PostgreSQL:
1. Готовый MCP-сервер — быстрая установка, базовый функционал 2. Кастомный MCP-сервер — полный контроль, специфичные инструменты
Начнём с готового.
Установка готового MCP-сервера PostgreSQL
npm install -g @modelcontextprotocol/server-postgres
Конфигурация OpenClaw:
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"postgresql://user:password@localhost:5432/mydb"
]
}
}
}
Важно для безопасности: используйте read-only пользователя для анализа данных:
-- В PostgreSQL
CREATE USER openclaw_readonly WITH PASSWORD 'secure_pass';
GRANT CONNECT ON DATABASE mydb TO openclaw_readonly;
GRANT USAGE ON SCHEMA public TO openclaw_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO openclaw_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO openclaw_readonly;
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"postgresql://openclaw_readonly:secure_pass@localhost:5432/mydb"
]
}
}
}
Аналитика данных без SQL
После подключения можно задавать вопросы на русском:
> Сколько новых пользователей зарегистрировалось за последние 30 дней?
Разбей по неделям.
> Какие 10 товаров принесли наибольшую выручку в Q1 2026?
Выведи: товар, количество продаж, сумма, доля от общей выручки.
> Найди пользователей которые зарегистрировались > 6 месяцев назад,
но ни разу не совершили покупку. Сколько их?
Агент сам составит SQL-запрос, выполнит и интерпретирует результат.
Кастомный MCP-сервер с защитой
Для production нужен контроль над тем какие запросы может выполнять агент:
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 5,
idleTimeoutMillis: 30000,
});
// Белый список таблиц к которым разрешён доступ
const ALLOWED_TABLES = ["orders", "products", "users_public", "reports"];
// Белый список операций
const ALLOWED_OPERATIONS = ["SELECT"];
function validateQuery(sql: string): void {
const upper = sql.toUpperCase().trim();
// Разрешаем только SELECT
if (!upper.startsWith("SELECT")) {
throw new Error("Only SELECT queries are allowed");
}
// Запрещаем опасные ключевые слова
const forbidden = ["DROP", "DELETE", "UPDATE", "INSERT", "TRUNCATE", "ALTER", "EXEC", "EXECUTE"];
for (const keyword of forbidden) {
if (upper.includes(keyword)) {
throw new Error(`Keyword ${keyword} is not allowed`);
}
}
}
const server = new Server(
{ name: "postgres-safe", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "query",
description: "Выполнить SELECT-запрос к базе данных. Только чтение, не изменяет данные.",
inputSchema: {
type: "object",
properties: {
sql: { type: "string", description: "SQL SELECT запрос" },
limit: { type: "number", description: "Максимум строк (default: 100, max: 1000)", default: 100 },
},
required: ["sql"],
},
},
{
name: "describe_table",
description: "Получить структуру таблицы: колонки, типы, индексы",
inputSchema: {
type: "object",
properties: {
table_name: { type: "string" },
},
required: ["table_name"],
},
},
{
name: "list_tables",
description: "Список доступных таблиц",
inputSchema: { type: "object", properties: {} },
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "query") {
const { sql, limit = 100 } = args as { sql: string; limit?: number };
validateQuery(sql);
const safeLimit = Math.min(limit, 1000);
const wrappedSql = `SELECT * FROM (${sql}) AS q LIMIT ${safeLimit}`;
const result = await pool.query(wrappedSql);
return {
content: [{
type: "text",
text: JSON.stringify({
rows: result.rows,
rowCount: result.rowCount,
fields: result.fields.map(f => ({ name: f.name, dataTypeID: f.dataTypeID })),
}, null, 2),
}],
};
}
if (name === "describe_table") {
const { table_name } = args as { table_name: string };
const result = await pool.query(`
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_name = $1
ORDER BY ordinal_position
`, [table_name]);
return {
content: [{ type: "text", text: JSON.stringify(result.rows, null, 2) }],
};
}
if (name === "list_tables") {
const result = await pool.query(`
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
ORDER BY table_name
`);
return {
content: [{ type: "text", text: result.rows.map(r => r.table_name).join("\n") }],
};
}
throw new Error("Unknown tool");
});
const transport = new StdioServerTransport();
await server.connect(transport);
Сценарии использования
Анализ данных для отчёта
> Подготовь данные для ежемесячного отчёта по продажам.
База: таблицы orders, order_items, products, users.
Нужно:
1. Выручка за март 2026 total и по категориям
2. Топ-10 товаров по выручке
3. Средний чек по неделям (динамика)
4. Новые vs возвращающиеся покупатели (% от total)
Выведи в виде JSON, я потом сформирую Excel.
Поиск аномалий
> Найди потенциально подозрительные транзакции за последние 7 дней:
- Суммы > 3σ от среднего по пользователю
- Один пользователь, несколько транзакций за < 5 минут
- Транзакции с одинаковой суммой от разных пользователей в течение часа
Вернуть: transaction_id, user_id, amount, created_at, тип аномалии.
Помощь с миграциями
> Мне нужно добавить мягкое удаление (soft delete) к таблице products.
Сейчас: id, name, price, created_at.
Нужно: добавить поле deleted_at (nullable timestamp).
Напиши:
1. Миграцию (SQL)
2. Индекс для оптимизации WHERE deleted_at IS NULL
3. Как обновить существующие запросы чтобы фильтровать удалённые
Оптимизация запросов
> Вот медленный запрос (< 1 секунда ожидаем, сейчас 8 сек):
SELECT u.*, COUNT(o.id) as order_count, SUM(o.total) as total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2025-01-01'
GROUP BY u.id
Посмотри на структуру таблиц через describe_table и предложи:
1. Нужные индексы
2. Переписанный запрос (если нужно)
3. Объяснение почему это быстрее
Работа с большими таблицами
Для таблиц с миллионами строк используйте LIMIT и пагинацию:
> Таблица events содержит 50 млн строк. Мне нужна агрегация за год.
Используй оконные функции и НЕ делай SELECT * без LIMIT.
Проверь через EXPLAIN ANALYZE что запрос использует индексы.
Агент будет осторожен с большими таблицами если вы это укажете в системном промпте.
Безопасность: чек-лист
- Используйте read-only пользователя PostgreSQL
- Никогда не подключайте production-БД напрямую к агенту с write-правами
- Ограничьте доступ только к нужным таблицам (GRANT SELECT на конкретные таблицы)
- Включите SSL для подключения
- Логируйте все запросы агента (pg_audit или application-level logging)
- Для аналитики используйте read-реплику, не master
Итог
PostgreSQL + OpenClaw = аналитик данных доступный 24/7. Нет нужды в запросах к DBA для каждого отчёта — агент пишет запросы сам. При правильном разграничении прав риски минимальны.
Читайте также: Создание MCP-сервера и RAG для своих данных.