Skip to main content
Glama
yogaaltariz

mysql_mcp

by yogaaltariz

MySQL MCP Server

A production-grade MCP server that connects Claude to multiple MySQL Docker containers on a VPS. All connections are defined in a single JSON file. Each connection can optionally manage its own SSH tunnel — no autossh, no systemd services, no external tooling required.


How it works

Claude Desktop
     │
     └── mysql_mcp  (one process)
              ├── "app"       → SSH tunnel → VPS:3306 (app_db)       writes ON
              ├── "analytics" → SSH tunnel → VPS:3307 (analytics_db) read-only
              └── "legacy"    → SSH tunnel → VPS:3308 (legacy_db)    read-only

On startup the server:

  1. Reads connections.json (or another configured path — see below)

  2. Spawns one SSH tunnel per connection that has ssh_host

  3. Blocks until each tunnel's local port accepts TCP connections

  4. Opens a MySQL connection pool through each tunnel

  5. Starts a background watcher per tunnel that auto-reconnects if SSH dies


Related MCP server: Claude MCP Server Ecosystem

Security features

Feature

Details

SQL injection defence

Blocks SLEEP, BENCHMARK, LOAD_FILE, INTO OUTFILE, block comments, stacked dangerous statements

Forbidden statements

DROP, TRUNCATE, GRANT, REVOKE, FLUSH, SHUTDOWN — always blocked

Write guard per connection

allow_writes: false by default — opt-in per connection

Schema allowlist

allowed_dbs restricts accessible schemas per connection

TLS per connection

Configure TLS independently per container

Rate limiting

Per-connection sliding 60s window (default 120 req/min)

Query timeout

Cancels runaway queries server-side (default 30s)

Row cap

Hard ceiling on rows returned per query (default 500)

Identifier sanitisation

DB/table names validated against [a-zA-Z0-9_-] only

No secrets in code

All credentials live in connections.json, never in source

Config validation

Missing/invalid fields caught at startup with clear error messages


SSH tunnel manager

The built-in tunnel manager replaces autossh. It:

  • Spawns ssh -N -L ... as a subprocess with ExitOnForwardFailure=yes and ServerAliveInterval

  • Blocks startup until the local port actually accepts a TCP connection (15s timeout)

  • Runs a background watcher task per tunnel

  • Reconnects with exponential backoff when SSH dies — delay doubles per failure, capped at ssh_max_delay

  • Resets the MySQL connection pool after a successful reconnect so stale connections don't linger

  • Returns a clear "tunnel is reconnecting — please try again shortly" error during reconnects

  • Terminates cleanly on shutdown (SIGTERM → 3s grace → SIGKILL)


Quick start

1. Docker Compose on your VPS

Bind each container to a different port on 127.0.0.1 — never 0.0.0.0:

# docker-compose.yml
services:
  mysql-app:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${APP_ROOT_PW}
      MYSQL_DATABASE: app_db
    ports:
      - "127.0.0.1:3306:3306"
    volumes:
      - app_data:/var/lib/mysql

  mysql-analytics:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${ANALYTICS_ROOT_PW}
      MYSQL_DATABASE: analytics_db
    ports:
      - "127.0.0.1:3307:3306"
    volumes:
      - analytics_data:/var/lib/mysql

  mysql-legacy:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${LEGACY_ROOT_PW}
    ports:
      - "127.0.0.1:3308:3306"
    volumes:
      - legacy_data:/var/lib/mysql

volumes:
  app_data:
  analytics_data:
  legacy_data:

2. Create least-privilege MySQL users

-- mysql-app (writes allowed)
CREATE USER 'mcp_app'@'%' IDENTIFIED BY 'strong_password_1';
GRANT SELECT, INSERT, UPDATE, DELETE, SHOW DATABASES, SHOW VIEW
  ON app_db.* TO 'mcp_app'@'%';

-- mysql-analytics (read-only)
CREATE USER 'mcp_analytics'@'%' IDENTIFIED BY 'strong_password_2';
GRANT SELECT, SHOW DATABASES, SHOW VIEW ON analytics_db.* TO 'mcp_analytics'@'%';

-- mysql-legacy (read-only, two schemas)
CREATE USER 'mcp_legacy'@'%' IDENTIFIED BY 'strong_password_3';
GRANT SELECT, SHOW DATABASES, SHOW VIEW ON legacy_db.*  TO 'mcp_legacy'@'%';
GRANT SELECT, SHOW DATABASES, SHOW VIEW ON archive_db.* TO 'mcp_legacy'@'%';

FLUSH PRIVILEGES;

3. Set up SSH key auth on the VPS

# Generate a dedicated key for the MCP server (no passphrase — it runs unattended)
ssh-keygen -t ed25519 -f ~/.ssh/mcp_vps -C "mysql-mcp" -N ""

# Copy the public key to the VPS
ssh-copy-id -i ~/.ssh/mcp_vps.pub ubuntu@your-vps.example.com

# Verify it works without a password prompt
ssh -i ~/.ssh/mcp_vps -o BatchMode=yes ubuntu@your-vps.example.com echo ok

4. Install the MCP server locally

cd mysql-mcp
python -m venv .venv
source .venv/bin/activate       # Windows: .venv\Scripts\activate
pip install -r requirements.txt

5. Create your connections file

cp connections.example.json connections.json
$EDITOR connections.json        # fill in your VPS hostname, passwords, key path

The file is plain JSON with // and # line comments supported:

// connections.json
[
  {
    "name":            "app",
    "label":           "App Database",
    "host":            "127.0.0.1",
    "port":            13306,
    "user":            "mcp_app",
    "password":        "strong_password_1",
    "database":        "app_db",
    "allow_writes":    true,
    "allowed_dbs":     ["app_db"],

    // SSH tunnel — managed automatically by the MCP server
    "ssh_host":        "your-vps.example.com",
    "ssh_user":        "ubuntu",
    "ssh_key":         "/home/you/.ssh/mcp_vps",
    "ssh_remote_port": 3306,
    "ssh_local_port":  13306
  },
  {
    "name":            "analytics",
    "label":           "Analytics Database",
    "host":            "127.0.0.1",
    "port":            13307,
    "user":            "mcp_analytics",
    "password":        "strong_password_2",
    "database":        "analytics_db",
    "allowed_dbs":     ["analytics_db"],
    "max_rows":        1000,

    "ssh_host":        "your-vps.example.com",
    "ssh_user":        "ubuntu",
    "ssh_key":         "/home/you/.ssh/mcp_vps",
    "ssh_remote_port": 3307,
    "ssh_local_port":  13307
  },
  {
    "name":            "legacy",
    "label":           "Legacy Database",
    "host":            "127.0.0.1",
    "port":            13308,
    "user":            "mcp_legacy",
    "password":        "strong_password_3",
    "allowed_dbs":     ["legacy_db", "archive_db"],

    "ssh_host":        "your-vps.example.com",
    "ssh_user":        "ubuntu",
    "ssh_key":         "/home/you/.ssh/mcp_vps",
    "ssh_remote_port": 3308,
    "ssh_local_port":  13308
  }
]

6. Configure Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) %APPDATA%\Claude\claude_desktop_config.json (Windows)

{
  "mcpServers": {
    "mysql": {
      "command": "/absolute/path/to/mysql-mcp/.venv/bin/python",
      "args":    ["/absolute/path/to/mysql-mcp/server.py"],
      "env": {
        "MYSQL_CONFIG": "/absolute/path/to/mysql-mcp/connections.json"
      }
    }
  }
}

Restart Claude Desktop after saving.


Where the config file is loaded from

The server searches these locations in order and uses the first one found:

Priority

Source

1

Path in MYSQL_CONFIG environment variable

2

connections.json in the working directory

3

~/.mysql-mcp/connections.json

4

/etc/mysql-mcp/connections.json

5

Inline JSON in MYSQL_CONNECTIONS env var (legacy / CI fallback)


All connection fields

MySQL fields

Field

Type

Required

Default

Description

name

string

Unique identifier ([a-zA-Z0-9_-] only)

host

string

MySQL host (usually 127.0.0.1 when tunnelling)

user

string

MySQL username

password

string

MySQL password

port

int

3306

MySQL port

label

string

name

Human-readable display name

database

string

null

Default schema

use_tls

bool

false

Enforce TLS on the MySQL connection

ssl_ca

string

null

Path to CA certificate (recommended with TLS)

ssl_cert

string

null

Path to client cert (mutual TLS)

ssl_key

string

null

Path to client key (mutual TLS)

allow_writes

bool

false

Allow INSERT/UPDATE/DELETE/CREATE/ALTER

allowed_dbs

[string]

[]

Schema whitelist — empty means all visible schemas

max_rows

int

500

Max rows returned per query

query_timeout

int

30

Query timeout in seconds

rate_limit

int

120

Max queries per minute

SSH tunnel fields

All optional. Omit the ssh_* fields entirely if MySQL is already reachable without a tunnel.

Field

Type

Required

Default

Description

ssh_host

string

✓ to enable

VPS hostname or IP

ssh_port

int

22

SSH port on the VPS

ssh_user

string

system default

SSH login username

ssh_key

string

Path to SSH private key (strongly preferred over password)

ssh_password

string

SSH password

ssh_remote_host

string

127.0.0.1

MySQL host as seen from the VPS

ssh_remote_port

int

same as port

MySQL port as seen from the VPS

ssh_local_port

int

✓ if ssh_host set

Local port the tunnel binds to

ssh_keepalive

int

30

ServerAliveInterval in seconds

ssh_retry_delay

int

5

Initial reconnect wait in seconds

ssh_max_delay

int

60

Max reconnect wait — exponential backoff ceiling


Available tools

Tool

Description

mysql_list_instances

List all connections with config and live tunnel state — call this first

mysql_query

Execute SQL on a named connection

mysql_list_databases

List schemas visible on a connection

mysql_list_tables

List tables and views in a schema

mysql_describe_table

Full schema: columns, indexes, CREATE TABLE DDL

mysql_explain_query

EXPLAIN a SELECT to inspect its execution plan

mysql_server_status

Version, uptime, connection counts, TLS status, tunnel state


What the logs look like

Normal startup:

[INFO] Loading connections from: /home/you/mysql-mcp/connections.json
[INFO] Connection 'app' (App Database): mysql=127.0.0.1:13306 db=app_db writes=True tunnel=your-vps.example.com:13306
[INFO] Starting 3 SSH tunnel(s)…
[INFO] SSH tunnel 'app' ready on 127.0.0.1:13306 (connect #1)
[INFO] SSH tunnel 'analytics' ready on 127.0.0.1:13307 (connect #1)
[INFO] SSH tunnel 'legacy' ready on 127.0.0.1:13308 (connect #1)
[INFO] MySQL pool ready for 'app' (127.0.0.1:13306)
[INFO] MySQL pool ready for 'analytics' (127.0.0.1:13307)
[INFO] MySQL pool ready for 'legacy' (127.0.0.1:13308)
[INFO] All connections ready ✓

Tunnel drops and reconnects:

[WARNING] SSH tunnel 'app' died (exit 255). Reconnecting in 5s…
[WARNING] SSH stderr for 'app': Connection to your-vps.example.com closed.
[INFO]    SSH tunnel 'app' ready on 127.0.0.1:13306 (connect #2)

Query during reconnect:

{ "error": "SSH tunnel for 'app' is reconnecting — please try again shortly." }

Adding a new container later

Add one more object to connections.json and restart the MCP server. No code changes:

{
  "name":            "warehouse",
  "label":           "Data Warehouse",
  "host":            "127.0.0.1",
  "port":            13309,
  "user":            "mcp_warehouse",
  "password":        "strong_password_4",
  "database":        "warehouse_db",
  "allowed_dbs":     ["warehouse_db"],
  "ssh_host":        "your-vps.example.com",
  "ssh_user":        "ubuntu",
  "ssh_key":         "/home/you/.ssh/mcp_vps",
  "ssh_remote_port": 3309,
  "ssh_local_port":  13309
}

Production checklist

  • connections.json is in .gitignore and never committed

  • Each MySQL user has only the minimum required privileges

  • allow_writes: false for every read-only connection

  • allowed_dbs set for every connection to restrict schema access

  • All Docker containers bound to 127.0.0.1 on the VPS — not 0.0.0.0

  • A dedicated SSH key with no passphrase is used (ssh-keygen -t ed25519)

  • All MySQL passwords are strong and randomly generated

  • max_rows and query_timeout are tuned per connection's workload

  • ssh_keepalive: 30 (default) detects dead connections within ~90s

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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/yogaaltariz/mysql-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server