Skip to the content.

ADR-0001: Build approach — adopt crystaldba/postgres-mcp as a hard-forked base

Superseded. The vendored SQL-safety kernel this ADR introduced was replaced by a first-party implementation in roadmap 18.1 — see ADR-0007. The historical rationale below is retained as the record of the original decision.

Context

The official @modelcontextprotocol/server-postgres is deprecated and archived (July 2025) after a SQL-injection CVE. Phase 0 evaluated the strongest active alternative, crystaldba/postgres-mcp (“Postgres MCP Pro”), hands-on to decide between: (a) fork & contribute upstream, (b) hard-fork into our own product, or (c) greenfield.

Evaluation findings (crystaldba/postgres-mcp @ commit 07eb329, Jan 2026)

Strengths

Weaknesses / debt

Options considered

  1. Fork & contribute upstream — fastest in theory, but our broad-scope vision (3 access modes, eliminate global state, multi-tenancy, our own tool taxonomy) diverges architecturally. With a stale cadence and PR backlog, our roadmap and resume-friendly cadence would be hostage to upstream review.
  2. Hard-fork as our own product (MCPg) — adopt the base under MIT (keeping attribution), restructure aggressively, own the roadmap. Keeps the battle-tested security kernel; we still cherry-pick upstream fixes and may upstream security patches as goodwill.
  3. Greenfield — cleanest TDD story, but discards ~1k lines of hard-won SQL safety code and months of analysis features. Unjustified risk and delay.

Decision

Adopt crystaldba/postgres-mcp as a hard-forked base (Option 2).

The security kernel (safe_sql.py) and analysis modules are too valuable to rewrite, and their permissive MIT license makes reuse clean. Hard-forking — rather than contributing upstream — is chosen because our architecture diverges and upstream cadence would gate our progress.

Vendoring scope — minimise inherited surface

To avoid inheriting tech debt we did not take the whole ~7k LOC base. Import-graph analysis confirmed the sql/ subpackage is fully self-contained — it imports only pglast, psycopg, psycopg_pool, typing_extensions, the stdlib, and its own siblings. It has no dependency on the upstream server.py (and its global state), the analysis modules (database_health/, explain/, index/, top_queries/), or the heavy instructor LLM dependency.

Therefore we vendor only sql/ (6 files, ~2,453 LOC) into src/mcpg/_vendor/sql/, pinned to upstream commit 07eb329:

File Role
safe_sql.py pglast AST allowlist SQL validator (the security kernel)
sql_driver.py DbConnPool, SqlDriver, obfuscate_password
bind_params.py parameter binding / AST visitors
extension_utils.py extension + PG-version checks
index.py IndexDefinition value type
__init__.py public exports

Everything else — server bootstrap, access-mode engine, tool layer, config, taxonomy, and the analysis features (health/explain/index) — is authored fresh by us under strict TDD. Inherited surface drops from ~7.3k to ~2.5k LOC.

Reconciling with the TDD mandate

This keeps TDD honest for everything we author without discarding the hard-won, security-critical SQL validator.

Consequences