MCP-сервер к внутреннему озеру данных по российским бизнес-субъектам: 1,16 млн компаний, 48 тыс. сделок, 3 млн строк финотчётности. Сотрудник спрашивает на естественном языке — агент сам формирует запрос, ходит в PostgreSQL и возвращает ответ.
Всё живёт в PostgreSQL 15 и отдаётся агенту через представления схемы
mcp. Базовых таблиц для агента не существует. Объёмы на полосах —
квадратичная шкала, иначе сделки стали бы волосяными.
Плюс 750 тыс. email и 559 тыс. телефонов в контактном контуре — они не на этой полосе, потому что дальше в раздел о безопасности. 43% субъектов — ИП, ответ только по юрлицам теряет их молча.
и при этом
разграничение — по данным,
а не по людям.
Агент не имеет прямого доступа к базе: он вызывает инструменты сервера, те выполняют запросы под read-only ролями. Каждый запрос привязан к сотруднику.
Если агент не понял вопрос, запрос будет валидным, а число — неверным. Поэтому инструкция требует показывать SQL и называть трактовку там, где от неё зависит число: «компания» — юрлицо, ИП или оба; «выручка» — наши продажи или выручка контрагента по ФНС.
Готовые отчёты нужны там, где ручной SQL легко даёт правдоподобный, но неверный результат: привязка заказа к юрлицу и к ИП считается в обеих ветвях сразу, группы товаров берутся из справочника, а не из текстового поля выгрузки.
query_marts --sql "SELECT …"Один SELECT по представлениям схемы mcp. Основной рабочий
инструмент — агент пишет запрос сам. Только чтение, лимит 1000 строк.
describe_data --view_name v_orderВитрины, колонки с типами, пояснения к ловушкам — amount против
sum, type_activity — это не ОКВЭД — и перечень
того, чего в базе нет.
find_subject_by_name --name "…"Остальные инструменты принимают только ИНН. Сначала поиск по названию, потом всё остальное.
get_contacts_by_innТелефон, email, ФИО и адреса — по одному ИНН за вызов, под отдельной ролью. Массовая выгрузка невозможна архитектурно.
top_clients_by_revenue · find_leadsТоп покупателей, профиль контрагента, продажи по периодам и группам товаров, поиск лидов по холодной базе, финотчётность по ИНН, клиенты без сделок, отчёт о качестве данных.
column "registration_date" does not existПолей, которых нет в базе, в представлениях нет намеренно: похожих колонок не существует, подставить нечего. Запрос по ним даёт ошибку, а не пустую выборку.
Read-only выставлен на самой роли Postgres, а не в коде: обойти настройку роли нельзя. Контакты доступны только поштучно: чтобы выгрузить 750 тысяч контактов, нужно 750 тысяч вызовов — и это видно в логах на первой сотне.
Модель внешняя: каждая отданная строка покидает контур компании. Это осознанное решение, отсюда лимиты на выдачу и поштучный доступ к контактам. Сервер не должен быть доступен из интернета: только корпоративная сеть или VPN.
Предел в 1000 строк — не про безопасность, а про физику: строка из десяти колонок стоит 30–60 токенов, тысяча строк съедает почти всё окно модели. Плюс модель внешняя. Поэтому выгрузка идёт мимо модели: SQL не дублируется — скрипт берёт запрос из того же tools.yaml.
$ python mcp/export.py --tool find_leads \ --param region=77 --param min_employees=100 \ --param require_site=true --param row_limit=20000 Готово за 3.0 с файл: outputs/leads.csv (1.2 MB) строк: 4100 колонок: 10 — subject_type, inn, name, region, …
claude mcp add --transport http datalake \
https://mcp-data-lake.first-bit.ru/mcp \
--client-id 389975564900892674 -s user
$ codex mcp add datalake \ --url https://mcp-data-lake.first-bit.ru/mcp \ --oauth-client-id 389975564900892674 $ codex mcp login datalake
// ~/.config/opencode/opencode.json "mcp": { "datalake": { "type": "remote", "url": "https://mcp-data-lake.first-bit.ru/mcp", "enabled": true, "oauth": { "clientId": "389975564900892674" } }} $ opencode mcp auth datalake
// в настройках коннектора — два значения:
адрес сервера https://mcp-data-lake.first-bit.ru/mcp
client id 389975564900892674
Вход — рабочей учётной записью, той же, что в GitLab и вики. После подключения перезапустить клиент: инструменты читаются при старте сессии. Подробно про каждый клиент — в CONNECT.md.