Skip to main content

mysqlpeek

A read-only MySQL / MariaDB MCP server that lets an agent explore your schema, cost a query before running it, and pick which of several instances it is talking to — per call, never by a mode it can forget it is in.

Most MySQL MCP servers give a model one blunt instrument, "run this SQL", on one host, with a regular expression standing between it and DROP TABLE. mysqlpeek is different on three counts:

  • Writes are refused by the engine, not only by a parser. Every connection runs inside a READ ONLY transaction with server-side caps on execution time, result rows and rows examined. A statement that gets past the parser still cannot write, and cannot run away.
  • Cost before execution. EXPLAIN tells you how many rows a query would examine before a single one is read, and max_join_size makes the server refuse a query the optimiser expects to be too big — before it starts.
  • Several instances, one server. A profiles file names your prod replica, staging and local boxes; every tool takes instance=, and every response says which one answered.

Works with MySQL 5.7.8+, MySQL 8 and 9, MariaDB 10.1+, and the managed flavours that keep the standard session variables (RDS, Cloud SQL, Azure Database for MySQL). MariaDB reports no query_cost and no tree-shaped plan; everything else behaves the same.

Install

As a Claude Code plugin (recommended)

/plugin marketplace add sgr-xd/mysqlpeek
/plugin install mysqlpeek

Claude Code then asks for four things:

Field Example
Server db.internal, host:3307, or mysqls://db.example.com for TLS
Username readonly_user
Password masked; goes to your operating system's secure storage, never to a settings file
Database optional — leave empty and every call names its own

Port and TLS come from whatever you put in Server, so there is nothing else to set.

This also installs two skills: mysql-query-craft, which teaches the explore → estimate → run discipline the tools are built around, and mysql-instance-health, a one-shot sweep of replication, lock waits, long transactions, connections, buffer pool, temp tables and top statements that reports whether the instance is healthy right now.

As a standalone MCP server

claude mcp add mysqlpeek --scope user \
  -e MYSQL_HOST=db.internal \
  -e MYSQL_USER=readonly_user \
  -e MYSQL_PASSWORD_FILE=$HOME/.config/mysqlpeek/password \
  -e MYSQL_DATABASE=shop \
  -- uvx mysqlpeek

Works with any MCP client, not only Claude Code — point it at uvx mysqlpeek over stdio.

From source

git clone https://github.com/sgr-xd/mysqlpeek && cd mysqlpeek
uv venv && uv pip install -e ".[dev]"
pytest -m "not integration"      # unit tests, no database needed
pytest                           # adds live tests against a real server

Configure

Only needed for the standalone and from-source paths; the plugin asks instead.

export MYSQL_HOST=db.internal                     # or host:port, or mysqls://host
export MYSQL_USER=readonly_user
export MYSQL_PASSWORD_FILE=~/.config/mysqlpeek/password   # preferred over MYSQL_PASSWORD
export MYSQL_DATABASE=shop                        # optional
You enter Host Port TLS
db.internal db.internal 3306 no
db.internal:3307 db.internal 3307 no
mysql://db.internal:3306 db.internal 3306 no
mysqls://db.example.com db.example.com 3306 yes

MYSQL_PORT, MYSQL_SSL, MYSQL_SSL_CA and MYSQL_SSL_VERIFY still work and take precedence if you set them. The password is never accepted as a command-line argument — arguments leak through shell history and ps.

Variable Purpose
MYSQLPEEK_PROFILES Path to a multi-instance profiles file — see below
MYSQLPEEK_AUDIT_LOG Path to a JSONL record of every query decision

Tools

Discovery — schema exploration, no user SQL accepted:

Tool Purpose
list_instances Configured instances, which is default, and each one's limits
list_databases Schemas visible to your user, system ones flagged
list_tables Tables with engine, approximate rows, size, primary key, partitioning
describe_table Columns, types, nullability, key membership, charset, comments
list_indexes Every index with its column order and cardinality
show_create_table Full CREATE TABLE / CREATE VIEW DDL

Query:

Tool Purpose
run_select_query Execute a SELECT under enforced caps; reports rows_examined and the optimiser's query_cost
sample_rows Preview rows from a table (SQL built server-side)
profile_column One column's nulls, distinct count, range and most common values, over a bounded sample

Operations — what the server is doing, and whether it is keeping up:

Tool Purpose
list_running_queries Statements executing now, longest first (needs PROCESS to see other sessions)
top_statements Statement digests from performance_schema ranked by time, count, rows examined or missing index
table_storage_stats Tables by size with fragmentation, index-to-data ratio and auto-increment headroom
replication_status Replica threads, lag, last error, read_only / super_read_only
lock_waits Sessions blocked on row locks and who blocks them
long_transactions Transactions open longer than N seconds, idle-in-transaction included
server_health Connections, buffer pool hit rate, temp tables on disk, lock waits, history list

Cost & validation — these read no table data:

Tool Purpose
validate_query EXPLAIN — is the SQL legal, and do the names resolve?
estimate_query_cost EXPLAIN FORMAT=JSON — per table: access type, index chosen, rows it would examine, share of the table; a full_scan / heavy / selective verdict
explain_plan EXPLAIN FORMAT=TREE (MySQL 8.0.16+) or the classic table, plus whether every base table is reached through an index

Every response names the instance that answered and the SQL actually executed, so a rewritten LIMIT is visible rather than silent.

Safety

mysqlpeek is read-only, enforced in three independent layers:

  1. Statement policy — single statement only; must open with SELECT, WITH…SELECT, SHOW, DESCRIBE or EXPLAIN; DML/DDL keywords, INTO OUTFILE, locking reads (FOR UPDATE, FOR SHARE), LOAD_FILE(), SLEEP(), lock functions, executable comments (/*! … */) and cap-raising hints (SET_VAR, MAX_EXECUTION_TIME) are rejected. mysql.user and the other credential tables are refused, backticked or not. A missing LIMIT is appended; an oversized one is clamped.

  2. Session guards — every connection is put into this state the moment it opens, and a connection where any guard fails is refused outright:

    Guard Statement
    no writes SET SESSION TRANSACTION READ ONLY
    time cap SET SESSION max_execution_time = … (MariaDB: max_statement_time)
    result cap SET SESSION sql_select_limit = …
    examined-rows cap SET SESSION max_join_size = … with sql_big_selects = 0 — the optimiser refuses a statement it expects to examine more rows than this, before reading any

    Multi-statement execution is off at the protocol level, so SELECT 1; DROP … is a syntax error to the server.

  3. Database grants — connect as a user with only SELECT. This is the layer that cannot be argued with, and the one you should not skip. It is also the only layer that stops server-state statements such as SET GLOBAL: the parser refuses them, but a READ ONLY transaction does not, so an account holding SUPER or SYSTEM_VARIABLES_ADMIN is one parser bug away from changing the server. Do not point mysqlpeek at root:

CREATE USER 'readonly_user'@'%' IDENTIFIED BY '…';
GRANT SELECT ON shop.* TO 'readonly_user'@'%';
-- and, if you want the ops tools:
GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'readonly_user'@'%';

Tuning the caps

Variable Default
MYSQLPEEK_MAX_EXECUTION_TIME 30 (seconds)
MYSQLPEEK_MAX_RESULT_ROWS 10000
MYSQLPEEK_MAX_JOIN_SIZE 100000000 (rows the optimiser may plan to examine)
MYSQLPEEK_DEFAULT_LIMIT 100
MYSQLPEEK_MAX_LIMIT 10000

Several instances

Point MYSQLPEEK_PROFILES at a JSON file, and every tool gains an optional instance argument.

The file holds references, never values — so it is safe to commit:

{
  "default": "prod-replica",
  "limits": { "max_limit": 5000 },
  "instances": {
    "prod-replica": {
      "description": "read replica of the primary",
      "host":     "mysqls://db-ro.example.com",
      "user":     "${env:PROD_MYSQL_USER:-readonly}",
      "password": "${cmd:vault kv get -field=password secret/mysql-prod}",
      "database": "shop",
      "limits":   { "default_limit": 25, "max_limit": 200, "max_join_size": 5000000 }
    },
    "staging": {
      "host":     "db-staging.example.com:3307",
      "password": "${file:~/.config/mysqlpeek/staging.pw}"
    },
    "local": {
      "host":     "127.0.0.1",
      "user":     "root",
      "password": "${env:LOCAL_MYSQL_PW:-}"
    }
  }
}
Scheme Example Notes
env ${env:PROD_MYSQL_PASSWORD} ${env:VAR:-default} supplies a fallback
file ${file:~/.config/mysqlpeek/prod.pw} Trailing newline stripped
cmd ${cmd:op read op://vault/mysql-prod/password} stdout of a command, run without a shell

A literal password in the file is rejected with an error pointing at the reference syntax. A profiles file writable by anyone but its owner is refused, because a ${cmd:…} reference means the file decides what gets executed.

Limits layer: environment defaults → file-wide limits → per-instance limits. Connections are lazy, so an unreachable instance does not stop the server starting. There is deliberately no use_instance tool: selection is an argument on every call, and every response carries the instance that answered it.

Releasing

Three files carry the version and must agree: .claude-plugin/plugin.json, .claude-plugin/marketplace.json (plugins[0].version) and pyproject.toml. Bump on every shipped change, documentation included — the plugin cache is keyed by version, so an unchanged version means installed copies silently keep the previous build.

License

Apache-2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mysqlpeek-0.1.0.tar.gz (67.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mysqlpeek-0.1.0-py3-none-any.whl (58.8 kB view details)

Uploaded Python 3

File details

Details for the file mysqlpeek-0.1.0.tar.gz.

File metadata

  • Download URL: mysqlpeek-0.1.0.tar.gz
  • Upload date:
  • Size: 67.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.13

File hashes

Hashes for mysqlpeek-0.1.0.tar.gz
Algorithm Hash digest
SHA256 de126fc1e54ca9cdf7a2b5e1e3efe66b2c00a271880e0bf07f1868d8cdb013e3
MD5 a215c56bb99bce1cad050da2ec47fd8c
BLAKE2b-256 8a5de732239c817a0668080a98ce72db3427e9e17ae07726255c3cfe3d901b51

See more details on using hashes here.

File details

Details for the file mysqlpeek-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: mysqlpeek-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 58.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.13

File hashes

Hashes for mysqlpeek-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c07da6c19586e89c49fe803f71af77a6e5b15a8f045bc47b7d8c9a22b4c5f06f
MD5 6c7acce36d76b0f553bdc19f97a4c020
BLAKE2b-256 0ef59d3d69e89f93b168371e52f646ada352aa2a6fc05b6c38f1753c798a859c

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page