mcp-read-only-sql
Allows read-only SQL queries on ClickHouse databases with enforced read-only mode, timeout protection, and support for both Python and CLI implementations.
Imports existing database connections from a DBeaver workspace into the MCP server's configuration.
Allows read-only SQL queries on PostgreSQL databases with enforced read-only mode, timeout protection, and support for both Python and CLI implementations.
Click on "Install 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., "@mcp-read-only-sqlshow me the first 10 rows from the orders table"
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.
MCP Read-Only SQL Server
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.yamlCredentials: stored in
connections.yamlState:
~/.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:
Database-level read-only - Sessions forced to read-only mode
Timeout protection - Connection and query timeouts are configurable per connection
Managed result files - Successful query results are written to
state_dir/resultswith0600permissions
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 configurablestatement_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 allpsqlinvocations include--single-transaction,-v ON_ERROR_STOP=1, andPGOPTIONS=-c default_transaction_read_only=onfor defence in depth.ClickHouse (Python) – The driver sets
readonly=1plus connection/query timeouts, forcing the server to reject any write or DDL attempt.ClickHouse (CLI) –
clickhouse-clientis 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.
Related MCP server: PostgreSQL MCP Server
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,
sshpassin CLI), and falling back to agent-loaded identities when no credentials are providedSecurity built-in - Timeouts, managed result files, session controls
DBeaver import - Import existing connections easily
Prerequisites
uv for package installs and ephemeral
uvxrunspsqlif you want PostgreSQL connections withimplementation: cliclickhouse-clientif you want ClickHouse connections withimplementation: clisshpassonly if you want CLI-based SSH tunnels with password authenticationjust 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: cliuses the official database client binaries you already trust in operations.implementation: pythonstays 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 -VQuick Start
1. Install or Run the Server
For the published package, prefer @latest with uvx:
uvx mcp-read-only-sql@latest --write-sample-configOr install it once and reuse the command directly:
uv tool install mcp-read-only-sql
mcp-read-only-sql --write-sample-configWhen 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-configFor a persistent local install from this checkout:
uv tool install .
mcp-read-only-sql --write-sample-configFor 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_meUse 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/.dbeaverThat 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.yamlcontains 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_meIf 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-pathsIf 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-paths5. Add the MCP Server to Your Client
For Claude Code:
claude mcp add mcp-read-only-sql -- uvx mcp-read-only-sql@latestFor Codex:
codex mcp add mcp-read-only-sql -- uvx mcp-read-only-sql@latestFor manual testing with a different config root:
uvx mcp-read-only-sql@latest --config-dir /path/to/config-dir --print-pathsMCP 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_connectionsquery(required): SQL text that must remain read-onlydatabase(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 |
|
|
|
|
SSH Key Auth | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
SSH Password Auth | ✅ Yes (requires | ✅ Yes (Paramiko) | ✅ Yes (requires | ✅ Yes (Paramiko) |
Timeout Control | ✅ Via SQL | ✅ Driver-level | ✅ CLI flags | ✅ Driver-level |
Result Streaming | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
Binary Required |
| None |
| 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
sshpassYou 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: pythonsince HAProxy usually proxies HTTP trafficIf 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 settingsMultiple 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.passwordandssh_tunnel.private_keyCLI implementation: Supports key-based authentication and can use passwords when
sshpassis installedSSH agent / identity fallback: Omit both
private_keyandpasswordto use agent-loaded identities and identity-related SSH configuration. The Python implementation lets paramiko discover keys vialook_for_keys/allow_agent; the CLI implementation invokes systemsshwithout-i, so agent identities and matching identity options can be used. The configuredssh_tunnel.host,user, andportare still passed explicitly; full OpenSSHHostalias fallback for those fields is future work.Timeout behavior: CLI SSH tunnel startup defaults to 30 seconds to allow system
sshinteractive 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 systemsshinteractive prompt path. Setssh_tunnel.ssh_timeoutto 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=nofor CLI, ParamikoAutoAddPolicyfor Python). Use these tunnels only on trusted networks until configurable host-key verification is added.
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
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/lukleh/mcp-read-only-sql'
If you have feedback or need assistance with the MCP directory API, please join our Discord server