Skip to content

About

MCP server for read-only MySQL diagnostics

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

mysql-mcp-server

A general purpose Model Context Protocol (MCP) server for investigating MySQL 8 databases. It connects to a single instance directly, over a unix socket, or through an SSH tunnel, and exposes a curated set of read-only diagnostic tools: sessions, locks, transactions, slow queries, InnoDB internals, schema, and replication.

It is designed for incident response and day-to-day troubleshooting, so it is read-only by default and avoids impactful actions.

Features

  • Connect directly (TCP), via unix socket, or through an SSH tunnel (key, password, or agent auth, with known_hosts verification).
  • Read-only by default. The generic query tool rejects anything that is not SELECT/SHOW/EXPLAIN/DESCRIBE/WITH/USE, blocks stacked statements, and the driver runs with multi-statements disabled.
  • System schemas only by default. Queries may only touch MySQL's own catalogs (information_schema, performance_schema, mysql, sys), and live statement text is masked, so the server never reads application row data unless you opt in with --allow-user-data.
  • Per-query timeout and a configurable row cap on every result.
  • Session guardrails. Every connection is opened read-only (when --readonly) with a max_execution_time and short lock-wait timeouts, so a diagnostic query can never write, run away, or sit waiting on a lock.
  • Privilege-aware tools. At connect the server probes what the user can do (PROCESS, REPLICATION CLIENT, performance_schema, sys) and hides the tools that would just return "access denied".
  • Toolsets. Expose only the groups you need (--toolsets health,locks) or cherry-pick individual tools (--tools mysql_query,mysql_ping).
  • Investigation runbooks. Ships MCP prompts (slow server triage, blocking locks, replication lag, ...) that walk an agent through the right tools in order.
  • Targets MySQL 8.0.22 or newer and uses modern syntax (SHOW REPLICA STATUS, Replica_*/Source_* columns, and SHOW BINARY LOG STATUS on 8.4+). MariaDB and MySQL 5.7 are rejected at connect.
  • 25 diagnostic tools plus an optional, explicitly gated mysql_kill.

Install

Requires Go 1.25+.

go build -o mysql-mcp-server ./cmd/mysql-mcp-server
# or
make build

Docker

docker build -t mysql-mcp-server .

The server speaks MCP over stdio, so run it interactively and pass configuration as environment variables. From an MCP client, use docker run -i --rm as the command:

docker run -i --rm \
  -e MYSQL_HOST=db.internal -e MYSQL_USER=diag -e MYSQL_PASSWORD \
  mysql-mcp-server

Security

This server is read-only and limited to MySQL's system schemas by default, so it does not read application data unless you opt in. See SECURITY.md for the full safety model, operator guidance, and how to report a vulnerability.

Configuration

Every setting can be provided as an environment variable or a flag; flags take precedence over environment variables, which take precedence over defaults.

Connection

Env Flag Default Description
MYSQL_HOST --host 127.0.0.1 MySQL host
MYSQL_PORT --port 3306 MySQL port
MYSQL_USER --user (required) MySQL user
MYSQL_PASSWORD --password MySQL password (prefer the env var or --password-file; a flag value is visible in the process list)
MYSQL_PASSWORD_FILE --password-file Path to a file holding the password (trailing newline trimmed); cannot be combined with MYSQL_PASSWORD/--password
MYSQL_DATABASE --database Default database/schema
MYSQL_SOCKET --socket Unix socket path (overrides host/port)
MYSQL_TLS --tls false false, true, skip-verify, or preferred

SSH tunnel (optional)

Setting MYSQL_SSH_HOST enables the tunnel. MySQL host/port are then resolved from the SSH server's perspective (e.g. keep 127.0.0.1:3306 to reach the database bound to localhost on the remote host).

Env Flag Default Description
MYSQL_SSH_HOST --ssh-host SSH host; enables SSH when set
MYSQL_SSH_PORT --ssh-port 22 SSH port
MYSQL_SSH_USER --ssh-user SSH user
MYSQL_SSH_PASSWORD --ssh-password SSH password (optional)
MYSQL_SSH_KEY --ssh-key Path to private key
MYSQL_SSH_KEY_PASSPHRASE --ssh-key-passphrase Passphrase for the key
MYSQL_SSH_KNOWN_HOSTS --ssh-known-hosts ~/.ssh/known_hosts known_hosts file
MYSQL_SSH_INSECURE_HOST_KEY --ssh-insecure-host-key false Skip host key check (insecure)
MYSQL_SSH_AGENT --ssh-agent true Use SSH_AUTH_SOCK when available

Safety / limits

Env Flag Default Description
MYSQL_READONLY --readonly true Reject non read-only statements in mysql_query
MYSQL_ALLOW_USER_DATA --allow-user-data false Allow querying user tables and unmask live statement text
MYSQL_ALLOW_KILL --allow-kill false Register the impactful mysql_kill tool
MYSQL_MAX_ROWS --max-rows 500 Max rows returned per query
MYSQL_QUERY_TIMEOUT --query-timeout 30s Per-query timeout
MYSQL_CONNECT_TIMEOUT --connect-timeout 10s Connection timeout

Tool exposure & guardrails

Env Flag Default Description
MYSQL_TOOLSETS --toolsets (all) Comma list of toolsets to enable (all for everything)
MYSQL_TOOLS --tools Comma list of individual tools to additionally enable
MYSQL_PRIVILEGE_FILTERING --privilege-filtering true Hide tools the connected user lacks privileges for
MYSQL_SESSION_GUARDRAILS --session-guardrails true Bound each connection with read-only, statement, and lock-wait timeouts
MYSQL_LOCK_WAIT_TIMEOUT --lock-wait-timeout 10s Lock-wait timeout applied when guardrails are on
MYSQL_PROMPTS --prompts true Register the investigation runbook prompts

Toolset names are: health, query, processes, locks, innodb, performance, schema, replication. When --toolsets is empty all of them are enabled; --tools is additive on top of the selected toolsets.

Use with an MCP client

The server speaks MCP over stdio. Point your client at the built binary and pass configuration via env.

{
  "mcpServers": {
    "mysql": {
      "command": "/absolute/path/to/mysql-mcp-server",
      "env": {
        "MYSQL_HOST": "127.0.0.1",
        "MYSQL_PORT": "3306",
        "MYSQL_USER": "readonly_user",
        "MYSQL_PASSWORD": "secret",
        "MYSQL_DATABASE": "appdb"
      }
    }
  }
}

SSH tunnel example (reach a DB on a private host through a bastion):

{
  "mcpServers": {
    "mysql-prod": {
      "command": "/absolute/path/to/mysql-mcp-server",
      "env": {
        "MYSQL_HOST": "127.0.0.1",
        "MYSQL_PORT": "3306",
        "MYSQL_USER": "readonly_user",
        "MYSQL_PASSWORD": "secret",
        "MYSQL_SSH_HOST": "bastion.example.com",
        "MYSQL_SSH_USER": "ops",
        "MYSQL_SSH_KEY": "/home/me/.ssh/id_ed25519"
      }
    }
  }
}

Grant the database user the minimum needed for diagnostics, typically: SELECT, PROCESS, REPLICATION CLIENT (and SHOW VIEW for schema inspection).

Tools

All tools are read-only except mysql_kill, which is only registered when MYSQL_ALLOW_KILL=true.

Connectivity & health

  • mysql_ping - verify connectivity; report version, user, server time, uptime.
  • mysql_server_status - curated health dashboard (connections, QPS, buffer pool hit ratio, temp tables on disk, full scans) with threshold flags.
  • mysql_health_report - one-shot triage combining the health snapshot, long-running queries, long-open transactions, lock waits, and replication.
  • mysql_show_variables - SHOW GLOBAL VARIABLES with an optional LIKE.
  • mysql_show_status - SHOW GLOBAL STATUS with an optional LIKE.
  • mysql_error_log - recent server error-log events from performance_schema.error_log (8.0.22+). filter=corruption (default) surfaces InnoDB corruption and "page LSN in the future" signals (MY-011971/MY-011972); filter=errors or all, optionally narrowed by subsystem. Needs SELECT on performance_schema.error_log.

Sessions, locks & transactions

  • mysql_processlist - active sessions from the non-locking performance_schema.processlist (idle hidden by default; filter by time/user).
  • mysql_connection_summary - activity per user or host (connections, statements, latency, table scans, file IO) from the sys summary views.
  • mysql_lock_waits - InnoDB row-lock waits, pairing blocked and blocking transactions.
  • mysql_transactions - open InnoDB transactions, oldest first, with age, rows locked/modified, and current query.
  • mysql_metadata_locks - metadata locks (MDL) from performance_schema (8.0+).
  • mysql_wait_events - what each active connection is currently waiting on (performance_schema.events_waits_current), worst waits first.
  • mysql_innodb_status - SHOW ENGINE INNODB STATUS, optionally a single section (deadlock, transactions, buffer-pool, log, ...).
  • mysql_kill - kill a query or connection. Gated by MYSQL_ALLOW_KILL.

Queries & performance

  • mysql_query - run a single read-only statement and return a table.
  • mysql_explain - execution plan for a SELECT (traditional/tree/json, or EXPLAIN ANALYZE).
  • mysql_explain_for_connection - plan for the statement already running on another connection (EXPLAIN FOR CONNECTION); format=json requires --allow-user-data.
  • mysql_top_queries - most expensive statement digests by latency, count, or rows examined.
  • mysql_table_scans - statement digests doing full table scans.
  • mysql_io_hotspots - top IO consumers by file or by table (sys views).
  • mysql_index_usage - unused, redundant, or no-primary-key index reports.

Schema

  • mysql_databases - databases with sizes and default charset/collation.
  • mysql_tables - tables in a database with engine, rows, and size.
  • mysql_describe_table - columns, indexes, foreign keys, and CREATE TABLE.
  • mysql_largest_tables - largest tables server-wide by data+index size.

Replication

  • mysql_replication_status - replica IO/SQL thread health and lag, plus the binary log / primary position.

Toolsets & privilege filtering

Tools are grouped into toolsets so you can expose only what a given client needs. Restrict them with --toolsets health,locks (or MYSQL_TOOLSETS), and add back individual tools with --tools mysql_query. The startup banner (on stderr) prints exactly what was registered.

With --privilege-filtering on (the default), the server probes the connected user's privileges at connect and hides tools it cannot run: mysql_innodb_status, mysql_transactions, and mysql_lock_waits need PROCESS; mysql_replication_status needs REPLICATION CLIENT; the performance, mysql_processlist, mysql_metadata_locks, and mysql_lock_waits tools need performance_schema (and mysql_index_usage also needs readable sys views); mysql_error_log needs SELECT on performance_schema.error_log, which is a separate grant from the rest of performance_schema. Detection degrades gracefully: only a genuine access-denied error hides a tool, so a transient failure never silently drops one.

If filtering is off, or a probe misses an edge case and a tool is still called without the needed grant, the tool returns the raw MySQL error (for example Error 1142 ... SELECT command denied ...) plus a short hint naming the missing privilege, so it is clear what to grant rather than just failing opaquely.

Prompts (investigation runbooks)

When --prompts is on (the default) the server registers MCP prompts that guide an agent through a read-only investigation using the tools above:

  • slow_server_triage - triage a server that is slow or overloaded right now.
  • blocking_locks - trace an InnoDB lock-wait chain to the root blocker.
  • replication_lag - diagnose why a replica is falling behind its source.
  • connection_saturation - investigate too-many-connections / refused connects.
  • query_tuning - find and tune an expensive query (optional schema argument).
  • capacity_review - find where space goes and which indexes are wasteful.

Data access policy

By default the server reads only MySQL's own system schemas and never your application data:

  • System-schema allowlist. mysql_query and mysql_explain reject any statement whose table references are not in information_schema, performance_schema, mysql, or sys. Bare table names resolve against a system schema, and backtick-quoted, comma-joined, and subquery table refs are all checked, so SELECT * FROM appdb.customers is refused.
  • Masked statement text. Live SQL shown by mysql_processlist, mysql_transactions, mysql_lock_waits, mysql_health_report, mysql_innodb_status, and replication error fields has its string and numeric literals replaced with ?, so values like emails or tokens do not leak while you can still see the query shape and the tables involved. mysql_query also masks well-known raw-statement columns (info, sql_text, query_sample_text, argument, ...) in its own results, so a direct SELECT from a performance_schema events table does not leak literals; the normalized digest_text is left intact. This column masking is best effort (an alias or expression can evade it), which is why --allow-user-data remains the real boundary.
  • Metadata, not rows. The schema and performance tools report on user tables using catalog metadata only (names, columns, sizes, normalized digests), never their row contents.
  • INTO OUTFILE/INTO DUMPFILE blocked to prevent writing query results to the server filesystem.
  • Error-log text is shown verbatim. mysql_error_log returns the data column from performance_schema.error_log unmasked, because it holds diagnostic text (LSNs, file paths, object names) rather than query literals, and masking would destroy the values you need to read. It is gated behind its own SELECT grant on that table instead.

Set MYSQL_ALLOW_USER_DATA=true (or pass --allow-user-data) only when you genuinely need to read application data; this lifts the schema allowlist and disables literal masking. Note that mysql_innodb_status masking covers the statement text after each thread/query header and the hex/asc record dumps in the lock sections; InnoDB counters, lock metadata, and table/index names are kept so the dump stays useful.

Safety model

  • Read-only classification. mysql_query strips comments and quoted literals, then requires a single leading read-only verb and rejects embedded semicolons, so keywords or ; inside strings cannot smuggle writes.
  • Driver hardening. Multi-statements are disabled and parameters are not interpolated client-side.
  • Bounded results. Every query is capped at MYSQL_MAX_ROWS and runs under the per-query timeout.
  • Session guardrails. When enabled, each pooled connection is pinned transaction_read_only (under --readonly) with a max_execution_time equal to the query timeout and short innodb_lock_wait_timeout/lock_wait_timeout, a server-enforced backstop independent of the SQL classifier.
  • Impactful actions are opt-in. mysql_kill is hidden unless explicitly enabled and is annotated as destructive.

Read-only mode is a guardrail, not a security boundary. Always connect with a least-privilege database user.

Development

make fmt-check   # gofmt
make vet         # go vet
make test        # unit tests
make build       # build the binary
make all         # all of the above

Integration tests

The tests in internal/tools/integration_test.go and internal/tools/integration_features_test.go run the full safety chain, session guardrails, and privilege filtering against a real server. They skip unless MYSQL_MCP_INTEGRATION is set, so plain make test never needs a database:

MYSQL_MCP_INTEGRATION=1 MYSQL_HOST=127.0.0.1 MYSQL_PORT=3306 MYSQL_USER=root \
  go test -run Integration -v ./internal/tools/

License

MIT

About

MCP server for read-only MySQL diagnostics

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages