How to Build a Postgres MCP Server for Claude Code
MCP (Model Context Protocol) servers give Claude Code direct access to external tools and data sources — similar to how plugins and subagents extend its capabilities in other ways. Instead of copying and pasting query results into your prompt, you can wire up a Postgres database and let Claude explore schemas, run queries, and debug data issues in real time. Here is how to set it up.
What MCP servers are
MCP is an open protocol that lets AI tools communicate with external systems through a standardized interface. An MCP server exposes a set of tools (functions Claude can call), and Claude Code connects to these servers as a client. When you ask Claude a question that requires database access, it calls the appropriate tool, gets the result, and uses it in its response.
Think of it as giving Claude a read-only database client it can use on its own.
Using the official Postgres MCP server
The fastest path is the official @modelcontextprotocol/server-postgres package. It connects to any Postgres database and exposes tools for common operations.
Installation and configuration
Add the server to your project's .claude/settings.json (alongside your CLAUDE.md configuration):
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"postgresql://user:password@localhost:5432/mydb"
]
}
}
}
Restart Claude Code after saving. You should see the Postgres tools available when you type /mcp.
Exposed tools
The official server provides three tools:
- query -- Run a read-only SQL query and get results back as JSON. Claude uses this to answer questions about your data.
- list_tables -- Returns all tables in the database. Claude calls this first to understand what it is working with.
- describe_table -- Returns column names, types, and constraints for a given table. Claude uses this to write correct queries without guessing at column names.
These three tools cover the vast majority of database exploration workflows.
Example workflows
Once configured, you can have conversations like:
Exploring a database you are unfamiliar with:
Ask Claude "What tables are in this database and how do they relate to each other?" It will call list_tables, then describe_table on each result, and synthesize an overview of the schema and likely foreign key relationships.
Writing queries: Ask "Show me the top 10 customers by total order value in the last 30 days." Claude will inspect the relevant tables, figure out the joins, and write and execute the query -- then explain the results.
Debugging data issues: Ask "Why are there orders with null customer_ids?" Claude will query for examples, check constraints on the tables, look for patterns in the bad data, and help you figure out where things went wrong.
Security considerations
Giving an AI agent database access requires caution. Follow these rules:
Use a read-only connection. Create a dedicated Postgres role with SELECT-only permissions. Never use a role that can INSERT, UPDATE, DELETE, or modify schema.
CREATE ROLE claude_readonly WITH LOGIN PASSWORD 'a_strong_password';
GRANT CONNECT ON DATABASE mydb TO claude_readonly;
GRANT USAGE ON SCHEMA public TO claude_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO claude_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO claude_readonly;
Never use production credentials. Point the MCP server at a read replica, a staging database, or a local snapshot. If Claude runs an expensive query, you do not want it affecting your production traffic.
Do not check credentials into version control. Use environment variables or a .env file (gitignored) and reference them in your configuration:
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"$DATABASE_URL"
],
"env": {
"DATABASE_URL": "postgresql://claude_readonly:password@localhost:5432/mydb"
}
}
}
}
Limit row counts. If your tables are large, consider setting statement_timeout on the read-only role to prevent runaway queries:
ALTER ROLE claude_readonly SET statement_timeout = '5s';
Building a custom MCP server with FastMCP
The official package works well for general exploration. But if you need custom logic -- filtered access to specific tables, pre-built reporting queries, or write operations behind confirmation gates -- build your own server with Python's fastmcp library.
Install it:
pip install fastmcp psycopg2-binary
Here is a minimal server that exposes a safe query tool and a table listing tool:
from fastmcp import FastMCP
import psycopg2
import os
mcp = FastMCP("postgres-custom")
def get_connection() -> psycopg2.extensions.connection:
"""Create a new database connection from DATABASE_URL."""
return psycopg2.connect(os.environ["DATABASE_URL"])
@mcp.tool()
def run_query(sql: str) -> list[dict]:
"""Execute a read-only SQL query and return results as a list of dicts."""
conn = get_connection()
conn.set_session(readonly=True)
try:
with conn.cursor() as cur:
cur.execute(sql)
columns = [desc[0] for desc in cur.description]
return [dict(zip(columns, row)) for row in cur.fetchall()]
finally:
conn.close()
@mcp.tool()
def list_tables() -> list[str]:
"""List all user-defined tables in the public schema."""
rows = run_query(
"SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename"
)
return [row["tablename"] for row in rows]
@mcp.tool()
def describe_table(table_name: str) -> list[dict]:
"""Return column names, types, and nullability for a table."""
rows = run_query(f"""
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = '{table_name}'
ORDER BY ordinal_position
""")
return rows
Then configure Claude Code to use it:
{
"mcpServers": {
"postgres-custom": {
"command": "python",
"args": ["path/to/server.py"],
"env": {
"DATABASE_URL": "postgresql://claude_readonly:password@localhost:5432/mydb"
}
}
}
}
With a custom server you can add tools for specific reports, restrict which tables are visible, add parameterized queries to prevent SQL injection, or implement write operations that require explicit confirmation. The fastmcp library handles all the MCP protocol details so you just write normal Python functions.
When to use which approach
Use the official package when you need general-purpose database exploration with minimal setup. It works out of the box and covers most use cases.
Build a custom server when you need to restrict access to specific tables, add business-logic tools (like "get revenue for quarter"), enforce parameterized queries, or expose write operations with safety checks.
You can also apply this same MCP approach to other tools — for example, connecting Jira to Claude Code for ticket-driven development. Either way, the combination of Claude Code and a Postgres MCP server turns your database into something you can have a conversation with. Set up the read-only role, point the server at your database, and start asking questions.