Skip to main content
Glama
Zenith10-fck

Guarded Postgres MCP Server

by Zenith10-fck

Guarded Postgres MCP Server

An MCP server that lets Claude query and safely write to a Postgres database — built so it structurally cannot run DROP TABLE, leak PII, or silently return wrong data. Every guardrail is driven by one policy.yaml file, so the same server works for any client's database by editing config, not code.

Files

Path

What it is

policy.example.yaml

The single source of truth: which tables/columns Claude may read or write, row limits, and which columns are PII. Copy to policy.yaml and edit per client.

docker-compose.yml

One-command demo Postgres, auto-seeded from seed/.

seed/

Demo schema (leads, orders, invoices), sample data, and the two least-privilege DB roles.

src/guarded_mcp/server.py

Entry point — registers the tools and starts the server.

src/guarded_mcp/app.py

The shared MCPServer instance.

src/guarded_mcp/policy.py

Loads and enforces policy.yaml.

src/guarded_mcp/sqlguard.py

Parses and validates query SQL — single read-only SELECT only.

src/guarded_mcp/redact.py

PII masking (column-precise + a regex safety net).

src/guarded_mcp/audit.py

Append-only audit log + the @audited decorator on every tool.

src/guarded_mcp/ratelimit.py

Per-tool token-bucket rate limiting.

src/guarded_mcp/db.py

Async connection pools for the read role and the write role.

src/guarded_mcp/models.py

Pydantic v2 schemas validating write-tool inputs.

src/guarded_mcp/tools/

The tools Claude calls: list_tables, describe_table, query, insert_record, update_record, get_audit_log.

tests/adversarial/attacks.py

The attack catalog — single source of truth for the suite and the report.

tests/adversarial/test_attacks.py

pytest suite: every attack must be refused and the DB must be unchanged.

scripts/run_adversarial.py

Runs every attack and writes results.json ("X attacks, 0 succeeded").

scripts/smoke_test.py

Fast end-to-end check of all tools (normal + rejected cases).

results.json

Output of run_adversarial.py (the reproducible evidence table).

claude_desktop_config.example.json

Drop-in Claude Desktop config to connect over stdio.

case-study/

Portfolio copy (case-study.md) + a polished page (case-study.html).

Related MCP server: Argos-MCP

Run it

docker compose up -d                 # start the demo Postgres (seeds itself)
python -m venv .venv && source .venv/Scripts/activate   # (Windows Git Bash)
pip install -r requirements.txt && pip install -e .
python scripts/smoke_test.py         # prove every tool works (normal + rejected)

Reproduce the metrics

python scripts/run_adversarial.py    # writes results.json
pytest tests/adversarial/ -v         # the same attacks as a formal test suite

Current run — 19 adversarial attacks, 0 succeeded, database intact (row count unchanged, target table still present). Categories: DDL, DML-via-read-tool, injection (stacked statements / comment smuggling / CTE writes), exfiltration, write authorization, and integrity. Every number regenerates from the command above.

Connect to Claude Desktop

See claude_desktop_config.example.json. The server speaks stdio (not HTTP), so Claude Desktop launches it as a subprocess — there's no URL or port. Point DATABASE_URL/WRITE_DATABASE_URL at your own Postgres and edit policy.yaml to match your schema.

Glossary

Terms introduced while building this, in plain language:

  • MCP (Model Context Protocol) — the standard that lets Claude call your Python functions as "tools". You tag a function with @mcp.tool() and Claude can invoke it.

  • stdio transport — Claude Desktop talks to this server over stdin/stdout (like two programs connected by a pipe), not over the network. Hence no URL/port.

  • async/await — lets Python wait on slow I/O (a database round-trip) without freezing the whole program.

  • Connection pool — a set of ready-to-use DB connections borrowed and returned, instead of opening a fresh (expensive) connection per request.

  • Allow-list vs deny-list — allow-list = "only what's on the list is permitted" (the safe default: forget to list something and it's denied). Deny-list = "everything except the list". This server is allow-list throughout.

  • SQL parser / AST — sqlglot turns SQL text into a tree a program can inspect, so we can detect a hidden DROP inside a complex query — not just string-match "DROP".

  • CTE (Common Table Expression) — a WITH x AS (...) SELECT ... query. Postgres allows writes inside a CTE, so the guard checks there too.

  • Parameterized query — passing values separately from the SQL text ($1, $2) so user data can never be parsed as SQL. The write tools use this throughout.

  • Least-privilege role — a database user granted only the permissions it needs (SELECT for reads; INSERT/UPDATE on specific tables for writes) — never a superuser.

  • Defense in depth — independent layers (app-level SQL guard and DB-level role grants) that must all fail for an attack to succeed.

  • Structured error — a consistent {error_code, message, hint} shape returned on every failure, instead of a raw stack trace or a silent empty result.

  • Dry-run / confirm — a write reports what it would do and changes nothing until called again with confirm=true.

  • Transaction + rollback — a group of DB operations that either all succeed or all undo, so a half-written row can't be left behind.

  • Append-only log — a log you only ever add to (never edit/delete), so the audit trail can be trusted.

  • Token bucket — a rate-limit metaphor: a bucket of tokens refilled over time; each call spends one; empty bucket = request politely refused.

  • PII (Personally Identifiable Information) — data identifying a person (email, phone). Masked in results based on policy.yaml.

  • Adversarial test — a test that actively tries to break the system (attack it), proving the guards hold — stronger evidence than testing only the happy path.

Honesty note

This is a reference/demo build, not a paid client engagement. Metrics come from the self-built 19-attack adversarial suite and are fully reproducible. No client names, logos, or data are implied.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables interaction with PostgreSQL databases through a production-ready MCP server with global connection pooling, automatic AWS password rotation, and comprehensive SQL operations. Supports multiple concurrent Claude sessions while maintaining efficient connection limits to the database.
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables Claude Code to securely interact with PostgreSQL, MySQL, SQLite, and SQL Server databases, featuring read-only mode, query validation, SSH tunneling, and field redaction for production-safe data access.
    3
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables read-only, SELECT-only querying of any Postgres database through MCP-compatible clients like Claude, with schema introspection and guarded SQL execution.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables Claude Desktop and Claude Code to inspect a Postgres schema and run read-only SQL queries against a demo database through the tools describe_schema and query_readonly. Every proposed query passes the same deterministic guardrails — SELECT-only, allow-listed tables, enforced row limits, statement timeouts and a read-only role — before execution.
    2
    MIT