Skip to content

Releases: dbelokursky/pg-explorer-mcp

pg-explorer-mcp 0.8.0

Choose a tag to compare

@github-actions github-actions released this 28 Oct 16:57
bc27439
  • Инструмент schema_diagram во всех профилях рисует модель данных: ответ — текст диаграммы Mermaid
    (erDiagram), который агент показывает в блоке ```mermaid, и пользователь видит картинку там, где читает:
    в GitHub, Obsidian, VS Code или в чате агента. Схема целиком — таблицы с первичными, уникальными и внешними ключами и
    связи между ними, в том числе к таблицам других схем, на которые схема ссылается; или одна таблица с соседями
    по внешним ключам в обе стороны (table, `depth` до 3). По умолчанию только ключевые колонки, `columns: all` —
    все колонки с комментариями, `none` — только таблицы и связи. Кардинальность берётся из каталога: `}o--||` у
    ключа из колонок NOT NULL, `}o--o|` у необязательного, `|o--||` у ключа на уникальных колонках. Диаграмма
    укладывается в бюджет ответа целиком: когда не влезает, опускаются колонки, потом ключи, а о слишком большой
    схеме сервер говорит, как нарисовать часть; обрезанный текст диаграммы не рендерился бы. На стендах те же
    `stand`, `schema` и `database`, а строка `стенд / база / схема` идёт комментарием Mermaid, чтобы ответ
    рендерился как есть. Команда `/pg:diagram` просит агента показать диаграмму и сохранить её в файл рядом с
    проектом. Список инструментов стал длиннее примерно на тысячу символов (ADR 0008).
  • Стенд, где одна учётка читает много схем, работает целиком. Реестр считал схему именем учётки: импорт из DBeaver
    записывал подключение ИФТ-Caldera как [ift-caldera.caldera_ift1.cldr_testers_user], и сервер видел на стенде
    одну схему cldr_testers_user, которой в базе нет: любая настоящая из 61 схемы отвергалась ещё до подключения,
    list_tables отвечал «нет таблиц в cldr_testers_user», а имена без схемы давали 42P01. Теперь база, у всех
    секций которой одна учётка, читается целиком через неё: schema принимает любую схему, list_schemas и
    list_tables без схемы показывают то, что учётка читает по правам, а describe_table находит таблицу в любой
    из этих схем. База с учёткой на схему работает как раньше, только по схемам реестра; если на стенде несколько
    баз, читаемых целиком, ошибка просит database. Секции на каждую настоящую схему с user= больше не нужны.
  • Стенд за пулером соединений в transaction-режиме (pgbouncer, Odyssey) работает целиком. Раньше там отвечали
    только run_select и explain_query, а status, list_tables, describe_table, search_schema,
    sample_rows и compare_schema падали с prepared statement "pg8000_statement_0" does not exist
    (SQLSTATE 26000): запросы к каталогу шли именованным prepared statement в трёх сообщениях без транзакции, и
    пулер отдавал их разным серверным сессиям или сбрасывал сессию между ними. К тому же statement_timeout и
    search_path, заданные при подключении, после такого сброса исчезали: таймаут не действовал, а имена без
    схемы не находились. Теперь каждый вызов — одна транзакция READ ONLY, которая в той же посылке задаёт
    SET LOCAL statement_timeout и SET LOCAL search_path, а запрос идёт безымянным statement. Интеграционный
    тест гоняет инструменты через настоящий pgbouncer с DISCARD ALL после каждой транзакции.
  • Импорт из DBeaver берёт базу и хосты сначала из URL и только потом из полей: у подключения «по URL» поля держат
    значения по умолчанию, базу postgres среди них. Подключение, в котором шаблон стенда ничего не нашёл ни в папке,
    ни в имени, ни в хосте, получает стендом своё имя (IFT-Caldera → ift-caldera, DEV-indiamall →
    dev-indiamall), и предпросмотр это отмечает; раньше такие подключения пропускались. Шаблон по умолчанию
    принимает разделитель между буквами и числом, (dev|ift)[-_ ]?\d+, а найденный стенд его теряет: DEV-27
    становится dev27 и складывается в диапазон с соседями.
  • Стенд может жить на нескольких хостах. В реестре host=h1,h2 с одним port или port=6544,6545 — по порту на
    хост, как в libpq: сервер пробует хосты по порядку и берёт первый, который пустил, а если не пустил ни один —
    называет каждый. Так же читаются PGHOST и PGPORT записи на одну базу. Импорт из DBeaver сохраняет оба хоста
    подключения, как их пишет DBeaver (h1:6544,h2 с портом 6544 или URL jdbc:postgresql://h1:6544,h2:6544/db),
    и кладёт в файл паролей строку на каждый хост; раньше такое подключение попадало в реестр с хостом h1:6544,h2,
    которого нет в DNS. Выгрузка отдаёт все хосты psql, DBeaver и DataGrip; pgAdmin и расширению PostgreSQL для
    VS Code — когда порт у хостов один, иначе первый хост; SQLTools получает первый хост.
  • Проверка SQL отвергает идентификаторы в записи U&"...". PostgreSQL раскрывает в них escape-последовательности,
    а проверка читала имя как написано: SELECT U&"pg\005fsleep"(0) проходил её и выполнялся, и так же прошла бы
    pg_terminate_backend, которой любая роль обрывает сессии своей же роли, а у группы роль одна. Отказ говорит
    написать имя как есть; модели такая запись не нужна.
  • Стенды опрашиваются одновременно. run_select, search_schema и compare_schema со stands спрашивают
    все стенды вызова сразу, по потоку на стенд, и вызов длится столько, сколько самый медленный стенд, а не
    все вместе; предел — 100 стендов за вызов вместо 10. Раньше стенды шли по очереди, и перед каждым
    следующим сервер резервировал 15 секунд из 25: даже когда каждый стенд отвечал за 3 секунды, вызов успевал
    около четырёх, а «на каком стенде заказ» по 53 стендам требовало 13 вызовов. Стенд, который за 25 секунд не
    ответил, называется в конце ответа. Стенды, ответившие без строк или без совпадений, сворачиваются в одну
    строку (no rows on dev1–dev6, dev8–dev50), чтобы стенд с находкой был виден сразу.
  • Вшитые пакеты видны GitHub: src/pg_explorer_mcp/_vendor/requirements.txt перечисляет их точные версии, граф
    зависимостей читает его как манифест pip, и об уязвимости в pg8000 или его зависимостях придёт Dependabot
    alert — раньше GitHub не знал, что они в репозитории есть. scripts/check_vendor.py сверяет манифест с
    *.dist-info: пакет без строки, строка без пакета и другая версия — ошибка.
  • Новое имя — pg-explorer-mcp: репозиторий dbelokursky/pg-explorer-mcp, архив pg-explorer-mcp-X.Y.Z.zip с
    папкой pg-explorer-mcp/, пакет pg_explorer_mcp (python -m pg_explorer_mcp), команда pg-explorer-mcp,
    расширение pg-explorer-mcp, переменные PG_EXPLORER_MCP_*, application_name = 'pg-explorer-mcp'. Под
    именем pg-mcp на PyPI живёт другой проект с тем же пакетом pg_mcp и той же командой pg-mcp: pip принимал
    бы один за другой — pip install -U pg-mcp заменил бы копию из git чужим пакетом, — а каталоги MCP путали бы
    их (ADR 0007).
  • Установленное раньше продолжает работать. Записи агентов со старыми PG_MCP_* читаются как прежде: новое имя
    важнее старого, а повторный install.py пишет только новые. Реестр стендов из ~/.pg-mcp/ читается, пока в
    новой папке нет своего. Разделы pg_service.conf под строкой # pg-mcp export выгрузка узнаёт и переписывает
    под новой. Подключения в DBeaver и VS Code обновляются те же: у них прежние id.
  • Что сделать при переходе. Клону — git pull и перезапуск агента. Распакованному архиву — распаковать новый и
    один раз запустить install.py из папки pg-explorer-mcp/, затем заново подключить расширение. Если GigaCode
    отвечает, что расширение уже установлено, удалите ~/.gigacode/extensions/pg-mcp (для Qwen Code —
    ~/.qwen/extensions/pg-mcp): клиент сам её не удаляет, а install.py называет её, если находит. Расширение,
    которое выключали, после перехода снова включено. DBA, которые отбирают подключения сервера по
    application_name = 'pg-mcp', должны сменить фильтр.

pg-mcp 0.7.0

Choose a tag to compare

@github-actions github-actions released this 25 Oct 07:48
e2e948f
  • Угаданная схема больше не тупик. Если в названной схеме нет ни одной таблицы (обычно это угаданная моделью
    public), list_tables и search_schema отвечают таблицами всех схем, а первая строка говорит, что в той
    схеме таблиц нет; describe_table и sample_rows находят таблицу по имени, в том числе по полному, как
    shop.orders. В прогоне с моделью она получила на list_tables со schema: public ошибку со списком схем
    и вместо нового вызова спросила пользователя.
  • Схема, где таблицы есть, но роль их не читает, как hr в демо-базе, по-прежнему получает отказ, и теперь он
    прямо говорит, что роль не может читать ни одну её таблицу: прежнее «нет таблиц, доступных для чтения»
    модель пересказывала как «в схеме hr таблиц нет». Про системные схемы ответ говорит, что list_tables и
    search_schema их не показывают. list_tables принимает схему в кавычках и с пробелами, как остальные
    инструменты.
  • Ещё три находки прогона с моделью. compare_schema пишет имена со схемой, как list_tables
    (shop.returns | table): таблицу, которая есть только на одном стенде, модель ни разу не упомянула — похоже,
    строку returns | table она читала как слова. Ошибка стенда о пропущенной или неизвестной схеме сразу
    называет его схемы, а не только отсылает к list_schemas; если схем больше пятнадцати — первые пятнадцать и
    сколько всего. Ошибка 42703 о колонке у run_select и explain_query перечисляет колонки таблиц запроса,
    которые роль читает: таблицу to_regclass находит так же, как её нашёл запрос.
  • status сразу после имени сервера говорит, что сервер только читает и что просьбу изменить данные надо отклонить, а
    не просить её подтвердить. В прогоне с моделью она видела «read only = on» и всё равно предлагала удалить
    заказы; ответ status, в отличие от описаний инструментов, не ограничен по размеру.
  • Описание команды /pg:setup больше не обещает проверить подключение: это делает инструмент status, а
    команда — для установки и починки, когда инструментов нет или status говорит, что база недоступна. На
    «Check the database connection» модель выбрала /pg:setup — описание почти дословно повторяло вопрос —
    и, не сумев запустить команду без подтверждения, так и не вызвала status.
  • Описания инструментов без лишних полей: destructiveHint и idempotentHint при readOnlyHint ничего не
    значат, а название осталось только в annotations, где его находят клиенты любых версий протокола. Список
    инструментов для стендов стал короче на 700 символов — запас для будущих правок описаний.
  • На стендах модель знает стенды до первого вызова. Описание аргумента stand называет стенды реестра
    (dev1–dev50, ift1–ift3; если записей больше двенадцати — первые и сколько всего), а не пример "dev7".
    Список берётся при запуске сервера, а status и ошибки называют стенды такими, какие они сейчас. Если
    run_select на одном стенде не вернул строк, ответ называет другие стенды с той же схемой и напоминает про
    stands. В прогоне с моделью на вопрос, на каком стенде заказ, она смотрела только dev7 из примера,
    ничего не находила и называла стенды, которых нет; по-английски сценарий проходил в 5 случаях из 9.
  • run_select и search_schema понимают шаблон или список и в stand ("*", "dev7,dev12") — как
    stands: так модель спрашивала все стенды сразу и получала «No stand '*'». Имя стенда не может содержать
    *, ?, [ или запятую, поэтому путаницы нет.
  • Ошибка о неизвестном стенде у run_select и search_schema говорит, как посмотреть на всех стендах:
    stands: "*", а если стендов больше десяти — список или шаблон, не больше десяти за вызов. Узнав только,
    что стенда pg (имени сервера) нет, модель спрашивала пользователя, какой стенд взять.
  • На стенде run_select, explain_query и compare_schema без схемы (модель присылает schema: "")
    отвечают ошибкой стенда со списком его схем, а не одним «Missing argument(s): schema.»: после неё модель
    тратила вызов на list_schemas.
  • В журнале запросов у каждой строки есть версия Python (python), а scripts/querylog_report.py
    показывает, сколько пользователей на какой версии: так видно, во что обойдётся подъём минимальной.
  • scripts/agent_eval.py проверяет и стенды: демо-база становится стендом dev7, а её изменённая копия на
    время прогона — стендом dev12, и модель отвечает через pg-stands, какие стенды есть, чем отличается схема,
    где лежит заказ и сколько заказов на dev12 (--mode single|stands|both). --repeat N задаёт каждый вопрос
    несколько раз и печатает долю успехов и ответы с ошибкой, которые повторялись.
  • В scripts/agent_eval.py заказ, который есть только на копии dev12, теперь 9001, а не 501: в копии 501
    заказ, и модель принимала «~501 rows» в списке таблиц за этот заказ, не проверив его запросом.
  • CI проверяет выгрузку настоящими программами: psql подключается по секции, которую пишет install.py export pg-service, а pgAdmin 9.18 загружает файл серверов и отдаёт его обратно без расхождений. Полный набор CI с
    macOS идёт и ночью на develop: слияние, которое сделало автослияние, push-прогона не запускает. PR в main
    тоже вливаются автослиянием: у main теперь те же обязательные проверки, что у develop.

pg-mcp 0.6.0

Choose a tag to compare

@github-actions github-actions released this 23 Oct 19:06
6bbfad8
  • Запись сервера для других клиентов MCP: install.py --client пишет её в файл и в формате VS Code
    (GitHub Copilot), Cursor, Windsurf, Cline, Roo Code, Continue, LM Studio, Claude Desktop и Junie, а для
    JetBrains AI Assistant печатает, что вставить в настройки; --print только показывает запись и куда её
    положить. Файл с комментариями не переписывается — печатается, что добавить. В терминале install.py
    находит другие установленные клиенты и предлагает зарегистрировать сервер и там.
  • Выгрузка подключений к стендам в инструменты БД: install.py export для DBeaver, DataGrip и других
    IDE JetBrains, pgAdmin 4, psql и pgcli (pg_service.conf), расширений VS Code PostgreSQL (Microsoft)
    и SQLTools — предпросмотр по умолчанию, запись с --write, --stands выбирает стенды. DBeaver
    получает свой файл data-sources-pg-mcp.json с папкой на стенд, DataGrip — проект
    ~/.pg-mcp/datagrip (или --project), pgAdmin — servers.json для импорта, psql — секции со схемой
    первой в search_path, расширения VS Code — подключения в настройках пользователя (--editor для
    Cursor и Windsurf). Пароли не копируются: подключения входят по файлу паролей, только расширение
    Microsoft его не читает и спрашивает пароль один раз. Выгрузка заменяет только свои подключения,
    оставляет датированные копии и права файлов (файл-ссылка остаётся ссылкой), пропускает то, что уже
    есть в DBeaver, и переносит sslmode и sslrootcert. В ~/.pg_service.conf выгруженные секции стоят
    под строкой # pg-mcp export: следующая выгрузка переписывает только их, сервер и импорт не считают
    их стендами, а одноимённые секции пользователя не трогаются. Файл настроек с комментариями не
    переписывается — печатается, что добавить.
    В терминале install.py после импорта и регистрации находит эти инструменты и предлагает выгрузку.
  • Модели реже ошибаются с первого вызова — по находкам прогона scripts/agent_eval.py. Пустая строка в
    необязательном аргументе считается пропущенным аргументом. Из описаний и ошибок убран пример "public",
    который модель подставляла буквально. Ошибка PostgreSQL о ненайденной таблице (42P01) у run_select и
    explain_query называет схемы, где таблица с таким именем есть; на стенде — схемы реестра, с которыми
    повторить вызов. search_schema в схеме без доступных таблиц — модель по привычке берёт public —
    называет схемы с таблицами, как list_tables. Описания status и run_select, инструкции сервера и
    отказ проверки SQL прямо говорят, что сервер только читает и просьбы изменить данные надо отклонять, а не
    просить их подтвердить.
  • scripts/querylog_report.py — сводка по журналу запросов для администратора группы: один или несколько
    файлов (ротированные .1 и .gz тоже), окно --since/--until в UTC; вызовы по инструментам с долей
    ошибок, отказов проверки SQL, недоступной базы и сбоев сервера, по пользователям ОС (вызовы, запросы, строки)
    и по стендам (вызов на несколько стендов засчитывается каждому), медиана и 95-й процентиль duration_ms,
    десять самых долгих вызовов и все отклонённые проверкой SQL запросы; --format json и --format csv в
    разрезе --by. Аргументы обрезаются до 80 символов, значения ключей, похожих на пароль, скрыты; битые
    строки пропускаются и считаются.
  • scripts/agent_eval.py — ручная проверка с настоящей моделью перед выпуском (в CI модели нет): в одноразовой
    домашней папке регистрирует сервер в Qwen Code на демо-базе, задаёт типовые вопросы по-русски и по-английски
    модели из Ollama (или другого OpenAI-совместимого эндпоинта) и по журналу запросов сверяет, какие
    инструменты она вызвала и что ответила; просьба удалить должна кончиться отказом сервера или модели, заказы
    после неё пересчитываются. Итог — таблица и счёт. Раздел в CONTRIBUTING.md; разбор журнала и сверка со
    сценариями покрыты тестами без модели.
  • PR в develop вливаются сами, когда зелёные все обязательные проверки: workflow
    .github/workflows/automerge.yml включает на них автослияние GitHub. Черновики, PR из форков и PR
    в main вливаются вручную.

pg-mcp 0.5.0

Choose a tag to compare

@github-actions github-actions released this 23 Oct 09:26
0145465
  • Импорт подключений к стендам: install.py import dbeaver и install.py import pg-service <файл> —
    предпросмотр по умолчанию, запись с --write: реестр ~/.pg-mcp/stands.conf (дополняется, с --prune
    заменяется), пароли в файл паролей (только добавляются и обновляются), датированные копии обоих, запись
    pg-stands рядом с pg; при сбое ничего не меняется. Стенд распознаётся по папке DBeaver, имени
    подключения и хосту (--stand-pattern), схема — учётка подключения. Сохранённые пароли DBeaver
    расшифровываются своим AES-128-CBC (src/pg_mcp/aes.py), без зависимостей. В терминале install.py
    предлагает предпросмотр, найдя DBeaver; агенту — команда /pg:import.
  • Сервер перечитывает реестр стендов, когда его файлы изменились: импорт виден без перезапуска агента.
    Если после изменения стендов прочитать не удалось, остаются прежние, а status предупреждает.
  • Реестр стендов со строкой password=, который могут читать другие пользователи, даёт предупреждение
    в status с подсказкой chmod 600.
  • compare_schema сравнивает один стенд с несколькими: stands вместо stand_b (список или шаблон, не
    больше 10). Эталон читается один раз, стенды — по очереди в бюджете вызова; до трёх стендов получают
    полные различия, больше — строку с числами; в конце итог «как эталон / отличаются / не проверены».
    Недоступный стенд или стенд без учётки схемы не мешает остальным; журнал запросов пишет итог по стендам.
  • Демо-база из README в репозитории: docs/demo.sql строит магазин с детерминированными данными
    (50 покупателей, 20 товаров, 500 заказов — 174 shipped, 162 paid, 90 cancelled, 74 new — и 2200
    позиций) и схему hr, закрытую для агента; scripts/demo.py создаёт базу и роль агента и удаляет их
    с --drop. Тест сверяет ответы инструментов с примерами README на PostgreSQL 13–18 и Windows.
  • Репозиторий стал публичным: лицензия MIT (LICENSE), лицензии вендорных пакетов в
    THIRD_PARTY_NOTICES.md, оба файла входят в архив релиза; SECURITY.md о том, как сообщать об
    уязвимостях и где проходит граница безопасности; у архива релиза аттестация происхождения
    (gh attestation verify); CodeQL по push и раз в неделю; ветки main и develop защищены от
    удаления и force-push.

pg-mcp 0.4.0

Choose a tag to compare

@github-actions github-actions released this 23 Oct 08:04
7aff2ce
  • Вызов на несколько стендов начинает следующий стенд, только если до 25 секунд хватит и на подключение,
    и на запрос до его таймаута; раньше в запас входило только подключение, и последний стенд мог вывести
    вызов за 30 секунд, которые ждёт клиент.
  • Описания инструментов короче: в режиме стендов tools/list ужался с 9,5 до 7 тысяч символов, для одной базы —
    с 5 до 4,3 тысячи. Правила про stand, schema и database сказаны один раз в инструкциях сервера, а не в
    каждом инструменте; тест не даёт объёму расти обратно.
  • CI на PR стал вдвое дешевле: без macOS и с PostgreSQL 13, 16 и 18 вместо шести версий; полный набор
    идёт на push в develop и main. Сборка архива релиза на PR — только когда менялись её исходники.
  • Инструмент compare_schema в режиме стендов, во всех профилях: сравнивает структуру одной схемы на двух
    стендах — таблицы и представления, колонки (тип, NOT NULL, значение по умолчанию), ограничения и
    индексы — и показывает, что есть только на одном стенде и что отличается; данные не читаются. Структура
    берётся из каталога под учёткой схемы на каждом стенде, три запроса на стенд. Схема, которой нет в базе
    одного из стендов, — отдельная ошибка, а не «только на A: всё».
  • Один запрос на нескольких стендах за один вызов: в режиме стендов run_select и search_schema принимают
    вместо stand аргумент stands — имена через запятую или шаблон (dev1,dev2, dev*), не больше 10
    стендов. Стенды опрашиваются по очереди, в ответе на каждый своя секция под строкой стенд / база / схема;
    недоступный стенд, стенд без учётки схемы или ошибка запроса — одна строка под его заголовком, остальные
    стенды отвечают. Строки run_select и совпадения search_schema делятся между стендами, не меньше 10 на
    стенд; вызов укладывается в 25 секунд, остальные стенды перечисляются как не проверенные. Журнал запросов
    пишет на такой вызов одну запись с итогом по каждому стенду (stands).
  • Тесты на то, что раньше проверялось только без базы: TLS во всех режимах PGSSLMODE через
    TLS-прослойку перед настоящим PostgreSQL (сертификат делает openssl), включая отказ от чужого
    сертификата и несовпадение имени хоста; срабатывание statement_timeout на долгом запросе и то, что
    сокет его дожидается; подключение через Unix-сокет.
  • Сервер, который принял подключение и закрыл его при входе без сообщения, объясняется отдельно:
    обычно это пароль или pg_hba.conf. Так на Windows выглядит отвергнутый пароль, когда сброс
    соединения стирает сообщение сервера; раньше это была безликая «network error».
  • Много стендов в одной записи (ADR 0006). С PG_MCP_STANDS сервер читает реестр в формате
    pg_service.conf (~/.pg-mcp/stands.conf и ~/.pg_service.conf, секции стенд.база.схема), и каждый
    инструмент принимает stand, а для чтения данных — schema: сервер подключается под учёткой этой схемы.
    Новый инструмент list_schemas; структура по всему стенду берётся из каталога, только по схемам реестра;
    каждый ответ начинается со строки стенд / база / схема, журнал запросов пишет стенд и схему.
    Подключения открываются по требованию, открытыми держится не больше 8 (PG_MCP_MAX_CONNECTIONS).
    Без PG_MCP_STANDS или без стендов в реестре всё работает как раньше. Запись регистрирует
    PG_MCP_STANDS='*' install.py --name stands; проверке установки достаточно найденных стендов.
  • Быстрый отказ недоступного стенда. Хост, который не ответил (нет маршрута, таймаут, отказ порта), в
    пределах вызова больше не пробуется ни под одной учёткой, а вторая учётка базы пробуется, только когда
    сервер ответил и отверг первую: стенд за упавшим VPN стоит один таймаут, а не один на каждую учётку
    и базу, и вызов укладывается в 30 секунд ожидания клиента. «Нет маршрута до хоста» объясняется
    отдельно — подключите VPN. Хост, который принимает соединение и молчит, раньше ронял вызов ошибкой
    сервера, теперь даёт ту же подсказку про таймаут.
  • Таймаут сети разделён: подключение ждёт PG_MCP_SOCKET_TIMEOUT_S (30 секунд, для стендов 10), а
    открытый сокет — не меньше таймаута запроса плюс 5 секунд: долгий запрос кончается ошибкой PostgreSQL,
    а не обрывом соединения и повторным запуском.
  • Сервер, который принял подключение и закрыл его при входе без сообщения, объясняется отдельно:
    обычно это пароль или pg_hba.conf. Так на Windows выглядит отвергнутый пароль, когда сброс
    соединения стирает сообщение сервера; раньше это была безликая «network error».
  • Один запрос на нескольких стендах за один вызов: в режиме стендов run_select и search_schema принимают
    вместо stand аргумент stands — имена через запятую или шаблон (dev1,dev2, dev*), не больше 10
    стендов. Стенды опрашиваются по очереди, в ответе на каждый своя секция под строкой стенд / база / схема;
    недоступный стенд, стенд без учётки схемы или ошибка запроса — одна строка под его заголовком, остальные
    стенды отвечают. Строки run_select и совпадения search_schema делятся между стендами, не меньше 10 на
    стенд; вызов укладывается в 25 секунд, остальные стенды перечисляются как не проверенные. Журнал запросов
    пишет на такой вызов одну запись с итогом по каждому стенду (stands).
  • Инструмент compare_schema в режиме стендов, во всех профилях: сравнивает структуру одной схемы на двух
    стендах — таблицы и представления, колонки (тип, NOT NULL, значение по умолчанию), ограничения и
    индексы — и показывает, что есть только на одном стенде и что отличается; данные не читаются. Структура
    берётся из каталога под учёткой схемы на каждом стенде, три запроса на стенд. Схема, которой нет в базе
    одного из стендов, — отдельная ошибка, а не «только на A: всё».
  • Тесты на то, что раньше проверялось только без базы: TLS во всех режимах PGSSLMODE через
    TLS-прослойку перед настоящим PostgreSQL (сертификат делает openssl), включая отказ от чужого
    сертификата и несовпадение имени хоста; срабатывание statement_timeout на долгом запросе и то, что
    сокет его дожидается; подключение через Unix-сокет.
  • Архив для тех, у кого нет git: пуш тега vX.Y.Z собирает pg-mcp-X.Y.Z.zip из дерева тега без
    .github, проверяет, что сервер запускается из распакованного архива (install.py --check), и
    публикует GitHub Release с SHA256SUMS и заметками из раздела этой версии здесь; версия тега сверяется
    с версией в файлах. README: установка и обновление из архива.
  • Несколько баз из одного клона: install.py --name sales регистрирует сервер pg-sales рядом с pg, со
    своими подключением, профилем и языком. status показывает имя сервера (PG_MCP_NAME).
  • Интеграционные тесты на Windows с настоящим PostgreSQL: файл паролей в %APPDATA%, docs/role.sql
    через psql и install.py --check через канал, как его запускает агент.
  • install.py больше не падает, когда его вывод идёт через канал в кодировке без кириллицы или тире
    (cp1252, cp866) или когда путь к профилю содержит такие символы: недостающие знаки заменяются на «?».
  • Проверка в настоящем клиенте: CI запускает Qwen Code с сервером, который зарегистрировал install.py, —
    клиент должен подключиться к нему и увидеть расширение. scripts/client_smoke.py делает то же локально.