safe-postgres-mcp
Allows read-only querying and schema exploration of a PostgreSQL database, with enforced read-only transactions, statement timeout, and row limits.
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., "@safe-postgres-mcplist all tables in the database"
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.
safe-postgres-mcp
The safe default for giving an AI agent your Postgres. A zero-config, read-only PostgreSQL MCP server where read-only isn't a regex you hope holds — it's enforced by Postgres itself. Every query runs inside a BEGIN TRANSACTION READ ONLY with a statement timeout and a row cap. One npx command: your agent can explore a schema and run SELECTs, but it physically cannot write, cannot stack a second statement, and a runaway query is cancelled within the statement timeout (default 5s) so it can't hang your database.
Why another Postgres MCP server?
Most "read-only" database tools enforce read-only by scanning the SQL text for scary keywords. That is a filter, and filters get bypassed. As of early 2026, the original TypeScript reference Postgres MCP server (@modelcontextprotocol/server-postgres) took exactly this text-filter approach and has since been archived in the modelcontextprotocol/servers repo, moved to the community servers-archived list with no maintained successor. Meanwhile popular DBA-oriented alternatives (e.g. Postgres MCP "Pro" / crystaldba) default to unrestricted read/write — read-only is an opt-in access-mode flag — and ship as a Python/Docker install that is friction for the Node/TS majority of MCP users. (Check each project's current docs; the ecosystem moves fast.)
This server inverts that. The guarantee doesn't live in a string matcher — it lives in the database engine.
Defense in depth, from the outside in:
Layer | What it does | Is it the guarantee? |
Keyword pre-check | Rejects obvious writes ( | No — it's fast-fail UX + a second line |
| Postgres cancels a runaway query instead of hanging your DB | Resource guardrail |
| The engine rejects any write ( | Yes — this is the real guarantee |
Row cap + | Truncates oversized result sets; never commits anything | Resource guardrail |
The keyword check is deliberately a courtesy, not the wall. Casing tricks, comment smuggling, or a data-modifying CTE that slips past the text analysis still hit a Postgres READ ONLY transaction and fail with error 25006. Belt and suspenders — and the suspenders are bolted to the engine.
For your outermost layer, point DATABASE_URL at a dedicated least-privilege read-only role (see .env.example). This server is the second wall behind that role, not a replacement for it.
Related MCP server: postgres-mcp-query-tool
60-second quickstart
You need Node >= 20 and a Postgres connection string. Two of the most common clients:
Claude Code
claude mcp add --env DATABASE_URL=postgres://user:pass@host:5432/dbname \
--transport stdio safe-postgres -- npx -y safe-postgres-mcpThe order matters: keep another flag (
--transport stdio) between--env KEY=valueand the server name, otherwise the CLI parses the name as anotherKEY=valuepair. Everything after--is handed to the server untouched.
Verify it's connected:
claude mcp list
claude mcp get safe-postgresAdd --scope project to share it with your team via a checked-in .mcp.json, or --scope user to enable it across all your projects.
Claude Desktop
Open Settings → Developer → Edit Config (or edit the file directly):
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"safe-postgres": {
"command": "npx",
"args": ["-y", "safe-postgres-mcp"],
"env": {
"DATABASE_URL": "postgres://user:pass@host:5432/dbname"
}
}
}
}Fully quit and reopen Claude Desktop to load it. If it doesn't appear, check ~/Library/Logs/Claude/mcp-server-safe-postgres.log (the server logs all diagnostics to stderr; stdout is reserved for the JSON-RPC channel).
Running from a local build
No npm publish required — build once and point any client at the absolute path:
git clone https://github.com/samuel-cabral/safe-postgres-mcp.git
cd safe-postgres-mcp && npm ci && npm run buildclaude mcp add --env DATABASE_URL=postgres://user:pass@host:5432/dbname \
--transport stdio safe-postgres -- node /abs/path/to/safe-postgres-mcp/build/index.jsThe server fails fast: a missing/invalid DATABASE_URL or an unreachable database exits with an actionable message before the agent ever calls a tool.
Tools
Five small, curated tools — a focused read-only toolset, deliberately not a 14-tool management suite. Every tool is read-only; nothing here can mutate your data.
Tool | Description | Parameters | Access |
| Run a single read-only SQL statement inside a |
| Executes SQL (read-only tx) |
| Return the query plan via |
| Plans only, never executes |
| List all non-system schemas with their owner. | — | Catalog read |
| List tables, views, and materialized views in a schema with approximate row counts (from planner stats — fast, no full scan) and on-disk size. |
| Catalog read |
| Full description of one table/view: columns (type, nullability, default), primary key, foreign keys, and indexes. |
| Catalog read |
Introspection tools query the Postgres system catalogs with parameterized lookups — identifiers are never string-interpolated into SQL. Every tool returns both human-readable text and typed structuredContent matching its output schema, so an agent can consume either.
Safety model
Each row is a thing an agent (or a hostile prompt steering one) could try, and the mechanism that stops it.
Threat | Mitigation |
Write / DDL — | Rejected by the |
Stacked-query injection — | Statement splitter (literal/comment/dollar-quote aware) rejects any input with more than one statement |
Data-modifying CTE — | Dedicated hidden-write scan of CTE bodies, backstopped by the |
Comment / casing smuggling — | Comments stripped (respecting string and dollar-quoted literals) before the head check; real enforcement is in the engine, not the text |
Runaway query hanging the DB — |
|
Oversized result set blowing up memory / agent context | The result is streamed through a server-side cursor that stops at |
Accidental persistence | Every transaction ends in |
Identifier injection via introspection | System-catalog lookups are parameterized; no identifier interpolation |
Disabling a guardrail via a bad env var | Config is validated with hard ceilings ( |
What this does not do: it does not mask or redact PII in rows you are allowed to SELECT, and it does not substitute for database-level permissions. Grant the connecting role only what the agent should ever see; this server enforces read-only and bounded, not authorized.
Configuration
All configuration is environment variables — that's the whole point of zero-config. See .env.example.
Variable | Required | Default | Max | Description |
| Yes | — | — | Postgres connection string. Point it at a least-privilege read-only role. |
| — | — | — | Fallback used only when |
| No |
|
| Per-statement timeout in ms. A slow query is cancelled, not run forever. |
| No |
|
| Hard cap on rows returned by |
When to use this vs. alternatives
Use this when you want an agent to safely read a plain Postgres — RDS, Neon, Supabase-as-plain-PG, or self-hosted — with a
READ ONLYguarantee enforced by the database, installed with onenpxcommand and no YAML, no Docker, no Go binary.Reach for a DBA-oriented tool (index tuning, health checks, hypothetical indexes) when you're doing performance engineering rather than agent-safe reads — and you're comfortable running it write-enabled.
Reach for a cloud-vendor server when you're fully inside that vendor's ecosystem (its auth, storage, and edge functions) and don't need neutral, portable Postgres access.
Development
npm ci
npm run build # tsc -> build/, chmod +x the bin
npm run typecheck # strict TS, no emit
npm test # vitest runThe suite runs 124 unit + MCP wiring tests with zero external dependencies. Safety parsing (comment stripping, statement splitting, literal-aware CTE-write detection, LIMIT/FETCH row-cap logic, dollar-quote tags) is exercised directly, and the MCP layer is tested end-to-end over an in-memory client/server transport — rejection paths run fully without a live database, because the safety check fires before the connection pool is ever touched.
A further 10 integration tests run against a real Postgres and are skipped automatically unless a DB is provided:
DATABASE_URL=postgres://user:pass@localhost:5432/db npm testThey only issue read-only queries, so a read-only replica is a perfectly safe target. CI (GitHub Actions) type-checks, builds, and tests on Node 20, 22, and 24 for every push and PR.
Built on the official @modelcontextprotocol/sdk (STDIO transport, protocol 2025-06-18) and pg. Written in strict TypeScript.
About
Built by Samuel Cabral — senior full-stack engineer (Node.js · TypeScript · NestJS · React · PostgreSQL). I build MCP servers and Claude Code / agent integrations, with a bias toward safety, tests, and tooling that a team can trust in production.
Available for MCP and Claude Code integration work.
GitHub: github.com/samuel-cabral
Email: samuelcabral.mail@gmail.com
Licensed under 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.
Latest Blog Posts
- 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/samuel-cabral/safe-postgres-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server