pg-lens
Secure PostgreSQL integration for AI assistants via Model Context Protocol
Installation
npx pg-lens-mcpAsk AI about pg-lens
Powered by Claude Β· Grounded in docs
I know everything about pg-lens. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
PG Lens MCP Server
Securely connect AI assistants to your PostgreSQL databases
Features β’ Installation β’ Configuration β’ Tools β’ Security
β¨ Features
| Feature | Description |
|---|---|
| Read-Only by Design | All queries execute in database-enforced READ ONLY transactions |
| Full Schema Discovery | Explore schemas, tables, columns, indexes, and relationships |
| Query Performance Analysis | Built-in EXPLAIN and EXPLAIN ANALYZE for optimization |
| SQL-Injection Safe | Structured filters with parameterized queries |
| Token-Optimized Output | Markdown table formatting reduces AI token usage by ~40-60% |
| 8 Powerful Tools | Complete toolkit for database exploration and analysis |
| Production-Ready | Configurable connection pooling with timeouts and health checks |
ποΈ Architecture
graph LR
A[Claude/AI Assistant] -->|MCP Protocol| B[PostgreSQL MCP Server]
B -->|READ ONLY Transactions| C[(PostgreSQL Database)]
style B fill:#4CAF50,color:#fff
style C fill:#336791,color:#fff
The server acts as a secure bridge between AI assistants and your PostgreSQL database, enforcing read-only access at the database transaction level.
π¦ Installation
git clone https://github.com/YohannHommet/pg-lens-mcp.git
cd pg-lens-mcp
npm install
npm run build
βοΈ Configuration
Environment Variables
Configure the connection using environment variables:
| Variable | Description | Default |
|---|---|---|
DB_HOST | PostgreSQL host | localhost |
DB_PORT | PostgreSQL port | 5432 |
DB_DATABASE | Database name | postgres |
DB_USERNAME | Database user | postgres |
DB_PASSWORD | Database password | postgres |
DB_SCHEMA | Default schema | public |
DB_MAX_CONNECTIONS | Connection pool size | 10 |
DB_IDLE_TIMEOUT_MS | Idle connection timeout | 30000 |
DB_CONNECTION_TIMEOUT_MS | Connection attempt timeout | 5000 |
MCP Configuration
Add to your Claude Desktop config (~/.claude/claude_desktop_config.json):
Option 1: Direct Node.js (Local Installation)
{
"mcpServers": {
"postgres": {
"command": "node",
"args": ["/absolute/path/to/postgres-server/dist/index.js"],
"env": {
"DB_HOST": "localhost",
"DB_PORT": "5432",
"DB_DATABASE": "your_database",
"DB_USERNAME": "your_username",
"DB_PASSWORD": "your_password",
"DB_SCHEMA": "public"
}
}
}
}
Option 2: Using npx (No Installation Required)
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": [
"-y",
"pg-lens-mcp"
],
"env": {
"DB_HOST": "localhost",
"DB_PORT": "5432",
"DB_DATABASE": "your_database",
"DB_USERNAME": "your_username",
"DB_PASSWORD": "your_password",
"DB_SCHEMA": "public"
}
}
}
}
Option 3: Using Docker
{
"mcpServers": {
"postgres": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"--network=host",
"-e", "DB_HOST=localhost",
"-e", "DB_PORT=5432",
"-e", "DB_DATABASE=your_database",
"-e", "DB_USERNAME=your_username",
"-e", "DB_PASSWORD=your_password",
"-e", "DB_SCHEMA=public",
"pg-lens-mcp:latest"
]
}
}
}
Docker-specific notes:
- Use
--network=hostfor connecting to localhost databases - For remote databases, you can remove
--network=host - For databases in other Docker containers, use custom networks:
"args": [ "run", "--rm", "-i", "--network=your_docker_network", "-e", "DB_HOST=postgres_container_name", ... ]
π‘ Tip: Use a read-only database user for extra security, even though all queries run in READ ONLY transactions.
π οΈ Available Tools
ποΈ Schema Discovery
list_schemas β List all non-system schemas
Discover all user-defined schemas in your database.
Example usage:
"List all schemas in the database"
Returns: Markdown table with schema names and owners
list_tables β List all tables in a schema
Parameters:
schema(optional) β Schema name (default:public)
Example usage:
"Show me all tables in the public schema"
Returns: Markdown table with table names and types (TABLE, VIEW, etc.)
search_column β Find tables containing a column pattern
Parameters:
column_pattern(required) β Partial or full column name (case-insensitive)
Example usage:
"Find all tables that have an 'email' column"
Returns: Markdown table showing schema, table, column name, data type, and nullability
get_table_info β Get comprehensive table schema
Parameters:
table_name(required) β Name of the table to inspectschema(optional) β Schema name (default:public)
Example usage:
"Show me the complete structure of the users table"
Returns: JSON with:
- Column details (name, type, nullability, defaults)
- Primary keys
- Foreign key relationships
- Indexes with uniqueness information
π Data Querying
get_table_data β Query table data with structured filters
Parameters:
table_name(required) β Table to queryschema(optional) β Schema name (default:public)columns(optional) β Specific columns to select (default: all)filters(optional) β Structured filters (SQL injection safe!)[{ column: "status", operator: "=", // Options: =, !=, <, >, <=, >=, LIKE, ILIKE, IN, IS NULL, IS NOT NULL value: "active" }]limit(optional) β Max rows to return (default: 100, max: 1000)offset(optional) β Rows to skip for paginationorder_by(optional) β Column to sort byorder_direction(optional) βASCorDESC(default:ASC)
Example usage:
"Get the first 20 active users created after 2024-01-01, ordered by creation date"
Returns: Markdown table with:
- Query results
- Metadata (total rows, returned rows, pagination info)
execute_query β Execute custom read-only SQL
Parameters:
query(required) β SQL SELECT queryparams(optional) β Query parameters for$1,$2, etc.
Example usage:
"Execute this query:
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.name
ORDER BY order_count DESC
LIMIT 10"
Returns: Markdown table with query results
Security: Runs in BEGIN TRANSACTION READ ONLY β PostgreSQL itself enforces no writes can occur
β‘ Performance Analysis
explain_query β Get query execution plan (without running query)
Parameters:
query(required) β SQL query to analyzeformat(optional) βtext,json, oryaml(default:json)verbose(optional) β Include verbose details (default:false)
Example usage:
"Explain how PostgreSQL would execute: SELECT * FROM users WHERE email LIKE '%@example.com'"
Returns: Query execution plan showing:
- Scan types (Sequential Scan, Index Scan, etc.)
- Estimated costs and row counts
- Join strategies
Use case: Understanding query performance before optimization
explain_analyze β Execute and profile query performance
Parameters:
query(required) β SQL query to analyzeformat(optional) βtextorjson(default:json)buffers(optional) β Include buffer usage stats (default:false)timing(optional) β Include timing info (default:true)verbose(optional) β Verbose output (default:false)
Example usage:
"Analyze the actual performance of: SELECT * FROM large_table WHERE indexed_column = 'value'"
Returns: Actual execution statistics including:
- Real execution time
- Actual rows processed vs. estimated
- Buffer hits/misses (if
buffers: true) - Node-level timing breakdown
β οΈ Note: This actually executes the query (in READ ONLY mode). May be slow on large datasets.
π Security
Database-Enforced Read-Only Access
Unlike simple keyword filtering, this server uses PostgreSQL's transactional READ ONLY mode:
await client.query('BEGIN TRANSACTION READ ONLY');
const result = await client.query(userQuery); // β PostgreSQL blocks ANY writes
await client.query('COMMIT');
Why this matters:
β
No false positives β Queries containing words like "UPDATE" or "INSERT" in strings/comments work fine
β
No bypasses β Cannot be circumvented via stored procedures, functions, or extensions
β
Database-level guarantee β PostgreSQL itself enforces the read-only constraint
SQL Injection Protection
Structured filters replace dangerous string concatenation:
β Unsafe approach:
query += ` WHERE ${userInput}` // Direct concatenation = SQL injection risk
β Our approach:
filters: [{
column: "status",
operator: "=",
value: "active"
}]
// Becomes: WHERE "status" = $1
// PostgreSQL handles escaping automatically
All user inputs are properly parameterized, eliminating SQL injection vectors.
π§ͺ Testing the Server
Quick Test
# Set your database credentials
export DB_HOST=localhost
export DB_DATABASE=your_database
export DB_USERNAME=your_username
export DB_PASSWORD=your_password
# Start the server
node dist/index.js
Expected output:
β Database connection verified
β PostgreSQL MCP Server running on stdio
Testing with Claude Desktop
- Add the server to your MCP configuration
- Restart Claude Desktop
- Try these example prompts:
- "List all schemas in the database"
- "Show me the structure of the users table"
- "Find all tables with a 'created_at' column"
- "Explain the query plan for SELECT * FROM large_table LIMIT 10"
π Troubleshooting
Connection Issues
"password authentication failed"
- Check
DB_USERNAMEandDB_PASSWORDare correct - Verify the user has access to the specified database
"Connection timeout"
- Check
DB_HOSTis reachable - Verify PostgreSQL is running on
DB_PORT - Check firewall rules if connecting remotely
"database does not exist"
- Verify
DB_DATABASEname is correct - List available databases:
psql -l
Performance
If queries are slow:
- Use
explain_analyzetool to identify bottlenecks - Check if indexes exist on frequently queried columns
- Consider adjusting
DB_MAX_CONNECTIONSbased on your workload
π License
MIT License
π€ Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
Built for the Model Context Protocol ecosystem
Made with β€οΈ for AI-assisted database exploration
