Overview
The official MCP Postgres server exposes SQL query tools that Claude Code can call during a session, letting you ask questions about schema, data, and query plans in natural language. This guide installs the server, wires credentials without hardcoding them, and runs a verification query. The MCP permission model is in mcp-servers; security considerations are in mcp-security.
Prerequisites
- Claude Code installed and authenticated. If not, follow run-claude-code-with-mcp first.
- Node 22 on the path.
- A running Postgres instance. For local setup, see set-up-postgres-locally.
- A database user with read-only access. Never use a superuser account for MCP.
Create a read-only user if one does not exist:
CREATE ROLE mcp_readonly LOGIN PASSWORD 'strong-password-here';
GRANT CONNECT ON DATABASE mydb TO mcp_readonly;
GRANT USAGE ON SCHEMA public TO mcp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mcp_readonly;Steps
1. Install the MCP Postgres server
The server is published as @modelcontextprotocol/server-postgres. Use npx -y in the Claude Code settings so the package is fetched on demand; no global install is needed.
Test the package resolves without errors:
npx -y @modelcontextprotocol/server-postgres --helpIf the command prints usage, the package is reachable. If it fails with a network error, check your npm registry settings.
2. Store credentials safely
Never put the database password in .claude/settings.json directly, since that file is typically committed. Use environment variable substitution.
Export the connection string in your shell profile:
# ~/.zshrc or ~/.bashrc
export PG_MCP_URL="postgresql://mcp_readonly:strong-password-here@localhost:5432/mydb"For team projects, document the expected variable name in README.md and add the variable to .env.example with a placeholder value. Add .env to .gitignore.
3. Configure the MCP server in settings.json
Add the Postgres server to .claude/settings.json at the project root:
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "${PG_MCP_URL}"]
}
}
}The ${PG_MCP_URL} syntax reads from the environment at Claude Code startup. The connection string is never written to disk in plain text.
4. Restrict permissions to read-only tools
Limit which tools Claude Code may call without prompting. The Postgres MCP server exposes query and schema-reading tools; restrict to those.
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "${PG_MCP_URL}"]
}
},
"permissions": {
"allow": [
"mcp__postgres__query",
"mcp__postgres__list_tables",
"mcp__postgres__describe_table"
]
}
}Any write tool (if the server exposes one) requires manual confirmation. Pair this with the read-only database user for defense in depth. See mcp-security for the rationale.
5. Start Claude Code and verify connection
claudeInside the Claude Code session:
/mcp
Expected output:
postgres: connected (3 tools available)
If the server shows “disconnected”, check the troubleshooting section below.
6. Run a test query
Ask Claude Code to describe the schema:
List the tables in the database and show the columns of the users table.
Claude Code calls mcp__postgres__list_tables and mcp__postgres__describe_table. The response should show the real table names and column definitions.
Then run a data query:
How many users were created in the last 7 days?
Claude calls mcp__postgres__query with a SELECT count(*) statement. Verify the count matches a direct psql query.
Verify it worked
# Confirm the connection string is set
echo $PG_MCP_URL
# postgresql://mcp_readonly:...@localhost:5432/mydb
# Confirm the user has SELECT access
psql $PG_MCP_URL -c "SELECT count(*) FROM users;"
# Returns a row count without permission errors.
# Inside claude session: /mcp shows postgres: connectedCommon errors
postgres: disconnectedin /mcp. The${PG_MCP_URL}variable is not exported in the shell that startedclaude. Addexport PG_MCP_URL=...to your shell profile and restart the terminal.role "mcp_readonly" does not exist. Run theCREATE ROLEstatement in psql connected to the target database as a superuser.permission denied for table users. TheGRANT SELECTwas run before the table existed. Re-run the grant after creating the table, or useALTER DEFAULT PRIVILEGES.- Claude generates a destructive SQL query. The
permissions.allowlist does not prevent Claude from constructing a DELETE query and asking for confirmation; the read-only database role prevents it from executing. Both layers are necessary. - Connection string is logged in Claude Code output. Use the environment variable substitution syntax, not a hardcoded string. Check
.claude/settings.jsonfor any plain-text credentials and rotate them immediately.