safe-postgres-mcp
Provides read-only exploration and querying of a PostgreSQL database, including listing tables, describing schemas and relationships, running SELECT queries, and obtaining execution plans.
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-mcpWhat tables are in the database and what are their columns?"
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
An MCP server that lets a model explore and query PostgreSQL, built so that it cannot modify anything — not by convention, but because three independent layers each have to fail before a write becomes possible.
Runs locally over stdio (Claude Desktop today), with the transport isolated in a single file so an HTTP transport can be added later for a ChatGPT connector.
The three layers
1. Startup privilege audit. Before a single tool is registered, the server interrogates the connected role. If the role is too powerful, the process exits non-zero and the tools never exist. There is no degraded mode.
2. Lexical guard. Every statement must be a single read-only command. Comments and literals are
stripped before analysis, so a semicolon hidden inside 'a;b' is not a statement separator and a
DROP hidden behind a -- comment is still caught. Queries also travel over the extended query
protocol (queryMode: 'extended'), under which the backend refuses to parse more than one command —
a protocol-level defence that does not depend on our lexer being correct.
Passing
values: []is not enough to get that protection.pg'srequiresPreparation()returnsvalues.length > 0, so an empty array falls back to the simple query protocol, which acceptsSELECT 1; SELECT 2.queryModemust be set explicitly — seeextended()insrc/db.ts.
3. Read-only transaction. Every query runs inside BEGIN TRANSACTION READ ONLY with a statement
timeout, and is always rolled back — including on success. The session additionally sets
default_transaction_read_only=on.
Why layer 1 is not redundant
A read-only transaction is strong but, in PostgreSQL's own words, "does not prevent all writes to
disk". It reliably blocks INSERT/UPDATE/DELETE, all DDL, and those statements nested inside
functions, all with SQLSTATE 25006. It does not block:
Escape | Why it works | Required privilege |
| Opens a separate connection with its own read-write transaction |
|
| Runs a shell command; never writes to a table | superuser or |
| Arbitrary filesystem and network I/O inside a |
|
| Not a write, so read-only does not apply |
|
Every one of those needs a privilege the audit refuses. The audit is what turns the read-only transaction from a strong default into a guarantee.
What the audit checks
Always fatal — these let a query escape the read-only transaction:
SUPERUSER,BYPASSRLS,REPLICATIONmembership in
pg_execute_server_program,pg_write_server_files,pg_read_server_filesa reachable
dblink,postgres_fdw,file_fdw, or untrusted procedural language
Fatal by default, downgraded to a warning by ALLOW_WRITABLE_ROLE=true — the read-only transaction
does block these, so overriding is defensible:
INSERT/UPDATE/DELETE/TRUNCATEon any reachable tableCREATEon any reachable schema or databaseCREATEDB,CREATEROLE
Privileges are resolved with has_table_privilege() and friends, which account for inheritance
through role membership and through PUBLIC — something scanning information_schema misses.
Related MCP server: PostgreSQL MCP Server
Setup
npm installCreate a read-only role
CREATE ROLE mcp_reader LOGIN PASSWORD 'choose-a-strong-password';
GRANT USAGE ON SCHEMA tenant_acme TO mcp_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA tenant_acme TO mcp_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA tenant_acme GRANT SELECT ON TABLES TO mcp_reader;
-- PostgreSQL 14 and older grant CREATE on the public schema to PUBLIC by default:
REVOKE CREATE ON SCHEMA public FROM PUBLIC;If you skip this, the server tells you exactly which privilege blocked it and prints this script scoped to your schema.
Claude Desktop (Windows + WSL)
Dependencies are installed under WSL, so node_modules holds Linux binaries (@esbuild/linux-x64).
Claude Desktop runs on Windows and would use node.exe, which cannot load them — so the config
invokes the server through WSL rather than directly.
The config file lives in different places depending on how Claude Desktop was installed:
Install | Path |
Regular installer |
|
Microsoft Store (MSIX) |
|
MSIX packages virtualise %APPDATA%, so the Store build never creates %APPDATA%\Claude. On this
machine the real path is:
C:\Users\lucas\AppData\Local\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\That file also holds the app's own preferences, so add mcpServers to it — do not overwrite it.
{
"mcpServers": {
"postgres-safe": {
"command": "wsl.exe",
"args": [
"-d", "Ubuntu", "--",
"bash", "-lc",
"cd /mnt/c/Users/lucas/Desktop/mcpYt && exec npx tsx src/index.ts"
],
"env": {
"DATABASE_URL": "postgres://mcp_reader:password@host:5432/mydb",
"WSLENV": "DATABASE_URL/u:PG_SCHEMA/u:ALLOW_WRITABLE_ROLE/u:MAX_ROWS/u:STATEMENT_TIMEOUT_MS/u"
}
}
}
}WSLENV is what carries the variables across the Windows/Linux boundary — the /u suffix means
"forward this from Win32 into WSL". Keeping the URL in the env block rather than inline in the
shell command means passwords containing quotes or $ need no escaping.
If you would rather run entirely on Windows, run npm install from a Windows shell so npm fetches
@esbuild/win32-x64, then use "command": "npx" with a Windows path. Note that the two installs
overwrite each other in the same node_modules, so pick one.
The connection string lives in the client config, so it never enters the conversation.
Claude Code
.mcp.json in the project root already configures the server for Claude Code running inside WSL —
no wsl.exe wrapper needed. Fill in DATABASE_URL and it is picked up on the next session.
Configuration
Variable | Default | Meaning |
| (required) | Connection string |
| auto-detected | Tenant schema. Required only when the role can reach more than one |
|
| Downgrade the write-grant checks to warnings |
|
| Row cap per query |
|
| Per-query timeout |
Multi-tenant scoping
The server anchors to exactly one schema and every tool operates inside it, so unqualified table
names resolve there. The role's USAGE grants are the source of truth: if it can reach exactly one
non-system schema, that schema is detected automatically; if it can reach several, PG_SCHEMA
picks between them. The tenant boundary is enforced by PostgreSQL's grants, not by parsing SQL for
cross-schema references.
Tools
Tool | Purpose |
| Run one read-only statement |
| Tables, views and matviews with estimated rows, size and comments |
| Columns, types, defaults, PK, outbound and inbound FKs, constraints, indexes |
| The schema's whole foreign-key graph, for writing correct JOINs |
| Execution plan, never |
| Connection details and which safety checks passed |
Tests
npm run test:unit # lexical guard, no database needed
TEST_ADMIN_URL=postgres://owner:pw@host:5432/db npm run test:integrationThe integration suite creates a throwaway mcp_test schema with a read-only role and a writable
role, then drops both. Its load-bearing test issues an INSERT with a role that genuinely holds
INSERT, bypassing the lexical guard entirely, and asserts PostgreSQL rejects it with 25006 —
proving layer 3 works on its own rather than assuming it.
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
- AlicenseBqualityDmaintenanceA lightweight Postgres MCP server for safe database exploration and query analysis, read-only by default, with multi-database support.43MIT
- Alicense-qualityDmaintenanceAn open-source MCP server for PostgreSQL schema introspection and guarded read-only queries. It enables MCP clients to discover schemas, tables, columns, indexes, relationships, and safe queryable data from a configured PostgreSQL database.11MIT
- Alicense-qualityCmaintenanceRead-only PostgreSQL MCP server that enables running SELECT queries, listing tables and schemas, and describing columns, with built-in protection against writes and malicious SQL attacks.727MIT
- AlicenseAqualityCmaintenanceA self-hostable PostgreSQL MCP server for exploring database schemas and running guarded read/write queries with selectable access modes (readonly, readwrite, admin), plus a dry-run confirm workflow for safety.141MIT
Related MCP Connectors
MCP server for managing Prisma Postgres.
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
Security scanner for MCP servers. Detect vulnerabilities, prompt injection, and tool poisoning.
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/lucasoresi/mcp-local-optacost'
If you have feedback or need assistance with the MCP directory API, please join our Discord server