Skip to main content
Glama

PG Lens MCP Server

Securely connect AI assistants to your PostgreSQL databases

Version License Node TypeScript

FeaturesInstallationConfigurationToolsSecurity


✨ 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


Related MCP server: PostgreSQL MCP Server

🏗️ 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=host for 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

Discover all user-defined schemas in your database.

Example usage:

"List all schemas in the database"

Returns: Markdown table with schema names and owners

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.)

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

Parameters:

  • table_name (required) — Name of the table to inspect

  • schema (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

Parameters:

  • table_name (required) — Table to query

  • schema (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 pagination

  • order_by (optional) — Column to sort by

  • order_direction (optional)ASC or DESC (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)

Parameters:

  • query (required) — SQL SELECT query

  • params (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

Parameters:

  • query (required) — SQL query to analyze

  • format (optional)text, json, or yaml (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

Parameters:

  • query (required) — SQL query to analyze

  • format (optional)text or json (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

  1. Add the server to your MCP configuration

  2. Restart Claude Desktop

  3. 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_USERNAME and DB_PASSWORD are correct

  • Verify the user has access to the specified database

"Connection timeout"

  • Check DB_HOST is reachable

  • Verify PostgreSQL is running on DB_PORT

  • Check firewall rules if connecting remotely

"database does not exist"

  • Verify DB_DATABASE name is correct

  • List available databases: psql -l

Performance

If queries are slow:

  1. Use explain_analyze tool to identify bottlenecks

  2. Check if indexes exist on frequently queried columns

  3. Consider adjusting DB_MAX_CONNECTIONS based 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

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
2Releases (12mo)
Commit activity

Latest Blog Posts

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/YohannHommet/pg-lens-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server