Pg Lens Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Pg Lens Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center">
Securely connect AI assistants to your PostgreSQL databases
Features • Installation • Configuration • Tools • Security
</div>
| 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 |
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:#fffThe server acts as a secure bridge between AI assistants and your PostgreSQL database, enforcing read-only access at the database transaction level.
git clone https://github.com/YohannHommet/pg-lens-mcp.git
cd pg-lens-mcp
npm install
npm run buildConfigure 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 |
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:
--network=host for connecting to localhost databases--network=host "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.
<details> <summary><b>list_schemas</b> — List all non-system schemas</summary>
Discover all user-defined schemas in your database.
Example usage:
"List all schemas in the database"Returns: Markdown table with schema names and owners
</details>
<details> <summary><b>list_tables</b> — List all tables in a schema</summary>
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.)
</details>
<details> <summary><b>search_column</b> — Find tables containing a column pattern</summary>
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
</details>
<details> <summary><b>get_table_info</b> — Get comprehensive table schema</summary>
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:
</details>
<details> <summary><b>get_table_data</b> — Query table data with structured filters</summary>
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) — 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:
</details>
<details> <summary><b>execute_query</b> — Execute custom read-only SQL</summary>
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
</details>
<details> <summary><b>explain_query</b> — Get query execution plan (without running query)</summary>
Parameters:
query (required) — SQL query to analyzeformat (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:
Use case: Understanding query performance before optimization
</details>
<details> <summary><b>explain_analyze</b> — Execute and profile query performance</summary>
Parameters:
query (required) — SQL query to analyzeformat (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:
buffers: true)⚠️ Note: This actually executes the query (in READ ONLY mode). May be slow on large datasets.
</details>
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
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 automaticallyAll user inputs are properly parameterized, eliminating SQL injection vectors.
# 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.jsExpected output:
✓ Database connection verified
✓ PostgreSQL MCP Server running on stdio"password authentication failed"
DB_USERNAME and DB_PASSWORD are correct"Connection timeout"
DB_HOST is reachableDB_PORT"database does not exist"
DB_DATABASE name is correctpsql -lIf queries are slow:
explain_analyze tool to identify bottlenecksDB_MAX_CONNECTIONS based on your workloadMIT License
Contributions are welcome! Please feel free to submit issues or pull requests.
<div align="center">
Built for the Model Context Protocol ecosystem
Made with ❤️ for AI-assisted database exploration
</div>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.