pgscout
Provides safe, read-only insight into PostgreSQL databases, including schema discovery, guarded SELECT/EXPLAIN queries, query plan summaries, index suggestions, deny-column policies, result redaction, and audit logging.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pgscoutwhat columns and indexes does the orders table have?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
pgscout
A Model Context Protocol (MCP) server that gives agents safe, read-only insight into a PostgreSQL database: schema discovery, guarded queries, EXPLAIN summaries and index suggestions, all behind one policy layer with an audit trail.
The MCP stdio transport (JSON-RPC 2.0, newline-delimited) is implemented directly in under 200 lines, so the server has a single runtime dependency (psycopg) and no SDK to track.
Why
Agents are useful at the "why is this query slow / what does this table look like" stage of database work, but handing one a connection string is handing it DROP TABLE. pgscout narrows the surface to what inspection actually needs and makes every call explainable after the fact:
the agent can only read, and only from the schemas you allow;
sensitive columns are refused in SQL and redacted in results;
every call, allowed or denied, lands in a JSON lines audit log.
Related MCP server: PostgreSQL MCP Server
Tools
Tool | What it returns |
| Allowlisted schemas that exist in the database |
| Tables, views and materialized views with estimated row counts |
| Columns (type, nullability, default), indexes, foreign keys; denied columns are flagged |
| Rows from one |
|
|
| Heuristic suggestions with ready |
Resources: pgscout://policy (the effective policy) and pgscout://schema/<name> (table list per allowed schema).
Security model
Defence in depth. No single layer is trusted on its own.
Database role (the real boundary). Run pgscout as a dedicated role with
SELECTgrants only, and use column privileges for anything truly secret. Everything below is there to make mistakes in that setup survivable.Read-only session and transaction. The session default is
READ ONLY; every call additionally runsSET TRANSACTION READ ONLY, sets a localstatement_timeoutandsearch_path(the allowlisted schemas), and is always rolled back.SQL guard. A tokenizer that understands string literals (
'...',E'...',$tag$...$tag$), quoted identifiers and nested block comments. It accepts exactly one statement starting withSELECT,WITHorEXPLAINand rejects:a second statement after
;(trailing semicolons and comments are fine);write and DDL keywords anywhere, including data-modifying CTEs and
SELECT ... INTO;row-locking clauses (
FOR UPDATE,FOR SHARE...);functions that sleep, read files, signal backends, take advisory locks, change settings (
set_configcould lift the timeout) or bump sequences;EXPLAIN ANALYZEunless the policy allows it, because it executes the query.
Policy (one choke point). Every tool call and resource read passes through
Gate: argument validation against the tool's JSON Schema, schema allowlist (bothschemaarguments and schema-qualified names in SQL, includingpg_catalogandinformation_schema), denied-column checks, row cap, result redaction, audit.Denied columns. Glob patterns such as
*password*,ssn,*token*. A query that names a denied column is refused (an alias would otherwise defeat redaction);SELECT *results have those columns replaced with[REDACTED].
Known limits, stated plainly: name-based redaction cannot see through whole-row expressions such as row_to_json(t), and unqualified references to pg_catalog views resolve implicitly. Both are why layer 1 matters.
Architecture
flowchart LR
C["MCP client / agent"] -->|"JSON-RPC over stdio"| S["McpServer<br>initialize, tools, resources"]
S --> G["Gate<br>single choke point"]
G --> V["validate args<br>JSON Schema subset"]
G --> Q["sqlguard<br>one read-only statement"]
G --> P["Policy<br>schemas, denied columns, row cap"]
G --> T["tool handlers"]
T --> D[("Database protocol")]
D --> PG["PsycopgDatabase<br>READ ONLY tx, statement_timeout"]
D --> F["FakeDatabase<br>tests and --demo"]
T --> E["explain<br>plan summary, index heuristics"]
G --> A["AuditLog<br>JSON lines"]src/pgscout/
server.py JSON-RPC 2.0 framing and MCP methods
gate.py authorisation choke point and audit
policy.py Policy, PolicyDenied, AuditLog
sqlguard.py tokenizer and read-only statement guard
validation.py JSON Schema subset for tool arguments
tools.py tool specs and handlers
explain.py plan summaries and index suggestions
db.py Database protocol and psycopg implementation
fake.py in-memory Database and demo data
cli.py argument / environment configurationQuick start
pip install . # Python 3.11+
pgscout --demo # in-memory sample shop database, no PostgreSQL needed
PGSCOUT_DSN="postgresql://scout@localhost/app" pgscout --schemas public,reportingTalk to it by hand:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"suggest_indexes","arguments":{"sql":"SELECT * FROM orders WHERE status = '\''paid'\''"}}}' \
| pgscout --demo --audit-log audit.jsonlA minimal role for production use:
CREATE ROLE scout LOGIN PASSWORD '...';
GRANT USAGE ON SCHEMA public TO scout;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO scout;
REVOKE SELECT (password_hash) ON public.customers FROM scout; -- column-level, if neededClient configuration
Most MCP clients accept a JSON block like this:
{
"mcpServers": {
"pgscout": {
"command": "pgscout",
"args": ["--schemas", "public", "--max-rows", "200"],
"env": {
"PGSCOUT_DSN": "postgresql://scout:secret@localhost:5432/app",
"PGSCOUT_AUDIT_LOG": "/var/log/pgscout/audit.jsonl"
}
}
}
}With Docker: "command": "docker", "args": ["run", "-i", "--rm", "-e", "PGSCOUT_DSN", "pgscout"].
Configuration
Flag | Environment | Default |
|
| none (required unless |
|
|
|
|
|
|
|
|
|
|
|
|
|
| stderr |
| off |
Audit records look like:
{"arguments": {"sql": "SELECT 1; DELETE FROM orders"}, "decision": "denied", "duration_ms": 0.11, "kind": "tool", "reason": "only a single statement is allowed", "target": "run_readonly_query", "ts": "2026-10-19T09:12:03.418+00:00"}Design decisions
Protocol implemented by hand. Newline-delimited JSON-RPC with six methods is small enough to own. It keeps the dependency list to the database driver and makes the wire behaviour easy to test in-process.
Tool errors are results, protocol errors are errors. A denied query returns
isError: truewith the reason, so the agent can correct itself; an unknown method or tool returns a JSON-RPC error.A tokenizer, not regexes, guards SQL. Every bypass trick lives in the lexical layer (a
;in a comment, a quote inside a dollar body, an escaped quote inE''). Getting the lexer right closes that whole class; the read-only transaction catches what a lexer cannot.Refuse, then redact. Naming a denied column fails the call; redaction is the second net for
SELECT *.Row cap by fetch, not by rewriting SQL. pgscout fetches
max_rows + 1and reportstruncated, so the statement the agent wrote is the statement that runs.Databaseis a protocol. The psycopg implementation and the in-memory fake share it, so the whole server, including the stdio transport, is tested without PostgreSQL. A separate integration suite runs against a real server in CI.Index suggestions are heuristics and say so. Equality columns first, then one range column; unindexed foreign keys; an existing index on the leading column produces a note about statistics instead of a duplicate index.
What it deliberately does not do: write, run DDL, create the indexes it suggests, hold connections open in a transaction, or manage credentials.
Running tests
python3 -m venv .venv
.venv/bin/pip install pytest ruff "psycopg[binary]>=3.1"
.venv/bin/python -m pytest # unit and protocol tests, no database needed
.venv/bin/ruff check . && .venv/bin/ruff format --check .
# integration tests against a real PostgreSQL (creates and drops schema scout_it)
PGSCOUT_TEST_DSN=postgresql://postgres:postgres@localhost:5432/postgres .venv/bin/python -m pytestThe code is written to run on Python 3.9+ even though the package targets 3.11+, so the test suite runs on older system interpreters too.
License
MIT, see LICENSE. Author: Ahmed Mahdy.
This server cannot be deployed
Maintenance
Related MCP Connectors
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
Deterministic safety, correctness & cost gate that vets Postgres SQL before your AI agent runs it.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to safely interact with PostgreSQL databases through read-only operations, providing schema discovery, table inspection, and query execution capabilities with structured context awareness.MIT
- FlicenseAqualityDmaintenanceEnables AI agents to inspect and query PostgreSQL databases safely, with features like listing tables, retrieving schemas, and running read-only SQL queries.3-
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to query PostgreSQL, inspect schemas, and explain queries, designed for local and development databases with read-only safety by default.18 npmMIT
- FlicenseNot gradedqualityCmaintenanceEnables AI assistants to safely inspect and query PostgreSQL databases with read-only enforcement, row/timeout limits, and audit logging, providing tools for schema/table listing, read-only queries, query explanations, and table statistics.-