mcp-read-only-sql

mcp-read-only-sql

Provides secure read-only SQL access to PostgreSQL and ClickHouse databases with built-in safety features like read-only enforcement, timeouts, and managed result files.

Category
Visit Server

README

MCP Read-Only SQL Server

Tests

A secure MCP (Model Context Protocol) server that provides read-only SQL access to PostgreSQL and ClickHouse databases with built-in safety features.

Default layout:

  • Config: ~/.config/lukleh/mcp-read-only-sql/connections.yaml
  • Credentials: stored in connections.yaml
  • State: ~/.local/state/lukleh/mcp-read-only-sql/
  • Query results: ~/.local/state/lukleh/mcp-read-only-sql/results/
  • Cache: ~/.cache/lukleh/mcp-read-only-sql/

Security

The server implements a three-layer security model:

  1. Database-level read-only - Sessions forced to read-only mode
  2. Timeout protection - Connection and query timeouts are configurable per connection
  3. Managed result files - Successful query results are written to state_dir/results with 0600 permissions

All write operations (INSERT, UPDATE, DELETE, etc.) are blocked at the database level.

How Read-Only Is Enforced

  • PostgreSQL (Python) – Connections are opened with default_transaction_read_only=on, sessions are set to read-only, and every statement runs with a configurable statement_timeout.
  • PostgreSQL (CLI) – Queries are wrapped in a transaction that issues SET TRANSACTION READ ONLY; before execution. Input is sanitized so only a single statement (plus optional trailing semicolon) is forwarded, transaction-control keywords are rejected up front, and all psql invocations include --single-transaction, -v ON_ERROR_STOP=1, and PGOPTIONS=-c default_transaction_read_only=on for defence in depth.
  • ClickHouse (Python) – The driver sets readonly=1 plus connection/query timeouts, forcing the server to reject any write or DDL attempt.
  • ClickHouse (CLI)clickhouse-client is invoked with --readonly=1, --max_execution_time, and connection timeouts, turning the session into a read-only context.

The shared connector base also applies hard timeouts, giving the MCP server deterministic behaviour even if the database misbehaves.

See READ_ONLY_ENFORCEMENT_MATRIX.md for a statement-by-statement view of every write-capable command and the tests that enforce it.

Key Features

  • Read-only enforcement - Multiple layers of protection against writes
  • Multi-database support - PostgreSQL and ClickHouse
  • Dual implementations - Choose between Python (pure Python, no dependencies) or CLI (uses psql/clickhouse-client)
  • SSH tunnel support - Both implementations support key authentication, password authentication (Paramiko in Python, sshpass in CLI), and falling back to agent-loaded identities when no credentials are provided
  • Security built-in - Timeouts, managed result files, session controls
  • DBeaver import - Import existing connections easily

Prerequisites

  • uv for package installs and ephemeral uvx runs
  • psql if you want PostgreSQL connections with implementation: cli
  • clickhouse-client if you want ClickHouse connections with implementation: cli
  • sshpass only if you want CLI-based SSH tunnels with password authentication
  • just is optional and only needed for repo-local contributor workflows

Install the optional CLI binaries with your operating system's package manager or the official PostgreSQL / ClickHouse packages for your environment.

The CLI binaries are located via the override environment variable (MCP_READ_ONLY_SQL_PSQL_PATH / MCP_READ_ONLY_SQL_CLICKHOUSE_CLIENT_PATH) if set, then PATH, then OS-specific fallback locations (e.g. Homebrew keg-only libpq on macOS, packaged PostgreSQL directories on Linux). If a binary is installed somewhere not on PATH, set the matching variable to its full path.

The SQL package keeps both execution models first-class:

  • implementation: cli uses the official database client binaries you already trust in operations.
  • implementation: python stays fully supported when you want a pure-Python setup with no external database client binaries.

You can verify optional CLI dependencies with:

psql --version
clickhouse-client --version
sshpass -V

Quick Start

1. Install or Run the Server

For the published package, prefer @latest with uvx:

uvx mcp-read-only-sql@latest --write-sample-config

Or install it once and reuse the command directly:

uv tool install mcp-read-only-sql
mcp-read-only-sql --write-sample-config

When using uvx with the published package, prefer mcp-read-only-sql@latest in user-facing docs and MCP client configs. This avoids reusing a stale cached tool environment after a new release is published.

For one-off runs from this checkout, use uvx --from .:

uvx --from . mcp-read-only-sql --write-sample-config

For a persistent local install from this checkout:

uv tool install .
mcp-read-only-sql --write-sample-config

For checkout-based commands below, you can replace uvx --from . mcp-read-only-sql with uvx mcp-read-only-sql@latest once you want to use the published package instead.

That creates:

  • ~/.config/lukleh/mcp-read-only-sql/connections.yaml
  • ~/.local/state/lukleh/mcp-read-only-sql/
  • ~/.local/state/lukleh/mcp-read-only-sql/results/
  • ~/.cache/lukleh/mcp-read-only-sql/

2. Choose an Implementation Per Connection

connections.yaml supports both implementations side by side:

- connection_name: postgres_cli
  type: postgresql
  implementation: cli
  servers:
    - "db.example.com:5432"
  db: analytics
  username: analyst
  password: change_me

- connection_name: clickhouse_python
  type: clickhouse
  implementation: python
  servers:
    - "analytics.example.com:8123"
  db: default
  username: analyst
  password: change_me

Use CLI mode when you want the behavior of psql or clickhouse-client, or when those tools are already part of your operational setup. Use Python mode when you want a package-only setup with no extra system binaries.

3. Import or Edit connections.yaml

You can edit the generated sample directly, or import a DBeaver workspace:

uvx --from . mcp-read-only-sql import-dbeaver \
  ~/Library/DBeaverData/workspace6/General/.dbeaver

That writes connections.yaml with any decrypted passwords stored directly in the file. The importer writes user-only permissions and keeps timestamped backups when it overwrites an existing file.

connections.yaml contains credentials. Keep it private, do not commit it, and restart the MCP process after editing it so changes take effect.

To allow a connection to access multiple databases, add an explicit allowlist:

- connection_name: analytics_multi
  type: postgresql
  servers:
    - "analytics.example.com:5432"
  allowed_databases:
    - analytics
    - reporting
  default_database: analytics
  username: analyst
  password: change_me

If you only set db, that single database is implicitly the allowlist.

4. Validate and Test Connections

The package includes management subcommands for connection validation and dry-run testing:

uvx --from . mcp-read-only-sql validate-config
uvx --from . mcp-read-only-sql test-connection
uvx --from . mcp-read-only-sql test-connection my_postgres
uvx --from . mcp-read-only-sql test-ssh-tunnel
uvx --from . mcp-read-only-sql --print-paths

If you are working from a clone, the same helpers are available through just:

just validate
just test-connection
just test-connection my_postgres
just print-paths

5. Add the MCP Server to Your Client

For Claude Code:

claude mcp add mcp-read-only-sql -- uvx mcp-read-only-sql@latest

For Codex:

codex mcp add mcp-read-only-sql -- uvx mcp-read-only-sql@latest

For manual testing with a different config root:

uvx mcp-read-only-sql@latest --config-dir /path/to/config-dir --print-paths

MCP Tools

run_query_read_only

Execute read-only SQL queries on configured databases.

{
  "connection_name": "my_postgres",
  "query": "SELECT * FROM users LIMIT 10",
  "database": "analytics",
  "server": "db2.example.com"
}

Parameters:

  • connection_name (required): Identifier returned by list_connections
  • query (required): SQL text that must remain read-only
  • database (optional): Database to use (must be listed in the connection's allowlist).
  • server (optional): Hostname to target a specific server. If not provided, uses the first server in the connection's list.

Returns: Absolute path to a TSV file created under the server's managed state directory, typically ~/.local/state/lukleh/mcp-read-only-sql/results/. Successful query results are persisted with 0600 permissions and are no longer returned inline on success.

Result files accumulate under state_dir/results/ until you remove them. If you do not want to retain old query output, periodically clean ~/.local/state/lukleh/mcp-read-only-sql/results/.

list_connections

List all available database connections.

Returns: Tab-separated text with columns name, type, description, servers, database, databases, and user. database is the default database, while databases lists the allowlisted databases (comma-separated). The servers column lists comma-separated hostnames after resolving SSH/VPN tunnels, so entries reflect the endpoints the agent should reference.

Implementation Matrix

Database Support by Implementation

Feature PostgreSQL CLI PostgreSQL Python ClickHouse CLI ClickHouse Python
Protocol Native PostgreSQL Native PostgreSQL Native ClickHouse HTTP/HTTPS
Default Port 5432 5432 9000 8123
Supported Ports Any PostgreSQL port Any PostgreSQL port 9000, 9440 (native + TLS) 8123 (HTTP), 8443 (HTTPS)
TLS/SSL Support ✅ Yes ✅ Yes ✅ Yes (--secure for 9440) ✅ Yes (HTTPS on 8443)
Read-Only Method SET TRANSACTION READ ONLY default_transaction_read_only=on --readonly=1 flag readonly=1 setting
SSH Key Auth ✅ Yes ✅ Yes ✅ Yes ✅ Yes
SSH Password Auth ✅ Yes (requires sshpass) ✅ Yes (Paramiko) ✅ Yes (requires sshpass) ✅ Yes (Paramiko)
Timeout Control ✅ Via SQL ✅ Driver-level ✅ CLI flags ✅ Driver-level
Result Streaming ✅ Yes ✅ Yes ✅ Yes ✅ Yes
Binary Required psql None clickhouse-client None

ClickHouse Port Compatibility

Port Protocol CLI Support Python Support Notes
8123 HTTP ⚠️ Auto-converts to 9000 ✅ Native support Default HTTP interface
8443 HTTPS ⚠️ Auto-converts to 9440 (--secure) ✅ Native support Secure HTTP interface
9000 Native TCP ✅ Native support ⚠️ Auto-converts to 8123 Default native protocol
9440 Native TCP (TLS) ✅ Native support (--secure) ⚠️ Auto-converts to 8443 (HTTPS) Secure native protocol
Custom (e.g., 2650) Usually HTTP ❌ No conversion ✅ Yes HAProxy/Load balancers - NO auto-conversion

Important Notes:

  • ClickHouse CLI (clickhouse-client) uses native protocol ports (9000, 9440)
  • ClickHouse Python (using clickhouse-connect) uses HTTP/HTTPS ports (8123, 8443)
  • Port mismatches are automatically handled - see below

Automatic Port Handling (Bidirectional):

ClickHouse Python Implementation:

  • Direct connections: Port 9000 → automatically uses port 8123 on the same host
  • SSH tunnels: Port 9000 → automatically tunnels to remote port 8123
  • SSH tunnels: Port 9440 → automatically tunnels to remote port 8443

ClickHouse CLI Implementation:

  • Direct connections: Port 8123 → automatically uses port 9000 on the same host
  • SSH tunnels: Port 8123 → automatically tunnels to remote port 9000
  • SSH tunnels: Port 8443 → automatically tunnels to remote port 9440

This means you can use the same configuration for both CLI and Python implementations, regardless of which port you specify (8123 or 9000) - each implementation will automatically convert to the correct protocol port it needs!

Choosing an Implementation

Use CLI implementation when:

  • You have the database CLI tools installed (psql, clickhouse-client)
  • You prefer not to install Python database drivers
  • You're connecting to ClickHouse on native ports (9000, 9440)
  • You want the exact behavior of the official CLI tools

Use Python implementation when:

  • You want a pure Python solution with no external dependencies
  • You're connecting to ClickHouse HTTP interface (port 8123, 8443)
  • You need SSH password authentication without installing sshpass
  • You want more programmatic control over connections

Configuration Notes

HAProxy and Custom Ports

When using HAProxy or other proxy servers with ClickHouse:

  • HAProxy typically provides HTTP interface on custom ports (e.g., 2650, 8000, etc.)
  • Custom ports are NOT auto-converted - the system only converts standard ports (8123, 8443, 9000, 9440)
  • For HAProxy connections: Use implementation: python since HAProxy usually proxies HTTP traffic
  • If you get "Unexpected packet" errors with CLI on custom ports, switch to Python implementation

Example HAProxy configuration:

- connection_name: clickhouse_haproxy
  type: clickhouse
  servers:
  - haproxy-server:2650  # Custom HAProxy port
  implementation: python  # Use Python for HTTP protocol
  # ... other settings

Multiple Servers

When multiple servers are specified in a connection's configuration, the system currently uses only the first server in the list. Load balancing across servers is not implemented.

SSH Authentication

  • Python implementation: Supports both ssh_tunnel.password and ssh_tunnel.private_key
  • CLI implementation: Supports key-based authentication and can use passwords when sshpass is installed
  • SSH agent / identity fallback: Omit both private_key and password to use agent-loaded identities and identity-related SSH configuration. The Python implementation lets paramiko discover keys via look_for_keys/allow_agent; the CLI implementation invokes system ssh without -i, so agent identities and matching identity options can be used. The configured ssh_tunnel.host, user, and port are still passed explicitly; full OpenSSH Host alias fallback for those fields is future work.
  • Timeout behavior: CLI SSH tunnel startup defaults to 30 seconds to allow system ssh interactive approval flows such as hardware tokens or short-lived certificate prompts. Python/Paramiko SSH tunnel startup keeps the 5 second default because it does not use the system ssh interactive prompt path. Set ssh_tunnel.ssh_timeout to a lower value when fail-fast behavior is preferred for unreachable bastions.
  • Host-key trust: SSH tunnel helpers currently trust newly seen bastion host keys automatically (StrictHostKeyChecking=no for CLI, Paramiko AutoAddPolicy for Python). Use these tunnels only on trusted networks until configurable host-key verification is added.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured