docs: apply Codex review round 1

Accepted: dep major ranges, ssh client leak fix, tmp-name hardening,
dispose-ordering note, invalidate-during-build test, testable
parseArgs, coverage justification, README security sections.
Rejected with evidence: MySQL session read-only blocks DML/DDL
(verified on mysql:8.4, ERROR 1792); in-process store races
impossible (synchronous read-modify-write).
This commit is contained in:
smartass
2026-06-11 22:50:02 +05:00
parent e7e6a1dcd0
commit 40a75d342f
2 changed files with 127 additions and 31 deletions
@@ -8,7 +8,8 @@ MCP-сервер (stdio) для работы с PostgreSQL и MySQL через
## Стек
- TypeScript, ESM, Node >= 20
- `@modelcontextprotocol/sdk` — McpServer + StdioServerTransport
- `@modelcontextprotocol/server` (MCP SDK v2) — McpServer + StdioServerTransport;
`@modelcontextprotocol/client` в devDeps для тестов через InMemoryTransport
- `zod` — схемы входов тулзов и валидация конфигов
- `pg` — драйвер PostgreSQL, `mysql2` — драйвер MySQL
- `ssh2` — SSH-туннели (pure JS, не требует ssh-бинаря в Docker)
@@ -33,8 +34,11 @@ MCP-сервер (stdio) для работы с PostgreSQL и MySQL через
- Стор перечитывается при каждом обращении (resolve, list, mutate) — без
кеширования файла. Несколько одновременных инстансов dbmole видят изменения
друг друга сразу.
- Запись стора атомарная: tmp-файл + rename. Перед каждой мутацией — re-read
(read-modify-write), последняя запись побеждает.
- Атомарность — на уровне подмены файла: tmp-файл + rename (атомарен на POSIX).
Внутри процесса мутации сериализованы синхронным выполнением (в read-modify-write
нет await-точек, event loop не прерывает его). Между процессами остаётся окно
check-then-write — принято осознанно: последняя запись побеждает, конфликт
означает потерю одной мутации и чинится повторным add/update.
- У каждого подключения в выдаче `list_connections` есть `source: env | config | store`.
- `update/remove_connection` по записи из env/config → ошибка с указанием источника.
- `add_connection` с именем, занятым любым слоем, → ошибка (никаких теневых записей).
@@ -108,13 +112,19 @@ src/
## Readonly
Серверное принуждение, не SQL-парсинг. На каждом новом коннекте пула:
Серверное принуждение, не SQL-парсинг:
- pg: `SET default_transaction_read_only = on`
- mysql: `SET SESSION TRANSACTION READ ONLY`
- pg: startup-параметр libpq `options='-c default_transaction_read_only=on'`
сессия рождается read-only, гонки с выдачей коннекта из пула исключены
- mysql: `SET SESSION TRANSACTION READ ONLY` при первом checkout каждого
физического соединения пула (у mysql2 нет init-SQL опции в конфиге пула)
Блокирует DML и DDL на стороне движка. Обходится только явным `SET` в SQL —
для агентского сценария приемлемо (защита от случайной записи, не от злого умысла).
Блокирует DML и DDL на стороне движка. Проверено эмпирически на mysql:8.4:
INSERT/UPDATE/CREATE TABLE под session read-only падают с ERROR 1792
«Cannot execute statement in a READ ONLY transaction», SELECT работает.
Обходится только явным `SET` в SQL — это защита от случайной записи, не от
злого умысла. Жёсткая гарантия — read-only пользователь БД; рекомендация
фиксируется в README.
## Тулзы