Skip to content

MCPg v0.1.0 — production-grade PostgreSQL MCP server - #1

Merged
devopam merged 35 commits into
mainfrom
claude/postgresql-mcp-planning-8KssU
May 21, 2026
Merged

MCPg v0.1.0 — production-grade PostgreSQL MCP server#1
devopam merged 35 commits into
mainfrom
claude/postgresql-mcp-planning-8KssU

Conversation

@devopam

@devopam devopam commented May 21, 2026

Copy link
Copy Markdown
Owner

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

  • Foundation — ecosystem spike, 3 ADRs, the MIT SQL-safety kernel
    vendored from crystaldba/postgres-mcp behind a versioned boundary, CI.
  • Core server — env-driven config, Database lifecycle, a FastMCP
    bootstrap with no module-level global state, the mcpg CLI.
  • Introspection & readslist_schemas/list_tables/describe_table/
    list_indexes/list_extensions, run_select, explain_query,
    analyze_query_plan.
  • Security — access-mode policy engine, an adversarial SQL-injection
    regression suite, audit logging of every tool call, threat model.
  • Writesrun_write (DML) and run_ddl (gated behind unrestricted
    mode + MCPG_ALLOW_DDL), single-statement validated.
  • Ops & tuningcheck_database_health, analyze_workload,
    recommend_indexes.
  • Scalability — configurable connection pooling, RLS guidance, scaling
    docs + benchmark harness.
  • Release — usage guide, tool reference, Dockerfile, version 0.1.0.

Quality

  • 256 tests, 100% coverage of authored code.
  • ruff + mypy --strict clean.
  • CI green across PostgreSQL 14, 15, 16, and 17.
  • Every line of authored code written test-first (TDD); the vendored kernel
    carries one documented behaviour-preserving patch (ADR-0003).

Test plan

  • uv run pytest — full unit + integration suite passes
  • uv run ruff check / ruff format --check — clean
  • uv run mypy src/mcpg — clean
  • CI green on the PG 14–17 matrix
  • Reviewer: confirm the access-mode gating and SQL-safety suite meet
    expectations before merge

Notes

  • The vendored src/mcpg/_vendor/ is third-party MIT code (see NOTICE and
    src/mcpg/_vendor/README.md); it is excluded from the coverage gate and
    mypy.
  • Extension support (pgvector, pg_trgm, GIN/GiST/BRIN intelligence) is
    planned for Phases 8–11, post-1.0 — see PLAN.md §7a.

https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc


Generated by Claude Code

claude added 30 commits May 20, 2026 06:57
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
claude added 5 commits May 21, 2026 07:13
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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @devopam, your pull request is larger than the review limit of 150000 diff characters

@devopam
devopam merged commit 1af402b into main May 21, 2026
11 checks passed
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>
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>
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants