pg-sentinel
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pg-sentinelshow me the top 5 products by revenue"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
pg-sentinel
A PostgreSQL MCP server built like production infrastructure: three independent
safety layers, full query auditing, and query-plan intelligence. Let an LLM
explore and query your database without losing sleep — every statement is parsed
and proven read-only before it executes, inside a READ ONLY transaction, as a
role that couldn't write even if it wanted to.
Architecture
flowchart LR
subgraph client["MCP client (Claude Desktop, …)"]
LLM
end
LLM -- "tools & resources (stdio)" --> S
subgraph server["pg-sentinel"]
S[FastMCP server] --> A["Layer 1 · SQL analyzer<br/>(pglast parse tree)"]
A -- "verdict + policy" --> E["Layer 2 · executor<br/>READ ONLY txn, always rolled back<br/>timeout + row cap"]
S -. "audit log (structlog)" .-> L[(JSON logs)]
end
E -- "Layer 3 · read-only role<br/>(SELECT-only grants)" --> PG[(PostgreSQL)]Related MCP server: MCP PostgreSQL
Quick start
Try the demo (one command)
git clone https://github.com/anshujod/postgres_mcp_server && cd postgres_mcp_server/demo
docker compose up --buildThis starts Postgres 16 seeded with an e-commerce dataset (10k customers, 50k
orders, ~125k line items) and builds the pg-sentinel image connected as the
read-only sentinel_ro role. Postgres listens on host port 5433 (override
with PG_SENTINEL_DEMO_PORT if you like).
Run against your own database
PG_SENTINEL_DATABASE_URL=postgresql://user:pass@host:5432/mydb uvx pg-sentinelBest practice: create a dedicated read-only role first (layer 3), and connect as that:
CREATE ROLE sentinel_ro LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE mydb TO sentinel_ro;
GRANT USAGE ON SCHEMA public TO sentinel_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO sentinel_ro;Claude Desktop setup
Open your config file:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
Add pg-sentinel under
mcpServers(uvx form shown; a docker form is indemo/claude_desktop_config.json):{ "mcpServers": { "pg-sentinel": { "command": "uvx", "args": ["pg-sentinel"], "env": { "PG_SENTINEL_DATABASE_URL": "postgresql://sentinel_ro:sentinel_ro_demo@localhost:5433/demo" } } } }Restart Claude Desktop. Ask something like “which product category had the highest revenue last quarter?” and watch it write and run the SQL.
The safety model: three independent layers
Any single defense can have a hole. pg-sentinel stacks three, each of which alone blocks writes — an attacker (or a confused LLM) has to get through all of them at once.
Layer | Mechanism | Catches |
1 — SQL analyzer | Every statement is parsed with | Writable CTEs ( |
2 — READ ONLY transaction | Every query runs inside | Anything that somehow slips past the analyzer — Postgres itself rejects the write. Verified by test: an |
3 — Read-only DB role | The server connects as a role with only | Bugs in pg-sentinel itself. Even with layers 1–2 gone, |
An optional policy layer adds table allow/denylists (glob patterns like
public.*, deny auth.*) and per-query limits. Every call is logged as
structured JSON — SQL, verdict, duration, row count — for a full audit trail.
Tool reference
Tool | Arguments | What it does |
|
| Run a read-only SELECT. Rejections return |
|
| Query plan with a human summary: total cost, actual time (if analyzed), most expensive node, join strategies, and seq scans on large tables flagged as possible missing indexes. |
|
| Tables with approximate row counts and comments. |
|
| Columns, primary key, foreign keys, indexes, comment. |
|
| Shortlist the tables most relevant to a natural-language question (ranked by name/column/comment overlap), so large schemas don't flood the context. |
|
| A few example rows; uses |
Write mode (opt-in)
Off by default. Set PG_SENTINEL_WRITE_MODE=1 (and connect as a role that can
actually write) to expose a preview-and-confirm write path. The read tools stay
read-only regardless.
Tool | Arguments | What it does |
|
| Runs a single |
|
| Commits the exact transaction that was previewed. |
|
| Rolls back a pending preview. |
Unconfirmed previews auto-roll-back after PG_SENTINEL_WRITE_PREVIEW_TTL_SECONDS
(Postgres' own idle_in_transaction_session_timeout enforces it), and only a
handful may be pending at once.
Resources: schema://tables (all tables) and schema://tables/{schema}/{table}
(one table in detail) expose the same introspection for resource-aware clients.
Configuration
All settings are environment variables with the PG_SENTINEL_ prefix:
Variable | Default | Meaning |
| (required) | Postgres DSN. |
|
| Per-query timeout. |
|
| Hard cap on returned rows (results are marked |
|
| Safety switch; the server refuses to run queries if disabled. |
|
| Opt-in preview/confirm write tools (see above). |
|
| How long an unconfirmed write preview is held before auto-rollback. |
|
| Max affected rows shown in a write preview. |
|
| Connection pool bounds. |
|
| Log level. |
| unset |
|
Design decisions
Why a real parser (pglast) instead of regex or keyword filtering?
Regexes cannot see structure. WITH x AS (DELETE FROM t RETURNING *) SELECT * FROM x
contains a destructive write with no leading DELETE; a dollar-quoted string
SELECT $$DROP TABLE users$$ contains scary keywords but is harmless data.
pglast wraps PostgreSQL's actual parser, so pg-sentinel makes decisions on the
same syntax tree the database itself would execute — no false negatives from
clever encodings, no false positives from string contents. The adversarial test
suite (50+ cases) encodes exactly these attacks.
Why do rejections return messages instead of raising errors?
The consumer is an LLM. A raised exception surfaces as an opaque protocol error;
a returned Query rejected: only SELECT statements are allowed, got DELETE is
something the model can read, relay to the user, and act on — usually by
rewriting the query correctly on the next attempt.
The EXPLAIN ANALYZE rule.
Plain EXPLAIN only plans a statement, but EXPLAIN ANALYZE executes it —
EXPLAIN ANALYZE DELETE FROM users deletes your users. pg-sentinel therefore
never accepts EXPLAIN as raw SQL; the explain_query tool analyzes the inner
statement with the same layer-1 rules, so anything reaching ANALYZE true is
already a proven-safe SELECT — which layers 2 and 3 then guard anyway.
Why fetch max_rows + 1 through a cursor?
Fetching one row past the cap distinguishes "exactly 500 rows" from "truncated
at 500" without COUNT(*) overhead, and the cursor keeps a SELECT * FROM huge_table from ever materializing in server memory.
Development
uv sync # install everything
uv run pytest -m "not integration" # unit tests (no Docker needed)
uv run pytest # full suite (spins up Postgres 16 in Docker)
uv run ruff check src tests && uv run mypy srcFurther reading: the blog-post outline covers the threat model and what the adversarial suite caught during development.
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceA PostgreSQL MCP server with AST-based security for safe database operations. Enables AI assistants to query and manage PostgreSQL databases securely.MIT
- Alicense-qualityDmaintenanceA read-only MCP server for PostgreSQL that enables safe database introspection and querying via natural language.727MIT
- Alicense-qualityAmaintenanceA hardened, read-only Postgres MCP server that enables LLMs to safely query databases without write, DDL, shell, or credential exposure.MIT
- FlicenseAqualityBmaintenanceAn MCP server that enables AI agents to securely interact with PostgreSQL databases with least-privilege scopes, PII masking, and human approval for writes.4
Related MCP Connectors
Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.
GibsonAI MCP server: manage your databases with natural language
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/anshujod/postgres_mcp_server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server