MCPg v0.1.0 — production-grade PostgreSQL MCP server - #1
Merged
Conversation
Establishes the phased, TDD roadmap for the MCPg PostgreSQL MCP server, including technology selection, architecture, tool taxonomy, security posture (access modes), and a session-resume protocol. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Hands-on evaluation of crystaldba/postgres-mcp (MIT, commit 07eb329): strong SQL-safety kernel and real-Postgres test suite. Decision: hard-fork as our own base with a TDD-hybrid strategy. Stack confirmed as Python 3.12 + psycopg3 + the official mcp SDK. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Imports only the self-contained sql/ subpackage from crystaldba/postgres-mcp (@07eb329, MIT) into src/mcpg/_vendor/sql/, keeping inherited surface to the ~2.5k-LOC security kernel rather than the full ~7k-LOC base. Adds the uv project scaffold: pyproject.toml with ruff/mypy/pytest/coverage config, MIT attribution in NOTICE and _vendor/README.md, and CHANGELOG. The 75 portable upstream kernel tests pass under tests/vendor/. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Runs ruff (lint + format), mypy --strict, and the vendored kernel test suite on push and pull request. The coverage gate and the PostgreSQL service-container matrix are deferred to Phases 1 and 2 respectively, when authored code and integration tests exist. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Completes Phase 0. Adds CONTRIBUTING.md documenting the TDD workflow and vendored-code policy, local pre-commit hooks that reuse the pinned tools, and GitHub issue/PR templates. Sets ruff force-exclude so vendored code is skipped even when pre-commit passes file paths explicitly. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.config: a frozen Settings dataclass with AccessMode and Transport enums, loaded and validated by load_settings from MCPG_-prefixed environment variables. Read-only is the default access mode; the repr redacts database credentials via the vendored obfuscate_password. 16 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.database.Database wraps the vendored DbConnPool with explicit connect/close, async-context-manager support, a driver() accessor, and a typed DatabaseError. This gives the server a single owned connection object instead of the global state used upstream. Switches pytest to asyncio auto mode. 99 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
The Task 1.2 commit was not run through ruff format, failing the CI format check. Reformats the file and installs the pre-commit hook locally so the format/lint/type checks run before each commit. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.server: create_server constructs a configured FastMCP whose lifespan owns the Settings and Database and exposes them to tools via a frozen AppContext, replacing the module-level globals used upstream. The run dispatcher serves over stdio, streamable-HTTP, or SSE. 104 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.tools: build_server_info assembles a ServerInfo record from the AppContext, and register_tools exposes it as the get_server_info MCP tool. AppContext moves to mcpg.context to break a server/tools import cycle. The tool is verified end-to-end through an in-memory MCP client session. 108 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.__main__.main loads configuration and runs the server, reporting configuration errors with exit code 1; restores the [project.scripts] mcpg entry point. CI now runs the full suite with --cov, enforcing the 90% coverage gate on authored code. Completes Phase 1. 110 tests, 100% coverage. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Adds tests/integration/ with database_url and connected_database fixtures gated on MCPG_TEST_DATABASE_URL, an auto-applied integration marker, and three real-database tests for the Database lifecycle. The CI test job becomes a PostgreSQL 14-17 service-container matrix. Verified locally against PostgreSQL 16. 113 tests, 100% coverage. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.introspection with typed results and parameterised read-only catalog queries: list_schemas, list_tables, describe_table, list_indexes, and list_extensions, each registered as an MCP tool. Adds FakeDriver and FakeDatabase test doubles plus real-database integration tests. 126 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.query.run_select runs agent-supplied SQL through the vendored SafeSqlDriver, which parses and validates it against an allowlist and forces a read-only transaction; writes, DDL, and unparseable input are rejected as QueryError. Returns a typed QueryResult and is exposed as the run_select MCP tool. 136 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.query.explain_query wraps a query in EXPLAIN (FORMAT JSON), validates it with the same safety allowlist as run_select, and returns the typed ExplainResult plan without running the query. Registered as the explain_query MCP tool. 142 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built result shaping for run_select: a configurable max_rows cap (default 1000, exposed as a tool parameter) with a truncated flag on QueryResult; non-positive caps are rejected as QueryError. Cursor-style pagination is left to caller SQL LIMIT/OFFSET. Completes Phase 2. 146 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.policy: a Capability enum and a per-access-mode permission table. register_tools now takes the access mode and gates tool registration by capability. All current tools are reads (exposed in every mode); write-tool gating takes effect when Phase 4 adds them. 157 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Locks in run_select's rejection of the SQL-injection class that retired the reference PostgreSQL MCP server: statement stacking, line/block comment escapes, transaction-control escapes, DDL/DML, COPY TO PROGRAM / FROM file, and DO blocks — 21 hostile queries, each rejected before reaching the driver — while 5 legitimate read queries still pass. 183 tests. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.audit: AuditEvent, redact_arguments (masks secret-named arguments and obfuscates embedded connection-string passwords), and record. AuditedFastMCP overrides call_tool so every tool invocation — success or failure — is recorded to the mcpg.audit logger. 192 tests, 100% coverage. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Adds docs/security.md: trust boundaries, assets, threats T1-T5 (SQL injection, unintended writes, resource exhaustion, credential disclosure, attribution) with their mitigations, operator responsibilities for defence in depth, and out-of-scope items. Links docs from the README. Completes Phase 3. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.write.run_write parses the statement with pglast, requires exactly one INSERT/UPDATE/DELETE (blocking statement stacking), and executes it in a read-write transaction. register_tools now takes Settings and gates the run_write tool to unrestricted access mode via the WRITE capability. 209 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.write.run_ddl validates a single DDL statement against an allowlist and executes it read-write. Adds the MCPG_ALLOW_DDL config flag and a DDL capability; the run_ddl tool is registered only in unrestricted access mode with the opt-in enabled — DDL's high blast radius warrants the second gate. 224 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Adds an audit integration test confirming a run_write tool invocation is recorded to the mcpg.audit logger. Completes Phase 4. 225 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.health: check_connections, check_cache_hit_ratio, check_dead_tuples, and check_invalid_indexes, each classifying a read-only stats query as ok or warning. check_database_health aggregates them and is exposed as an MCP tool. Adds the FakeRoutingDriver test double. 234 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.workload.analyze_workload returns the slowest queries by mean execution time from pg_stat_statements, degrading gracefully to available=false when the extension is not installed. Exposed as the analyze_workload MCP tool. 239 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Adds a capability inventory (PLAN.md section 7a) covering index access methods (GIN/GiST/BRIN/Hash/SP-GiST) and extensions (pg_trgm, pgvector, full-text search, PostGIS, and others) with per-extension priorities, plus roadmap Phases 8-11: index intelligence & extension management, text search & fuzzy matching, vector search, and geospatial. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Extension support (Phases 8-11) lands after the v0.1.0 release; index intelligence (Phase 8) is the first extension area implemented. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.indexing.recommend_indexes flags large tables read mostly by sequential scan, using pg_stat_user_tables. A deliberately simple table-level heuristic; column-level and index-type-aware recommendations arrive in Phase 8. Exposed as the recommend_indexes MCP tool. 244 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
TDD-built mcpg.query.analyze_query_plan walks the EXPLAIN (FORMAT JSON) tree into a structured QueryPlanAnalysis: total estimated cost, estimated rows, node types used, and sequentially-scanned tables. Exposed as the analyze_query_plan MCP tool. Completes Phase 5. 249 tests, 100% coverage. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Adds MCPG_POOL_MIN_SIZE / MCPG_POOL_MAX_SIZE settings that flow through Settings into Database and the connection pool. Per ADR-0003, the vendored DbConnPool gains min_size/max_size parameters via a minimal, behaviour-preserving patch (defaults 1/5); the local modification is recorded in the vendor README. 256 tests, 100% coverage of authored code. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Task 6.2: RLS policies evaluate against the connecting role, and MCPg uses a single pooled role, so it cannot isolate tenants on its own. docs/security.md gains a "Multi-tenancy and Row-Level Security" section recommending one MCPg instance per tenant with a tenant-specific role. A per-request SET ROLE mechanism is deferred post-1.0; recorded in PLAN.md and the decisions log. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Task 6.3: benchmarks/bench.py runs concurrent run_select calls and reports throughput and latency percentiles; docs/scaling.md documents the execution model, connection-pool sizing, result-size bounds, a measured baseline (~2200 req/s, p50 ~7ms on loopback PG16), and bottleneck guidance. Phase 6 task 6.4 (server-side cursors, read-replica routing) is deferred post-1.0. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Task 7.1: docs/usage.md covers installation, the configuration env-var table, access modes, running, MCP client configuration, and troubleshooting; docs/tools.md is a reference for all 14 MCP tools grouped by category with parameters and access requirements. Both linked from the README. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Task 7.2: a uv-based Dockerfile (reproducible --frozen install, non-root user, streamable-HTTP default on 0.0.0.0:8000) plus .dockerignore. Documents Docker usage in docs/usage.md and a quick start in the README. The image is not built in CI or locally (no Docker in this environment). https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Bumps the version to 0.1.0 (pyproject.toml, mcpg.__version__, uv.lock) and finalises the CHANGELOG with a [0.1.0] section. Completes Phase 7. The release itself (git tag, PyPI / GitHub publish) is left for explicit sign-off. 256 tests, 100% coverage. https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
There was a problem hiding this comment.
Sorry @devopam, your pull request is larger than the review limit of 150000 diff characters
3 tasks
devopam
added a commit
that referenced
this pull request
Jun 14, 2026
…erify_audit_chain (#99) * perf: bound four scalability P0 cliffs in vector_ops / mmr_search / verify_audit_chain Addresses three of the four scalability P0s from the deep review. Each finding was a callable surface that took an unvalidated caller-supplied size and either pulled it all into Python or did quadratic work on it. src/mcpg/textsearch.py (mmr_search) — P0 #1: - Old: ``fetch_k`` unvalidated. Combined with the pure-Python diversity loop (O(pool · k) cosine similarities with every candidate's embedding held in memory), ``fetch_k=100_000`` would kill the process. - New: _MAX_MMR_FETCH_K = 10_000 + _MAX_MMR_K = 1_000 enforced up front. Constants live module-level so the ceilings are auditable in one place. Error messages explain *why* the cap exists (in-process O(pool·k) similarities) and gesture at the right next step (a vector-DB ANN library, not a higher cap). src/mcpg/vector_ops.py — P0 #3: - Old: each of cluster_vectors, detect_vector_outliers, monitor_embedding_drift, analyze_distance_metric guarded only ``sample_size < 1``. ``sample_size=10_000_000`` was accepted and the in-process k-means / centroid / z-score work would hang. - New: shared _validate_sample_size helper with _MAX_SAMPLE_SIZE = 50_000 (matches rag_efficiency._MAX_SAMPLE_SIZE so the two co-located vector-analytics surfaces have one mental model). All four entry points now go through the helper — pinned by a parametrised regression test that exercises every public tool. src/mcpg/audit_integrity.py (verify_audit_chain) — P0 #2: - Old: ``SELECT … FROM events ORDER BY id ASC`` with no LIMIT. On a million-row audit table that's gigabytes of jsonb loaded into the process before the chain check even starts. - New: keyset pagination — ``WHERE id > %s ORDER BY id ASC LIMIT %s`` walked in batches of _VERIFY_BATCH_SIZE = 1_000. Index-friendly (PRIMARY KEY does the work), order-stable (id is monotonic for an append-only audit), bounds peak memory at one batch. Not addressed in this PR — P0 #4 (monitor_embedding_drift's ORDER BY RANDOM() LIMIT n on the window scan): the agent flagged the full-scan-and-sort cost on un-pre-pruned windows. TABLESAMPLE BERNOULLI doesn't compose cleanly with the timestamp WHERE filter (samples the whole table, not the window), and the current shape is intentional for unbiased drift sampling. The sample_size cap above transitively bounds the worst case via LIMIT, but a proper replacement (probability-based pre-filter with COUNT-driven calibration) is out of scope for the no-guesswork pass. Flagging here so it surfaces in the followups queue rather than going silent. Tests (+4, 1769 total): - mmr_search parametrize gains two entries: fetch_k=100_000 and k=5000 are now rejected with "must be ≤" messages. - analyze_distance_metric oversized-sample test. - A single shared-cap test that exercises every vector_ops entry point so a future refactor that drops the helper from one call site can't slip through. - test_verify_audit_chain_uses_keyset_pagination pins the SQL shape (WHERE id > %s, ORDER BY id ASC, LIMIT %s; both placeholders bound, no literal splice). - test_verify_audit_chain_walks_multiple_batches uses a custom paged fake driver (FakeRoutingDriver ignores params, which defeats the streaming assertion) to prove the loop tracks last_seen_id across iterations and terminates on a short batch. * review followups (#99): hoist UTC import, echo sample_size value, hmac.compare_digest Three small review wins from PR #99: (1) Hoist ``from datetime import UTC`` out of verify_audit_chain's inner per-row loop (sourcery). Cheap import-lookup saving on large audit tables; functionally equivalent. (2) Echo the offending value in _validate_sample_size's error messages so a caller debugging a stack trace can spot the misconfiguration without having to also log the call args (sourcery). Tests updated to match the looser substring. (3) Swap the two raw ``!=`` HMAC comparisons in verify_audit_chain for ``hmac.compare_digest`` (gemini-code-assist flagged as critical; matches deep-review P2 #7). Verification-side timing isn't a sensitive oracle (operator-initiated, not per-request hot path), but the inconsistency with the rest of the codebase's hmac.compare_digest usage was worth fixing. 1769 unit tests pass; full gates clean. --------- Co-authored-by: Claude <noreply@anthropic.com>
3 tasks
devopam
added a commit
that referenced
this pull request
Jun 15, 2026
…dit (#106) * security(nl2sql): close two prompt-injection paths flagged by the NL→SQL audit PR-1 of five in the NL→SQL remediation arc (this is the security audit raised in the threat model conversation; see plan in the issue thread). Closes the two P0 findings: P0 #1 — Stored prompt injection via column DEFAULT expressions: _build_schema_brief rendered ``col.default`` raw into the LLM prompt, so anyone who could CREATE TABLE or ALTER COLUMN ... SET DEFAULT on any schema reachable by NL→SQL could plant a multi- kilobyte expression containing line breaks and adversarial instructions. Every subsequent translate_nl_to_sql call against that schema would carry the attacker's prompt-override. New _sanitize_default_expr helper strips ASCII control chars (\\n / \\r / \\t plus C0/C1 bytes), collapses whitespace runs into a single space, and caps the rendered value at _DEFAULT_EXPR_MAX_CHARS (80). Returns None for empty / all- control inputs so the caller can omit the ``DEFAULT ...`` clause entirely. Legitimate defaults (now(), gen_random_uuid(), 'pending'::text, 0, TRUE) pass through unchanged. P0 #2 — Caller-supplied schema name fed verbatim into the prompt: the tool wrapper accepted any string for the ``schema`` arg, so schema="public; --" or schema="public\\nIgnore above" flowed through .replace() into the user prompt. Worse, nothing prevented schema="pg_catalog" or schema="mcpg_audit" — the LLM could be asked to dump system catalogs or audit history. New _validate_schema_name does three things in one boundary check: - Identifier regex (^[A-Za-z_][A-Za-z0-9_]*$, same as every other surface in the codebase) — refuses anything that would require delimited quoting. Prompt-injection via the schema name is shut off. - DEFAULT_SCHEMA_DENYLIST refuses pg_catalog, pg_toast, information_schema, mcpg_audit, mcpg_rag, mcpg_migrations by default. Operators add to it via MCPG_NL2SQL_SCHEMA_DENYLIST (comma-separated, case-insensitive). - MCPG_NL2SQL_SCHEMA_ALLOWLIST when set flips the policy to strict allowlist-only — only listed schemas pass. translate_nl_to_sql calls _validate_schema_name before doing any catalog work, so a bad value can't even reach _build_schema_brief. Tests (+13, 1815 total): - _sanitize_default_expr strips newlines + caps length on the canonical multi-line prompt-override payload. - _sanitize_default_expr preserves short legitimate defaults (now(), gen_random_uuid(), 'pending'::text, 0, TRUE). - _sanitize_default_expr returns None on empty / whitespace / all-control-char inputs. - _validate_schema_name rejects 8 different non-identifier shapes including the canonical injection payloads. - _validate_schema_name normalises case + whitespace. - _validate_schema_name denies every PG-system + MCPg-internal schema by default. - MCPG_NL2SQL_SCHEMA_DENYLIST extra entries honoured (case- insensitive, whitespace-tolerant). - MCPG_NL2SQL_SCHEMA_ALLOWLIST makes the policy strict. - _resolve_schema_policy defaults are as documented. - translate_nl_to_sql end-to-end rejects malformed schema args. - translate_nl_to_sql end-to-end rejects denied schema args. Followups (separate PRs in the agreed sequence): - PR-2: NL→SQL audit table with TimescaleDB hypertable + pg_partman fallback (P1 #3). - PR-3: hardening sweep — brief-size caps + LLM-vendor exfil note + QueryError redaction + single-statement assertion + better fence handling (P1 #4 + P2 #5-#8). - PR-4: retrofit mcpg_audit.events with full migration (chain- aware). - PR-5: retrofit mcpg_rag.rerank_events + .efficiency_observations. * review(nl2sql): address PR #106 feedback * trim schema-policy error messages so they don't leak operator configuration (sourcery) - surface only the offending schema name in NL2SQLError; full allow/deny lists stay out of caller-facing text. * normalise DEFAULT_SCHEMA_DENYLIST entries to lowercase at definition (sourcery) - built-in entries are now consistent with env-driven policy without relying on the source literal being lowercase already. * plumb env mapping through translate_nl_to_sql -> _validate_schema_name (gemini) - schema policy honours caller-supplied env in multi-tenant / test scenarios; previously it always fell back to os.environ. --------- Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Jun 15, 2026
devopam
pushed a commit
that referenced
this pull request
Jun 17, 2026
…ependabot alerts Lockfile-only refresh — all three packages reach the project as transitive dependencies (cryptography via pyjwt[crypto]; python- multipart + starlette via mcp), so no pyproject.toml change is required. uv lock --upgrade-package resolved each to the latest non-breaking version available, which exceeds the minimum patched versions Dependabot demands. * cryptography 48.0.0 -> 49.0.0 * GHSA: OpenSSL CVE in statically linked wheels (alert #5) https://openssl-library.org/news/secadv/20260609.txt * Patched minimum: 48.0.1; we pull 49.0.0 (no API changes for pyjwt's RS256/ES256 use). * python-multipart 0.0.29 -> 0.0.32 (closes 4 alerts) * Content-Disposition RFC 2231/5987 parameter smuggling (#1) * application/x-www-form-urlencoded ; separator differential (#2) * Negative Content-Length unbounded read in parse_form (#3) * Quadratic-time ; separator scan DoS (#4) * Patched minimum: 0.0.31; we pull 0.0.32. * starlette 1.2.0 -> 1.3.1 (closes 2 alerts) * Unvalidated request path poisons request.url.hostname (#6) — affects middleware / 404 handlers that read request.url before routing. * request.form() max_fields / max_part_size silently ignored on application/x-www-form-urlencoded (#7) — DoS. * Patched minimum: 1.3.1. mcpg uses request.form() only on the HTTP transport path; the starlette bump composes cleanly with the http_runtime tests (1895 unit tests pass). Dependabot's "cannot update to required version" banner on alerts #1-#4 and #7 is a uv vs pip resolution quirk — mcp's python-multipart and starlette pins (>=0.0.9, >=0.40 respectively) do not actually block the bump; uv resolved straight through.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds MCPg from an empty repo to a v0.1.0 release-ready PostgreSQL MCP
server — 35 commits across all seven planned phases. AI agents get 14 MCP
tools to safely inspect, query, operate, and tune a Postgres database;
read-only by default, every statement validated, every tool call audited.
What's included
vendored from
crystaldba/postgres-mcpbehind a versioned boundary, CI.Databaselifecycle, aFastMCPbootstrap with no module-level global state, the
mcpgCLI.list_schemas/list_tables/describe_table/list_indexes/list_extensions,run_select,explain_query,analyze_query_plan.regression suite, audit logging of every tool call, threat model.
run_write(DML) andrun_ddl(gated behind unrestrictedmode +
MCPG_ALLOW_DDL), single-statement validated.check_database_health,analyze_workload,recommend_indexes.docs + benchmark harness.
Quality
ruff+mypy --strictclean.carries one documented behaviour-preserving patch (ADR-0003).
Test plan
uv run pytest— full unit + integration suite passesuv run ruff check/ruff format --check— cleanuv run mypy src/mcpg— cleanexpectations before merge
Notes
src/mcpg/_vendor/is third-party MIT code (seeNOTICEandsrc/mcpg/_vendor/README.md); it is excluded from the coverage gate andmypy.planned for Phases 8–11, post-1.0 — see
PLAN.md§7a.https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Generated by Claude Code