Skip to main content
Glama
sr9000

pg-mcp

by sr9000

pg-mcp

An MCP server for safe, audited PostgreSQL access, with a web UI.

pg-mcp puts a PostgreSQL database behind one or more named MCP endpoints. Each endpoint has its own bearer token and its own set of connections, and every tool call on it is written to an audit log. A browser UI manages connections, endpoints, database roles and the audit records.

Features

  • MCP endpoints at /<name>/mcp, on the main listener or on an endpoint's own port.

  • Eleven MCP tools: list_connections, list_schemas, list_tables, get_ddl, get_table_info, query, list_roles, explain, count_rows, sample_rows, find_columns.

  • SQL guard: read-only and dry-run calls are parsed with PostgreSQL's own grammar before anything runs, so transaction control, DDL, COPY, DO and other escapes are refused.

  • Bearer-token authentication per endpoint; only a SHA-256 digest of the token is stored.

  • Web UI at /ui/ for connections, endpoints, roles and audit records.

  • Audit log with rotation, grouping and a live event stream.

  • Configuration in YAML or JSON, in memory, or in a single file rewritten atomically.

Related MCP server: dbmcp

Requirements

  • Python 3.12 or newer

  • PostgreSQL (any supported server version)

  • uv for the source install, or pip

Install

From a checkout:

uv sync

Or install the package into a virtual environment:

python -m venv .venv
. .venv/bin/activate
pip install .

Quick start

Start with a configuration file and an admin password:

export PG_MCP_ADMIN_PASSWORD='choose-a-strong-password'
uv run pg-mcp serve --config pg-mcp.yaml

Open http://127.0.0.1:8765/ui/ and log in with the admin password.

Without --config, the server starts with an empty in-memory configuration:

uv run pg-mcp serve

By default the server listens on 127.0.0.1:8765 and writes pg-mcp-audit.log in the working directory. Run uv run pg-mcp serve --help for every flag and environment variable.

Configuration

settings:
  audit_max_bytes: 10485760
  audit_max_files: 5

connections:
  - name: app
    host: 127.0.0.1
    port: 5432
    database: appdb
    user: app_reader
    sslmode: prefer
    read_only: true
    statement_timeout_ms: 30000
    max_rows: 1000

endpoints:
  - name: analyst
    description: Read-only access for the analyst agent
    connections: [app]

An endpoint without a token loads disabled. Issue a token from the Endpoints view (or POST /api/endpoints/<name>/token); issuing a token enables the endpoint. The token is shown once and only its digest is kept.

A hand-written file may contain connection passwords, but it must be readable by its owner only (mode 0600); such a file is then read-only and changes stay in memory. Passwords entered through the UI are never written to the file.

The admin password comes from --admin-password-stdin, then PG_MCP_ADMIN_PASSWORD, then a terminal prompt. It is held in memory for the process lifetime and compared in constant time.

Connect an MCP client

Create an endpoint in the UI, assign a connection to it, and issue a token. Then point the client at the endpoint:

{
  "mcpServers": {
    "pg": {
      "url": "http://127.0.0.1:8765/analyst/mcp",
      "headers": {
        "Authorization": "Bearer <endpoint-token>"
      }
    }
  }
}

If the endpoint has a listen_port, use http://<listen_host>:<listen_port>/<name>/mcp instead. The admin session cookie is accepted only on /api/, never on an MCP endpoint.

How read-only access works

Set read_only: true on a connection, or pass read_only to query. A guarded call is parsed before a database session is opened: only SELECT, VALUES, TABLE, SHOW and EXPLAIN of a select are allowed, and data-modifying WITH, locking clauses and SELECT ... INTO are refused. The server owns the transaction, forces client_encoding to UTF8 and standard_conforming_strings on, and applies the connection's statement timeout.

explain with analyze: true passes the same guard. Full-access query calls without read_only, dry_run or isolation_level are sent unread, as configured by the administrator.

Development

uv run ruff check .
uv run mypy src
uv run pytest tests/unit -q

The SPA in web/ is built into src/pg_mcp/static/:

cd web
uv run npm ci
uv run npm run build

Integration and browser tests use testcontainers (Docker, postgres:16) and headless Chromium:

uv run playwright install chromium
uv run pytest tests/integration -q < /dev/null

The integration suite takes about 20 minutes. Unit tests are the normal gate; integration tests need Docker.

Security notes

  • Endpoint tokens are random, URL-safe strings; only their SHA-256 digest is stored.

  • The admin session is signed with a key generated at process start, so a restart ends every session.

  • Responses never include a connection password.

  • Every MCP tool call writes an audit record, including refused calls.

  • The server binds to 127.0.0.1 by default. Put it behind TLS and a trusted reverse proxy before exposing it beyond the local machine.

License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables secure querying of PostgreSQL databases through MCP-compatible clients. Supports read-only SQL execution, table exploration, and connection management with built-in security validation.
    3
    48 npm
    9
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables querying PostgreSQL databases via MCP, with multi-database routing, credential isolation, and truncated results plus full CSV export.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to safely query one or more PostgreSQL databases with read-only tools and explicitly approved write operations.
    128 npm
    1
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables governed access to corporate Postgres databases via MCP, supporting read-only queries, confirmed data and schema changes, query explanation, audit trail retrieval, and role-based policies with optional Vault secret integration.
    10
    MIT