
[](https://github.com/askdba/mysql-mcp-server/releases)
[](https://golang.org/)
[](LICENSE)
A fast, read-only MySQL Server for the Model Context Protocol (MCP) written in Go.
This project exposes safe MySQL introspection tools to Claude Desktop via MCP. Claude can explore databases, describe schemas, and execute controlled read-only SQL queries — ideal for secure development assistance, debugging, analytics, and schema documentation.
## Features
- Fully read-only (blocks all non-SELECT/SHOW/DESCRIBE/EXPLAIN)
- **Multi-DSN Support**: Connect to multiple MySQL or MariaDB instances, switch via tool
- **MariaDB Support**: Native compatibility with MariaDB 10.x and 11.x
- **Vector Search** (MySQL 9.0+): Similarity search on vector columns
- MCP tools:
- list_databases, list_tables, describe_table
- run_query (safe and row-limited)
- ping, server_info
- list_connections, use_connection (multi-DSN)
- vector_search, vector_info (MySQL 9.0+)
- Supports MySQL 8.0, 8.4, 9.0+ and MariaDB 10.x, 11.x
- Query timeouts, structured logging, audit logs; optional **live token metrics** and **`/status`** dashboard in HTTP mode
- **Performance**: configurable pool/query timeouts, server-side row caps, `explain_query` plan warnings
- Single Go binary
- Unit and integration tests (Testcontainers)
- Native integration with Claude Desktop MCP
## Installation
### Homebrew (macOS/Linux)
```bash
brew install askdba/tap/mysql-mcp-server
```
**Update local installation** (after a new release):
```bash
brew update && brew upgrade mysql-mcp-server
```
**Apple Silicon — “Cannot install under Rosetta 2 in ARM default prefix (/opt/homebrew)”**
Your shell is running **Homebrew under Rosetta (Intel)** while **`/opt/homebrew`** is **ARM**. They must match. Use one of:
- Run upgrades as ARM: **`arch -arm64 brew upgrade mysql-mcp-server`** (or **`arch -arm64 /opt/homebrew/bin/brew upgrade mysql-mcp-server`**).
- **Terminal.app → Get Info** → uncheck **Open using Rosetta**, then open a new terminal.
- Put **`/opt/homebrew/bin`** first in **`PATH`** so you use the ARM `brew` and binary.
**Claude Desktop:** on Apple Silicon, point **`command`** at **`/opt/homebrew/bin/mysql-mcp-server`**. **`/usr/local/opt/mysql-mcp-server/...`** is normally **Intel** Homebrew and can stay on an old version.
### Docker
```bash
docker pull ghcr.io/askdba/mysql-mcp-server:latest
```
> Note: Docker image tags use the raw version number without a leading "v"
> (e.g., `1.5.0`, not `v1.5.0`).
### Download Binary
Download the latest release from [GitHub Releases](https://github.com/askdba/mysql-mcp-server/releases).
Available for:
- macOS (Intel & Apple Silicon)
- Linux (amd64 & arm64)
- Windows (amd64)
### Build from Source
```bash
git clone https://github.com/askdba/mysql-mcp-server.git
cd mysql-mcp-server
make build
```
Binary output: `bin/mysql-mcp-server`
## Quickstart
### Option A: Homebrew (macOS/Linux)
```bash
brew install askdba/tap/mysql-mcp-server
mysql-mcp-server --version
```
Then follow the client-specific setup in the [Claude Desktop](#claude-desktop-integration), [Claude Code](#claude-code-integration), or [Cursor IDE](#cursor-ide-integration) sections below.
> **Tip:** `brew info askdba/tap/mysql-mcp-server` shows a config reminder after install.
### Option B: Build from Source
```bash
git clone https://github.com/askdba/mysql-mcp-server.git
cd mysql-mcp-server
make build
```
When running from a cloned repo you can also use the interactive setup script:
```bash
./scripts/quickstart.sh
```
This will test your MySQL connection, optionally create a read-only MCP user, and generate your Claude Desktop configuration.
## Configuration
Environment variables:
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| MYSQL_DSN | Yes | – | MySQL DSN |
| MYSQL_MAX_ROWS | No | 200 | Max rows returned per query |
| MYSQL_QUERY_TIMEOUT_SECONDS | No | 30 | Query timeout (seconds); wins over `MYSQL_QUERY_TIMEOUT` when both are set |
| MYSQL_QUERY_TIMEOUT | No | – | Query timeout in **milliseconds** (e.g. `30000`); used only if `MYSQL_QUERY_TIMEOUT_SECONDS` is unset |
| MYSQL_POOL_SIZE | No | – | Alias for `MYSQL_MAX_OPEN_CONNS` (pool size); `MYSQL_MAX_OPEN_CONNS` overrides when both are set |
| MYSQL_MCP_EXTENDED | No | 0 | Enable extended tools (set to 1) |
| MYSQL_MCP_ENABLE_ADD_CONNECTION | No | 0 | Set `1` to expose the MCP tool **`add_connection`** (runtime DSN registration; see [add_connection](#add_connection)). Requires **`MYSQL_MCP_EXTENDED=1`**. Not available over the HTTP REST API. |
| MYSQL_MCP_JSON_LOGS | No | 0 | Enable JSON structured logging (set to 1) |
| MYSQL_MCP_TOKEN_TRACKING | No | 0 | Enable estimated token usage tracking (set to 1) |
| MYSQL_MCP_TOKEN_MODEL | No | cl100k_base | Tokenizer encoding to use for estimation |
| MYSQL_MCP_TOKEN_CARD | No | **on** when `MYSQL_MCP_HTTP` is set | **`/status`** live token dashboard + listing in **`GET /api`**; omit to use default **on**; set to **0** to disable |
| MYSQL_MCP_AUDIT_LOG | No | – | Path to audit log file |
| MYSQL_MCP_ALLOWED_DATABASES | No | – | Comma-separated schema allowlist (empty = all allowed). With an allowlist, **`run_query`** rejects **`SHOW DATABASES`** / **`SHOW DATABASES LIKE`**—use **`list_databases`**. |
| MYSQL_MCP_STRICT_READ_ONLY | No | 0 | Set `1` to enable `transaction_read_only=ON` on new connections |
| MYSQL_MCP_ALLOW_SYSTEM_SCHEMAS | No | 0 | Set `1` to allow **read-only** access to `information_schema`, `performance_schema`, and `sys` (for diagnostics: index cardinality, index usage, redundant/unused indexes). `mysql` stays blocked regardless. |
| MYSQL_MCP_PROCESS_ADMIN | No | 0 | Set `1` to enable **`process_list`** / **`kill_query`** (extended); **`kill_query`** issues **`KILL QUERY`** (cancels the running statement only, not the connection) |
| MYSQL_MCP_READ_AUDIT_TOOL | No | 0 | Set `1` to enable `read_audit_log` when audit path is set |
| MYSQL_MCP_SLOW_QUERY_TOOL | No | 0 | Set `1` to enable `slow_query_log` tool (extended) |
| MYSQL_MCP_VECTOR | No | 0 | Enable vector tools for MySQL 9.0+ (set to 1) |
| MYSQL_MCP_HTTP | No | 0 | Enable REST API mode (set to 1); **mutually exclusive** with stdio MCP |
| MYSQL_MCP_METRICS_HTTP | No | 0 | With **stdio MCP only**: expose **`/status`** + **`/api/metrics/tokens`** on **`MYSQL_HTTP_PORT`** (same process as Claude/Cursor) |
| MYSQL_HTTP_PORT | No | 9306 | Port for REST API **or** metrics sidecar |
| MYSQL_HTTP_RATE_LIMIT | No | 0 | Enable rate limiting for HTTP mode (set to 1) |
| MYSQL_HTTP_RATE_LIMIT_RPS | No | 100 | Rate limit: requests per second |
| MYSQL_HTTP_RATE_LIMIT_BURST | No | 200 | Rate limit: burst size |
| MYSQL_MAX_OPEN_CONNS | No | 10 | Max open database connections (overrides `MYSQL_POOL_SIZE` when both are set) |
| MYSQL_MAX_IDLE_CONNS | No | 5 | Max idle database connections |
| MYSQL_CONN_MAX_LIFETIME_MINUTES | No | 30 | Connection max lifetime in minutes |
| MYSQL_CONN_MAX_IDLE_TIME_MINUTES | No | 5 | Max idle time before connection is closed |
| MYSQL_PING_TIMEOUT_SECONDS | No | 5 | Database ping/health check timeout |
| MYSQL_MCP_DB_RETRY_MAX | No | 3 | Retries for transient errors on **`run_query`** and **`ping`** (0 disables retries) |
| MYSQL_MCP_DB_RETRY_MAX_INTERVAL_MS | No | 10000 | Max exponential-backoff interval between retries (milliseconds) |
| MYSQL_HTTP_REQUEST_TIMEOUT_SECONDS | No | 60 | HTTP request timeout in REST API mode |
| MYSQL_SSL | No | – | Enable SSL/TLS for connections (true, false, skip-verify, preferred) |
### SSL/TLS Configuration
Enable encrypted connections to MySQL servers:
| SSL Value | Description |
|-----------|-------------|
| `true` | Enable TLS with certificate verification |
| `false` | Disable TLS (default) |
| `skip-verify` | Enable TLS without certificate verification (self-signed certs) |
| `preferred` | Maps to `skip-verify` (Go MySQL driver limitation) |
> **Note:** The Go MySQL driver doesn't support `tls=preferred`. When you specify `preferred`, it is automatically mapped to `skip-verify` to ensure TLS is enabled.
**Environment variable:**
```bash
# Enable SSL for all connections
export MYSQL_SSL="true"
# Or per-connection SSL
export MYSQL_DSN_1_SSL="skip-verify"
```
**Config file:**
```yaml
connections:
production:
dsn: "user:pass@tcp(prod:3306)/db?parseTime=true"
ssl: "true"
```
### SSH Tunneling (Bastion Host)
> **Security — host keys (read this):** By default the server **verifies the SSH bastion’s host key** (same idea as OpenSSH). Without that, a network attacker could present a fake bastion and intercept database traffic. Use a **`known_hosts`** file (default: **`~/.ssh/known_hosts`**) or pin the key with **`MYSQL_SSH_HOST_KEY_FINGERPRINT`**. Paths support **`~`**. Turning verification off is **opt-in only** and **dangerous**: set **`MYSQL_SSH_STRICT_HOST_KEY_CHECKING=false`** or YAML **`strict_host_key_checking: false`** (JSON: **`ssh_strict_host_key_checking: false`**) only if you accept that risk.
Connect to MySQL through an SSH bastion when the database is not directly reachable:
| Variable | Description |
|----------|-------------|
| `MYSQL_SSH_HOST` | Bastion hostname |
| `MYSQL_SSH_USER` | SSH username |
| `MYSQL_SSH_KEY_PATH` | Path to private key file (`~` expanded) |
| `MYSQL_SSH_PORT` | SSH port (default 22) |
| `MYSQL_SSH_KNOWN_HOSTS` | Path to OpenSSH `known_hosts` file (optional; default `~/.ssh/known_hosts` when verifying) |
| `MYSQL_SSH_HOST_KEY_FINGERPRINT` | Pin server key, e.g. `SHA256:...` or legacy `MD5:...` / `aa:bb:...` (optional; overrides file lookup for the callback) |
| `MYSQL_SSH_STRICT_HOST_KEY_CHECKING` | Default strict. Set to `false` / `0` / `no` / `off` to **disable** host key verification (**insecure**) |
**Environment variables:**
```bash
export MYSQL_SSH_HOST="bastion.example.com"
export MYSQL_SSH_USER="deploy"
export MYSQL_SSH_KEY_PATH="$HOME/.ssh/id_rsa"
export MYSQL_SSH_KNOWN_HOSTS="$HOME/.ssh/known_hosts"
# Optional: instead of a file, pin the bastion key (from ssh-keygen -lf):
# export MYSQL_SSH_HOST_KEY_FINGERPRINT="SHA256:...."
export MYSQL_DSN="user:pass@tcp(mysql.internal:3306)/mydb?parseTime=true"
```
**Config file:**
```yaml
connections:
production:
dsn: "user:pass@tcp(mysql.internal:3306)/mydb?parseTime=true"
ssh:
host: "bastion.example.com"
user: "deploy"
key_path: "~/.ssh/id_rsa"
port: 22 # optional, default 22
strict_host_key_checking: true # default when omitted; false = insecure, opt-in only
known_hosts: "~/.ssh/known_hosts" # optional
host_key_fingerprint: "SHA256:...." # optional alternative to known_hosts
```
### Multi-DSN Configuration
Configure multiple MySQL connections using numbered environment variables:
```bash
# Default connection
export MYSQL_DSN="user:pass@tcp(localhost:3306)/db1?parseTime=true"
# Additional connections
export MYSQL_DSN_1="user:pass@tcp(prod-server:3306)/production?parseTime=true"
export MYSQL_DSN_1_NAME="production"
export MYSQL_DSN_1_DESC="Production database"
export MYSQL_DSN_1_SSL="true" # Enable SSL for this connection
export MYSQL_DSN_2="user:pass@tcp(staging:3306)/staging?parseTime=true"
export MYSQL_DSN_2_NAME="staging"
export MYSQL_DSN_2_DESC="Staging database"
```
Or use JSON configuration:
```bash
export MYSQL_CONNECTIONS='[
{"name": "production", "dsn": "user:pass@tcp(prod:3306)/db?parseTime=true", "description": "Production", "ssl": "true"},
{"name": "staging", "dsn": "user:pass@tcp(staging:3306)/db?parseTime=true", "description": "Staging"}
]'
```
**Runtime vs static DSNs:** Connections defined here (env vars, `MYSQL_CONNECTIONS`, or YAML) are loaded at process startup into the shared `ConnectionManager`. The optional MCP tool **`add_connection`** (requires **`MYSQL_MCP_EXTENDED=1`** and **`MYSQL_MCP_ENABLE_ADD_CONNECTION=1`**) can register **additional** named DSNs later without restart. New names must not collide with existing connection names. Runtime-registered pools use the same global “active” connection as config-loaded ones—see [add_connection](#add_connection). There is no separate per-client session; all MCP clients using the same server process share one active DSN unless you run multiple server instances.
### Configuration File
As an alternative to environment variables, you can use a YAML or JSON configuration file.
**Config file search order:**
1. `--config /path/to/config.yaml` (command line flag)
2. `MYSQL_MCP_CONFIG` environment variable
3. `./mysql-mcp-server.yaml` (current directory)
4. `~/.config/mysql-mcp-server/config.yaml` (user config)
5. `/etc/mysql-mcp-server/config.yaml` (system config)
**Example config file (`mysql-mcp-server.yaml`):**
```yaml
# Database connections
connections:
default:
dsn: "user:pass@tcp(localhost:3306)/mydb?parseTime=true"
description: "Local development database"
production:
dsn: "readonly:pass@tcp(prod:3306)/prod?parseTime=true"
description: "Production (read-only)"
ssl: "true" # Enable TLS with certificate verification
# Query settings
query:
max_rows: 200
timeout_seconds: 30
# Connection pool
pool:
max_open_conns: 10
max_idle_conns: 5
conn_max_lifetime_minutes: 30
# Features
features:
extended_tools: true
vector_tools: false
# HTTP/REST API (optional)
http:
enabled: false
port: 9306
```
**Command line options:**
```bash
# Use specific config file
mysql-mcp-server --config /path/to/config.yaml
# Validate config file
mysql-mcp-server --validate-config /path/to/config.yaml
# Print current configuration as YAML
mysql-mcp-server --print-config
# Silent mode: suppress INFO/WARN logs (only errors to stderr)
mysql-mcp-server --silent --config /path/to/config.yaml
# Daemon mode: run HTTP server in background (Unix; use with MYSQL_MCP_HTTP=1)
MYSQL_MCP_HTTP=1 mysql-mcp-server --daemon --config /path/to/config.yaml
```
**Silent and daemon mode:** Use `-s` / `--silent` to reduce log noise in production (INFO and WARN are suppressed; ERROR still goes to stderr). Use `-d` / `--daemon` to run the HTTP server detached in the background on Unix. For long-running services, use the example [systemd unit](contrib/systemd/mysql-mcp-server.service) or [launchd plist](contrib/launchd/com.askdba.mysql-mcp-server.plist). See [Silent and daemon mode](docs/silent-and-daemon.md) for details.
**Priority:** Environment variables override config file values, allowing:
- Base configuration in file
- Environment-specific overrides via env vars
- Docker/K8s secret injection via env vars
See [`examples/config.yaml`](examples/config.yaml) and [`examples/config.json`](examples/config.json) for complete examples.
Example:
```bash
export MYSQL_DSN="root:password@tcp(127.0.0.1:3306)/mysql?parseTime=true"
export MYSQL_MAX_ROWS=200
export MYSQL_QUERY_TIMEOUT_SECONDS=30
```
Run:
```bash
make run
```
### Version Information
Check the installed version:
```bash
mysql-mcp-server --version
```
Output:
```
mysql-mcp-server v1.7.1
Build time: