Status: living document. This is the single source of truth for the project plan. Session-to-session progress is tracked in
docs/PROGRESS.md. Architecture decisions are recorded indocs/adr/.
Build MCPg, a production-grade Model Context Protocol server for PostgreSQL that lets AI agents safely inspect, query, operate, and tune a Postgres database. It targets a broad scope: both an application data access layer (safe CRUD/query) and a database operations toolkit (health checks, index tuning, EXPLAIN analysis), with which tools are exposed gated by an access mode.
- The official
@modelcontextprotocol/server-postgreswas deprecated & archived (July 2025) after a SQL-injection CVE. There is no maintained reference implementation. - The strongest active project is
crystaldba/postgres-mcp("Postgres MCP Pro", MIT, Python). It is a credible base and Phase 0 evaluates whether to fork it or build greenfield.
- Security first. Read-only by default. No string-interpolated SQL. Every statement parsed/validated before execution. Treat the database connection string and query results as sensitive.
- Test-Driven Development. Red → Green → Refactor. No production code without a failing test first. Coverage gate enforced in CI.
- Resumable. Work is decomposed into small, independently shippable tasks.
docs/PROGRESS.mdalways reflects exact current state so any session can resume cold (see §8). - Best practices everywhere. Consistent taxonomy, typed code, documented tools, ADRs for decisions, semantic versioning, conventional commits.
- Incremental & honest. Each phase ends with something runnable and demoed. No half-finished phases.
| Concern | Choice | Rationale |
|---|---|---|
| Language | Python 3.12+ | Best MCP+Postgres ecosystem; keeps fork option open |
| MCP framework | Official mcp Python SDK |
First-party, supports stdio + streamable HTTP |
| Postgres driver | psycopg 3 (async) |
Modern, async, server-side cursors, pipeline mode |
| Connection pooling | psycopg_pool |
Async pool, health checks |
| SQL parsing | pglast (libpg_query) |
Real Postgres grammar; classify read vs write, block escapes |
| Test runner | pytest, pytest-asyncio |
TDD workhorse |
| Integration tests | testcontainers[postgres] |
Real Postgres per test session, no mocks for DB behavior |
| Packaging / env | uv |
Fast, reproducible, lockfile |
| Lint / format | ruff |
Fast, single tool |
| Types | mypy --strict |
Catch interface drift |
| Coverage | pytest-cov + CI gate |
Enforce TDD discipline |
| CI | GitHub Actions | Matrix over PG 14–17 |
| Distribution | PyPI + Docker image (distroless) |
uvx mcpg and container deploy |
Decision is provisional until ADR-0001 is accepted at end of Phase 0.
┌─────────────────────────────────────────────┐
│ MCP Client (agent) │
└───────────────┬─────────────────────────────┘
stdio / streamable HTTP
┌───────────────▼─────────────────────────────┐
│ MCPg server │
│ ┌────────────┐ ┌──────────────────────┐ │
│ │ Transport │ │ Tool registry │ │
│ └────────────┘ │ (gated by AccessMode)│ │
│ ┌────────────┐ └──────────────────────┘ │
│ │ AccessMode │ ┌──────────────────────┐ │
│ │ policy │ │ SQL guard (pglast) │ │
│ └────────────┘ └──────────────────────┘ │
│ ┌────────────┐ ┌──────────────────────┐ │
│ │ Conn pool │ │ Audit log │ │
│ └────────────┘ └──────────────────────┘ │
└───────────────┬─────────────────────────────┘
│ psycopg3 async
┌───────────────▼─────────────────────────────┐
│ PostgreSQL 14–17 │
└───────────────────────────────────────────────┘
| Mode | Tools exposed | Transaction |
|---|---|---|
read-only |
introspection + SELECT + ops/tuning (read) | READ ONLY, enforced |
restricted |
read-only set + statement timeout + row limits | READ ONLY + guards |
unrestricted |
all of the above + write/DDL/maintenance tools | read-write |
Tools are namespaced and named verb_noun. Stable contract; documented in
docs/tools/. Planned set (final list refined per phase):
- Introspection —
list_schemas,list_tables,describe_table,list_indexes,list_extensions,get_server_info - Query —
run_select(parsed, read-only),explain_query - Write (unrestricted) —
run_write,run_ddl(each parsed + audited) - Ops & health —
check_database_health,analyze_connections,analyze_vacuum,analyze_replication - Tuning —
analyze_workload,recommend_indexes,analyze_query_plan
Error taxonomy: every tool returns structured errors with a stable code
(E_ACCESS_DENIED, E_SQL_REJECTED, E_TIMEOUT, E_CONNECTION, ...).
- Config: env-var driven (
MCPG_DATABASE_URL,MCPG_ACCESS_MODE, ...), validated via a typed settings model; secrets never logged. - Result shape: tools return typed JSON —
columns,rows,row_count,truncated, plusnotices. Large results paginated/capped. - Naming:
snake_casetools/params;ADR-NNNNdecisions; conventional commits (feat:,fix:,test:,docs:,chore:). - Versioning: SemVer;
CHANGELOG.md(Keep a Changelog format).
Each phase is a milestone; each task is TDD (failing test first). Tasks are
sized to fit comfortably within a single session. Checklists live in
docs/PROGRESS.md.
- Hands-on evaluation of
crystaldba/postgres-mcp: code quality, test coverage, license, extensibility, maintenance health. - Write ADR-0001 (fork vs hard-fork vs greenfield) and ADR-0002 (stack).
- Scaffold repo:
uvproject,pyproject.toml,ruff/mypyconfig,pytestlayout, GitHub Actions CI, pre-commit,CONTRIBUTING.md. - Deliverable: green CI on an empty test, decision recorded.
- MCP server bootstrap; stdio transport; streamable HTTP transport.
- Typed config/settings loader; connection pool lifecycle.
get_server_infotool as the first end-to-end TDD vertical slice.- Deliverable: server connects to Postgres, one tool callable from an MCP client.
- Introspection tools;
run_selectwithpglastread-only enforcement;explain_query; result shaping, row caps, pagination. - Deliverable: agent can fully explore + query a DB read-only.
- Access-mode policy engine gating the tool registry.
- SQL guard: block multi-statement,
COMMIT/ROLLBACKescapes, DDL in read-only; statement timeout; row-limit enforcement. - Audit log; secrets-handling review; SQL-injection regression suite (the CVE class that killed the official server).
- Deliverable: documented, tested security posture; threat model in
docs/.
run_write,run_ddlgated tounrestricted; explicit transaction control; dry-run/preview; per-write audit entries.- Deliverable: safe, audited write path.
- Health checks (indexes, cache, connections, vacuum, replication, sequences).
analyze_workloadviapg_stat_statements;recommend_indexes;analyze_query_planwithhypopghypothetical indexes.- Deliverable: production tuning toolkit.
- Configurable pool sizing (done, ADR-0003); server-side cursors for big reads, backpressure.
- Multi-tenancy: for v0.1.0, document-only RLS guidance — one MCPg
instance per tenant with a tenant-specific role (
docs/security.md). An optional per-requestSET ROLE/ session-variable mechanism is deferred post-1.0 (pooled-connection session-state management). Read-replica routing also post-1.0. - Load/soak test harness.
- Deliverable: documented scaling characteristics + benchmarks.
- Support pages: README, usage guide, tool reference (
docs/tools/), security page, troubleshooting, FAQ; site scaffold optional. - PyPI publish, Docker image, install instructions (
uvx, Docker, source). v0.1.0release withCHANGELOG.md; if forking, upstream contribution.
A phased build-out of PostgreSQL extension and advanced-feature awareness. See §7a for the full capability inventory and rationale.
- Phase 8 — Index intelligence & extension management. Report index
access methods (B-tree/GIN/GiST/BRIN/Hash/SP-GiST) in introspection;
list_available_extensions;enable_extension(gated DDL, known-extension allowlist); makerecommend_indexesindex-type aware (GIN forjsonb/arrays, trigram GIN forLIKE, BRIN for append-only). - Phase 9 — Text search & fuzzy matching.
pg_trgmsimilarity/fuzzy search tool; built-in full-text search (tsvector/tsquery) helper;unaccent/fuzzystrmatchawareness. - Phase 10 — Vector search (pgvector).
vectorcolumn awareness in introspection; k-NN similarity-search tool (<->,<=>,<#>); HNSW / IVFFlat index awareness inlist_indexesandrecommend_indexes. - Phase 11 — Geospatial (PostGIS) [optional].
geometry/geographyawareness, spatial-index reporting, bounding-box / distance query helpers.
Deliverable per phase: new tools + introspection upgrades, fully TDD'd, with
graceful degradation when an extension is absent (as analyze_workload
already does for pg_stat_statements).
What "support" means here: MCPg should (a) detect which extensions are installed vs available, (b) make introspection extension-aware (index types, special column types), (c) offer gated management to enable known extensions, and (d) expose feature tools that use them. Every feature degrades gracefully when its extension is absent.
| Method | Best for | Phase |
|---|---|---|
| B-tree | equality / range on scalars (default) | done |
| GIN | jsonb, arrays, full-text, trigram |
8 |
| GiST | ranges, geometry, full-text, nearest-neighbour | 8 |
| BRIN | very large naturally-ordered tables | 8 |
| Hash | equality only | 8 |
| SP-GiST | non-balanced / partitioned data | 8 |
| Extension | Capability | Priority | Phase |
|---|---|---|---|
pg_stat_statements |
query workload stats | — | done (5.2) |
hypopg |
hypothetical indexes for tuning | high | 5.4 |
pg_trgm |
trigram similarity, fuzzy LIKE |
high | 9 |
pgvector |
vector type, similarity search, HNSW/IVF |
high | 10 |
| built-in FTS | tsvector/tsquery full-text search |
high | 9 |
unaccent |
accent-insensitive search | medium | 9 |
fuzzystrmatch |
soundex, levenshtein | medium | 9 |
citext |
case-insensitive text type | medium | 8 (awareness) |
hstore |
key-value type | low | 8 (awareness) |
pgcrypto/uuid-ossp |
crypto / UUID generation | low | 8 (awareness) |
ltree |
hierarchical data | low | later |
| PostGIS | geospatial types & indexes | medium | 11 |
pgstattuple |
table/index bloat estimation | medium | 8 (health) |
pg_partman/postgres_fdw |
partitioning / federation | low | later |
Operator note: some extensions (
pg_stat_statements, parts of PostGIS,hypopg) needshared_preload_librariesor superuser to install. MCPg detects and uses them but cannot always enable them itself.
Priority ordering (per user direction emphasising pgvector, GIN, trigram): Phase 8 (index intelligence) → 9 (text/trigram) → 10 (pgvector) → 11 (PostGIS). Re-orderable; revisit before starting Phase 8.
After the extension phases, MCPg still lacks introspection and operations for
several core PostgreSQL areas. Partition DDL already runs via run_ddl, but
nothing is partition-aware; similarly there is no view of constraints,
non-table objects, RLS policies, roles, or live activity.
list_constraints— primary keys, foreign keys, unique, check, exclusion.list_views(+ definitions),list_functions,list_triggers,list_sequences.- Deliverable: an agent can see a table's full structure, not just columns.
list_partitions— partition strategy (range/list/hash), bounds, parent↔partition links; flag partitioned tables inlist_tables.- Make
list_indexes/recommend_indexespartition-aware (parent vs per-partition indexes; aggregate partition scan stats). - Deliverable: partitioned schemas are correctly understood, including the index interaction.
list_policies— Row-Level-Security policies on a table (supports the multi-tenant / partition-per-tenant story).list_roles,list_grants— roles and table/object privileges.- Deliverable: "who can access what", and RLS visibility.
list_active_queries, lock / blocking inspection (pg_stat_activity,pg_locks).- Replication-lag and table/index bloat health checks (extends
check_database_health). - Gated maintenance:
run_maintenance(VACUUM/ANALYZE),cancel_query/terminate_backend. - Deliverable: diagnose and act on a running database.
Each phase is TDD'd with unit + real-PostgreSQL integration tests, like Phases 0–11. Ordering 12 → 15; re-orderable.
To resume at any time, a new session must:
- Read
PLAN.md(this file) anddocs/PROGRESS.md. docs/PROGRESS.mdcontains: current phase, per-task checklist with status, "Next action" pointer, open questions, and a decisions log.- Pick up the first unchecked task under "Next action".
- On finishing meaningful work: update
docs/PROGRESS.md, commit, push.
Every session ends by committing an updated docs/PROGRESS.md so state is
never lost when limits are hit.
- Failing test written first, then code to pass it.
-
ruff,mypy --strict, full test suite green; coverage gate met. - Public behavior documented (tool doc / docstring).
-
docs/PROGRESS.mdupdated; conventional-commit pushed.
- Hosting target for streamable HTTP (auth model for remote use)?
- Should tuning tools require a separate opt-in beyond
unrestricted? - Telemetry/observability scope (OpenTelemetry?) — revisit in Phase 6.
These are tracked and resolved via ADRs as phases reach them.
After Phases 12–15 closed (365 tests, 100% coverage), a second round of capability selection picked eleven themes spanning catalog completeness, advisors, extension wrappers, data movement, replication, events, and migrations. The work is grouped into six deliverable batches; each batch opens its own feature branch and pull request.
- Phase 16 — Introspection gaps:
list_enums,list_domains,list_composite_types,list_foreign_data_wrappers,list_foreign_servers,list_foreign_tables,list_user_mappings,list_publications,list_subscriptions. - Phase 17 — Schema visualisation:
generate_schema_diagramreturning a Mermaid ER diagram (tables, columns, PK/FK, partitions). - Phase 18 — Schema diff:
compare_schemas(left, right)returning a structured diff. Foundation for Batch F. - Phase 19 — Storage & cost telemetry:
analyze_storage;wal_volumehealth check.
- Phase 20 — Advisors / lint layer:
run_advisorswith codified rules (missing PKs, unindexed FKs, RLS gaps, duplicate indexes, etc). - Phase 21 — Audit trail with semantic diff:
run_write/run_ddlemit a structured diff alongside their result; optional persistence to anmcpg_auditschema (off by default).
- Phase 22 —
pg_cron+pg_partmanwrappers: list / schedule / unschedule / create-parent / run-maintenance / drop-partition. - Phase 23 — pgvector tuning:
tune_vector_index,vector_recall_at_k.
- Phase 24 — Export/import:
export_query,export_table,import_csv/import_json,dump_database/restore_database,copy_table_between_databases. Subprocess execution is a new attack surface; ADR-0004 (accepted) defines the policy: newCapability.SHELL+MCPG_ALLOW_SHELLopt-in, allowlisted binary set (pg_dump/pg_restore/psql), argv-only invocation viaasyncio.create_subprocess_exec, hard timeout and output cap, and credentials passed through libpq env vars (never on the command line).
- Phase 25 — Logical replication management: replication slots and publication/subscription create+drop wrappers (write-gated).
- Phase 26 —
LISTEN/NOTIFYbridge. ADR-0005 (accepted) picks the tool-poll model: a newmcpg.listenmodule owns subscription state in process memory;subscribe_channel/poll_notifications/unsubscribe_channelkeep the MCP wire request/response (no server-sent notifications). Gated behindCapability.LISTEN+MCPG_ALLOW_LISTEN.
- Phase 28 —
generate_prisma_schema: read the PG catalog and emit a valid.prismaschema (mirrorsprisma db pull). High-leverage differentiator for TS/JS agentic workflows; sibling tools for Drizzle, SQLAlchemy, and sqlc can follow under the same "schema → ORM DSL" umbrella. Scope is deliberately narrow: catalog → DSL only, no.prisma→ DDL parsing and noprisma migratesubprocess driving.
- Phase 27 —
prepare_migration/complete_migrationdriven by the Phase-18 schema diff. ADR-0006 (accepted) picks the same-DB shadow-schema strategy (cheap; structural-only; reuses Phase-18 diff as the agent's review surface) overCREATE DATABASE ... TEMPLATEso a 500 GB database doesn't pay full-clone cost per staged migration. State lives in a newmcpg_migrations.stagedtable; tools gate underCapability.MIGRATE+ the existingMCPG_ALLOW_DDLopt-in.
Cadence: per-task TDD; 90% coverage gate (current 100%); ruff / mypy / PG 14–18 CI matrix must be green before each commit. Batch policy decisions live in their ADRs; implementation PRs reference them and focus on code.