Multi-database MCP server for PostgreSQL, MySQL, and ClickHouse
SaferSkills independently audited db-connect-mcp (MCP Server) 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.
<!-- mcp-name: io.github.yugui923/db-connect-mcp -->
A read-only MCP (Model Context Protocol) server for exploratory data analysis across multiple database systems. This server provides safe, read-only access to PostgreSQL, MySQL, and ClickHouse databases with comprehensive analysis capabilities.
db-connect-mcp demo
pip install db-connect-mcpclaude_desktop_config.json: {
"mcpServers": {
"db-connect": {
"command": "python",
"args": ["-m", "db_connect_mcp"],
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb"
}
}
}
}Note: Usingpython -m db_connect_mcpensures the command works even if Python's Scripts directory isn't in your PATH. ExistingmcpServersconfigurations remain valid: stdio is still the default. The optional remote Streamable HTTP mode serves both older initialize-based clients and 2026-07-28 session-free MCP clients at/mcpwithout changing the database or SSH configuration. Existing HTTP deployments must review forwarded Host/Origin values: the upgrade enables transport validation and may requireMCP_ALLOWED_HOSTS/MCP_ALLOWED_ORIGINS.
EXPLAIN ANALYZE where supportedDB_HYPOPG_ENABLED=true on the server, and opt_in=true on each compare_hypothetical_index call. Provide a single simple SELECT, schema, table, and 1–4 column names; comments, CTEs, functions, literals, parameters, and multiple statements are rejected. The tool uses one read-only connection and EXPLAIN without ANALYZE, caps planning/lock waits at 5s/1s, and rolls back session-local changes. The response reports hypothetical_index_used by matching the HypoPG index name in the resulting plan; an unused candidate may be irrelevant or simply not chosen by the planner. The candidate requires schema USAGE and table SELECT privileges. These estimates are not measured speedups.db-connect-mcp inherits the MCP SDK's built-in OpenTelemetry server instrumentation. The API is a no-op until the launching process configures an SDK and exporter. Review exporter sampling and redaction before production use, because database identifiers and error details may be sensitive.
Tip: db-connect-mcp works best with databases that have proper comments on tables and columns. When your database includes descriptive comments, the MCP server can provide richer context to AI assistants, leading to better understanding of your data model and more accurate query suggestions.
Adding comments in PostgreSQL:
COMMENT ON TABLE users IS 'Registered user accounts with profile information';
COMMENT ON COLUMN users.email IS 'Primary email address, used for authentication';
COMMENT ON COLUMN users.is_verified IS 'Whether email has been verified via confirmation link';Adding comments in MySQL:
ALTER TABLE users COMMENT = 'Registered user accounts with profile information';
ALTER TABLE users MODIFY COLUMN email VARCHAR(255) COMMENT 'Primary email address, used for authentication';The server automatically retrieves and displays these comments when describing tables, helping AI assistants understand the purpose and semantics of your data.
SSHClient transport with target preflight and stable-port recoverySee the SSH Tunnel Guide for configuration details.
pip install db-connect-mcpThat's it! The package is now ready to use.
For developers: See Development Guide for setting up a development environment.
Create a .env file with your database connection string:
DATABASE_URL=your_database_connection_string_hereThe server automatically detects the database type and adds appropriate read-only parameters.
For PostgreSQL, set ENABLE_PG_STAT_STATEMENTS=true to advertise the optional get_top_queries tool. It reads bounded cumulative statistics for the current user/database if pg_stat_statements was installed and preloaded by the database administrator. It does not install extensions. Query text is omitted unless explicitly requested and can contain sensitive information. See the top-query report contract for bounds and statuses.
The server now provides more flexible and secure URL handling:
jdbc:postgresql://... → postgresql+asyncpg://...jdbc:mysql://... → mysql+aiomysql://...jdbc:postgres://, jdbc:mariadb://)postgresql, postgres, pg, psql, pgsqlmysql, mariadb, mariaclickhouse, ch, clickPostgreSQL:
# Simple URL (driver automatically added)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
# Common variations (all normalized to postgresql+asyncpg)
DATABASE_URL=postgres://user:pass@host:5432/db # Heroku, AWS RDS style
DATABASE_URL=pg://user:pass@host:5432/db # Short form
DATABASE_URL=psql://user:pass@host:5432/db # CLI style
# JDBC URLs (automatically converted)
DATABASE_URL=jdbc:postgresql://user:pass@host:5432/db # From Java apps
DATABASE_URL=jdbc:postgres://user:pass@host:5432/db # JDBC with variant
# With explicit async driver
DATABASE_URL=postgresql+asyncpg://user:pass@host:5432/db
# With supported parameters (see list below)
DATABASE_URL=postgres://user:pass@host:5432/db?application_name=myapp&connect_timeout=10Supported PostgreSQL Parameters:
application_name - Identifies your app in pg_stat_activity (useful for monitoring)connect_timeout - Connection timeout in secondscommand_timeout - Default timeout for operationsssl / sslmode - SSL connection requirements (automatically converted for asyncpg compatibility)server_settings - Server settings dictionaryoptions - Command-line options to send to serverprepared_statement_cache_size, max_cached_statement_lifetime, etc.MySQL/MariaDB:
# Simple URL (driver automatically added)
DATABASE_URL=mysql://root:password@localhost:3306/mydb
# MariaDB URLs (normalized to mysql+aiomysql)
DATABASE_URL=mariadb://user:pass@host:3306/db # MariaDB style
DATABASE_URL=maria://user:pass@host:3306/db # Short form
# JDBC URLs (automatically converted)
DATABASE_URL=jdbc:mysql://user:pass@host:3306/db # From Java apps
DATABASE_URL=jdbc:mariadb://user:pass@host:3306/db # JDBC MariaDB
# With explicit async driver
DATABASE_URL=mysql+aiomysql://user:pass@host:3306/db
# With charset (critical for proper Unicode support)
DATABASE_URL=mariadb://user:[email protected]:3306/db?charset=utf8mb4Supported MySQL Parameters:
charset - Character encoding (e.g., utf8mb4) - critical for data integrityuse_unicode - Enable Unicode supportconnect_timeout, read_timeout, write_timeout - Various timeoutsautocommit - Transaction autocommit modeinit_command - Initial SQL command to runsql_mode - SQL mode settingstime_zone - Time zone settingClickHouse:
# Simple URL (driver automatically added)
DATABASE_URL=clickhouse://default:@localhost:9000/default
# Short forms (normalized to clickhouse+asynch)
DATABASE_URL=ch://user:pass@host:9000/db # Short form
DATABASE_URL=click://user:pass@host:9000/db # Alternative
# JDBC URLs (automatically converted)
DATABASE_URL=jdbc:clickhouse://user:pass@host:9000/db # From Java apps
DATABASE_URL=jdbc:ch://user:pass@host:9000/db # JDBC with short form
# With explicit async driver
DATABASE_URL=clickhouse+asynch://user:pass@host:9000/db
# With performance settings
DATABASE_URL=ch://user:pass@host:9000/db?timeout=60&max_threads=4Supported ClickHouse Parameters:
database - Default database selectiontimeout, connect_timeout, send_receive_timeout - Various timeoutscompress, compression - Enable compressionmax_block_size, max_threads - Performance tuningNote:
ssl, sslmode) are automatically converted to the correct format for asyncpgsslcert, sslkey, sslrootcert) are filtered out as they can cause compatibility issues# Run the server (works everywhere, no PATH configuration needed)
python -m db_connect_mcp
# With environment variable
DATABASE_URL="postgresql://user:pass@host:5432/db" python -m db_connect_mcpNote: Using python -m db_connect_mcp works regardless of whether Python's Scripts directory is in your PATH.Add the MCP server to your project's .mcp.json:
claude mcp add --transport stdio db-connect --scope project \
--env DATABASE_URL=postgresql://user:pass@host:5432/db \
-- python -m db_connect_mcpOr manually create .mcp.json in your project root. Below are examples for each supported database:
PostgreSQL:
{
"mcpServers": {
"db-connect-mcp": {
"command": "python",
"args": ["-m", "db_connect_mcp"],
"env": {
"DATABASE_URL": "postgresql+asyncpg://user:pass@host:5432/mydb"
}
}
}
}MySQL:
{
"mcpServers": {
"db-connect-mcp": {
"command": "python",
"args": ["-m", "db_connect_mcp"],
"env": {
"DATABASE_URL": "mysql+aiomysql://user:pass@host:3306/mydb"
}
}
}
}ClickHouse:
{
"mcpServers": {
"db-connect-mcp": {
"command": "python",
"args": ["-m", "db_connect_mcp"],
"env": {
"DATABASE_URL": "clickhouse+asynch://default:@host:9000/default"
}
}
}
}PostgreSQL via SSH tunnel (database behind a firewall, reachable only through a bastion host):
{
"mcpServers": {
"db-connect-mcp": {
"command": "python",
"args": ["-m", "db_connect_mcp"],
"env": {
"DATABASE_URL": "postgresql+asyncpg://user:pass@db-internal:5432/mydb",
"SSH_HOST": "bastion.example.com",
"SSH_PORT": "22",
"SSH_USERNAME": "deployer",
"SSH_PRIVATE_KEY_PATH": "/home/user/.ssh/id_rsa"
}
}
}
}MySQL via SSH tunnel:
{
"mcpServers": {
"db-connect-mcp": {
"command": "python",
"args": ["-m", "db_connect_mcp"],
"env": {
"DATABASE_URL": "mysql+aiomysql://user:pass@db-internal:3306/mydb",
"SSH_HOST": "bastion.example.com",
"SSH_PORT": "22",
"SSH_USERNAME": "deployer",
"SSH_PASSWORD": "secret"
}
}
}
}Multiple databases (each MCP server instance connects to one database):
{
"mcpServers": {
"postgres-prod": {
"command": "python",
"args": ["-m", "db_connect_mcp"],
"env": {
"DATABASE_URL": "postgresql+asyncpg://user:pass@pg-host:5432/prod"
}
},
"mysql-analytics": {
"command": "python",
"args": ["-m", "db_connect_mcp"],
"env": {
"DATABASE_URL": "mysql+aiomysql://user:pass@mysql-host:3306/analytics"
}
}
}
}After creating .mcp.json, restart Claude Code and verify with /mcp. You should see db-connect-mcp listed with all available tools.
Tip: Instead ofSSH_PRIVATE_KEY_PATH, you can useSSH_PRIVATE_KEYto pass the private key content directly as a string (raw PEM or base64-encoded PEM). This is useful in CI/CD or cloud environments where mounting key files is impractical.
See the SSH Tunnel Guide for full tunnel configuration reference.
Add the server to your Claude Desktop configuration (claude_desktop_config.json):
{
"mcpServers": {
"db-connect": {
"command": "python",
"args": ["-m", "db_connect_mcp"],
"env": {
"DATABASE_URL": "postgresql+asyncpg://user:pass@host:5432/db"
}
}
}
}The same database URL formats and SSH tunnel environment variables shown in the Claude Code examples above work identically with Claude Desktop.
For development: See Development Guide for running from source with uv.
| Feature | PostgreSQL | MySQL | ClickHouse |
|---|---|---|---|
| Schemas | ✅ Full | ✅ Full | ✅ Full |
| Tables | ✅ Full | ✅ Full | ✅ Full |
| Views | ✅ Full | ✅ Full | ✅ Full |
| Indexes | ✅ Full | ✅ Full | ⚠️ Limited |
| Foreign Keys | ✅ Full | ✅ Full | ❌ No |
| Constraints | ✅ Full | ✅ Full | ⚠️ Limited |
| Table Size | ✅ Exact | ✅ Exact | ✅ Exact |
| Row Count | ✅ Exact | ✅ Exact | ✅ Exact |
| Column Stats | ✅ Full | ✅ Full | ✅ Full |
| Sampling | ✅ Full | ✅ Full | ✅ Full |
Modern MCP clients can discover database context as private, cache-aware JSON resources in addition to calling tools:
db-connect://database — database identity, dialect, and capabilitiesdb-connect://schema/{schema} — schema counts and metadatadb-connect://table/{schema}/{table} — columns, indexes, constraints, and commentsThe schema and table forms are also advertised as resource templates for direct access when the identifier is already known.
Resource catalogs are URI-sorted and cursor-paginated in pages of 100. Cursors are tied to a catalog snapshot; if schemas or tables change between pages, the server asks the client to restart pagination instead of returning an inconsistent traversal.
All tools publish JSON Schema input and output contracts, read-only behavior annotations, and machine-readable structured results. The same result remains available as JSON text for clients that do not yet consume MCP structured content. Structured list results use an items envelope while their legacy text form remains a JSON array.
Get database metadata, including the dialect, version, connection details, read-only status, and capabilities.
List all schemas in the database.
List all tables in a schema with metadata.
schema (optional): Schema name (default: "public")Get detailed information about a table.
table: Name of the tableschema (optional): Schema name (default: "public")Analyze a column with statistics and distribution.
table: Name of the tablecolumn: Name of the columnschema (optional): Schema name (default: "public")Get a sample of data from a table.
table: Name of the tableschema (optional): Schema name (default: "public")limit (optional): Number of rows (default: 100, max: 1000)Execute a read-only SQL query.
query: SQL query (must be SELECT or WITH)limit (optional): Maximum rows (default: 1000, max: 10000)Get foreign key relationships for a table.
table: Name of the tableschema (optional): Schema name (default: "public")Get a database-specific query execution plan.
query: SQL query to explainanalyze (optional): Execute the query and include actual runtime statistics (default: false)Search schemas, tables, views, columns, and indexes with progressive detail.
pattern: SQL LIKE pattern, such as %user%object_types (optional): Object types to includedetail_level (optional): names, summary, or full (default: summary)schema (optional): Restrict the search to a schematable (optional): Restrict column and index searches to a tablelimit (optional): Maximum matches (default: 100, max: 1000)Once configured, you can use the server with any compatible MCP client:
"Can you analyze my database and tell me about the table structure?"
"Show me the relationships between tables in the public schema"
"What's the distribution of values in the users.created_at column?"
"Give me a sample of data from the orders table"
"Run this query: SELECT COUNT(*) FROM users WHERE created_at > '2024-01-01'"Working with PostgreSQL:
"List all schemas except system ones"
"Show me the foreign key relationships in the sales schema"
"Analyze the performance of indexes on the products table"Working with MySQL:
"What storage engines are being used in my database?"
"Show me all tables in the information_schema"
"Analyze the customer_orders table structure"Working with ClickHouse:
"Show me the partitions for the events table"
"What's the compression ratio for the analytics.clicks table?"
"Sample 1000 rows from the metrics table"execute_query. Use a least-privilege database role on every engine, especially ClickHouse, where SELECT may invoke external table functions.execute_query bounds returned rows and serialized response size, not total database work or peak process memory. ClickHouse's synchronous driver may buffer a full result before the application checks it. Use database-side execution, memory, and access limits. See health and query safety notes.For detailed development setup, testing, and contribution guidelines, see the Development Guide.
db-connect-mcp/
├── src/
│ └── db_connect_mcp/
│ ├── adapters/ # Database-specific adapters
│ │ ├── __init__.py
│ │ ├── base.py # Base adapter interface
│ │ ├── postgresql.py # PostgreSQL adapter
│ │ ├── mysql.py # MySQL adapter
│ │ └── clickhouse.py # ClickHouse adapter
│ ├── core/ # Core functionality
│ │ ├── __init__.py
│ │ ├── connection.py # Database connection management
│ │ ├── executor.py # Query execution
│ │ ├── inspector.py # Metadata inspection
│ │ ├── analyzer.py # Statistical analysis
│ │ └── tunnel.py # SSH tunnel management
│ ├── models/ # Data models
│ │ ├── __init__.py
│ │ ├── capabilities.py # Database capabilities
│ │ ├── config.py # Configuration models
│ │ ├── database.py # Database models
│ │ ├── query.py # Query models
│ │ ├── statistics.py # Statistics models
│ │ └── table.py # Table metadata models
│ ├── __init__.py
│ ├── __main__.py # Module entry point
│ └── server.py # Main MCP server implementation
├── tests/
│ ├── unit/ # Unit tests (mocked)
│ ├── module/ # Module tests (single component + DB)
│ ├── integration/ # Integration tests (full stack)
│ └── conftest.py # Shared fixtures
├── .env.example # Example environment configuration
├── pyproject.toml # Project dependencies and console scripts
└── README.md # This fileThe server uses an adapter pattern to support multiple database systems:
# Start local test database (PostgreSQL 17 with sample data)
cd tests/docker && docker-compose up -d && cd ../..
# Run all tests in parallel (preferred - 6 workers)
uv run pytest -n 6
# Run specific test modules
uv run pytest tests/module/test_inspector.py -v -n 6
uv run pytest tests/integration/ -v -n 6
# Stop test database
cd tests/docker && docker-compose down && cd ../..
# Reset database (clean slate with fresh data)
cd tests/docker && docker-compose down -v && docker-compose up -d && cd ../..Local Test Database:
See the Development Guide and Testing Guide for detailed testing instructions.
?ssl=require)?charset=utf8mb4)PostgreSQL:
asyncpg driver is specified for async operationsMySQL/MariaDB:
aiomysql driver for async supportClickHouse:
asynch driver for async operationslimit parameter to control result sizeCreated by Yuri Gui.
Contributions are welcome! The server is designed to be read-only and safe by default. Any new features should maintain these safety guarantees.
MIT License - See LICENSE file for details
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.