postgres-mcp
Reference MCP server for PostgreSQL access. Read-first by design — schema introspection plus a configured-by-you query tool. Part of modelcontextprotocol/servers.
Default for read-only agentic SQL. For write workloads, prefer your app layer with proper migrations — not an LLM.
What it does
Three core tools: list_tables (every table the role can see), describe_table (columns, types, comments, indexes), and query (run an arbitrary SELECT). Some forks add execute (DDL / DML) and transaction wrappers. The canonical version stays close to read.
When you actually need it
Plain-English questions over your own database. “How many users signed up last week, broken down by source?” — the agent calls list_tables, picks the right table from the schema, drafts the query, runs it, returns the answer with the SQL it ran. The honesty of having the agent show its query is the whole point.
Setup
"postgres": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"postgresql://agent:password@localhost:5432/mydb"
]
}
The connection string carries the credentials. Two non-negotiables:
- Use a dedicated role. Not
postgres. Not your app’s role. A new role with onlyCONNECTto the database, onlySELECTon the tables you’ve thought about, and astatement_timeoutset short. - Default to read-only. Add
default_transaction_read_only = onfor the role.
CREATE ROLE agent_reader LOGIN PASSWORD 'rotate-this';
GRANT CONNECT ON DATABASE mydb TO agent_reader;
GRANT USAGE ON SCHEMA public TO agent_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_reader;
ALTER ROLE agent_reader SET default_transaction_read_only = on;
ALTER ROLE agent_reader SET statement_timeout = '30s';
Now even if the agent invents a DROP TABLE users, Postgres refuses.
The catch
The agent will sometimes write a query that returns a million rows. It will sometimes run a query that takes ten minutes. The MCP server doesn’t paginate — it returns whatever Postgres returns, and your context window is the limit.
Defensive moves: the role-level statement_timeout, plus a prompt rule (“if a query would return more than 200 rows, narrow it first”), plus a follow-the-money habit of watching pg_stat_activity while the agent is exploring an unfamiliar database.
FOR: Read-only agentic SQL over a Postgres database.
Use a dedicated Postgres role with SELECT-only and a short `statement_timeout`. Never connect with the `postgres` superuser or your app's role. For DDL/DML workloads, prefer migrations and code review — not an LLM with database write access.
Anthropic + the modelcontextprotocol working group.
Inherited from the modelcontextprotocol/servers monorepo.
Connection string carries credentials. Pass via env / args; the server doesn't persist or log it.
No filesystem operations.
Only the configured Postgres host.
Plain Postgres auth via connection string.
Whatever the connecting role can do. With a properly-scoped read-only role, this is small. With a default `postgres` superuser, this is everything.
Row contents the agent reads back can contain hostile instructions. Common in any user-generated text column (comments, descriptions). Treat all query output as untrusted text.
Direct deps are MCP SDK and pg (the canonical Node Postgres client). Both have strong release hygiene.
No MCP-layer rate limit. A long-running agent can run expensive queries; mitigations are at the Postgres role level (statement_timeout) not the MCP server.
npm package published with provenance attestations.
Wherever your Postgres lives. Query results enter the LLM context — pay attention to data-classification before pointing this at production.
Connection string lives in process env / args. No on-disk credential cache.
1 present · human approval optional
query
NOT A FORMAL SECURITY AUDIT · Adoption posture based on public-source review · See methodology for what's measured and what isn't