mysqlpeek
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mysqlpeekEstimate the cost of running SELECT * FROM orders WHERE created_at > '2024-01-01'."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 ONLYtransaction 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.
EXPLAINtells you how many rows a query would examine before a single one is read, andmax_join_sizemakes 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 mysqlpeekClaude Code then asks for four things:
Field | Example |
Server |
|
Username |
|
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 mysqlpeekWorks 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 serverRelated MCP server: MySQL MCP 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 # optionalYou enter | Host | Port | TLS |
| db.internal | 3306 | no |
| db.internal | 3307 | no |
| db.internal | 3306 | no |
| 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 |
| Path to a multi-instance profiles file — see below |
| Path to a JSONL record of every query decision |
Tools
Discovery — schema exploration, no user SQL accepted:
Tool | Purpose |
| Configured instances, which is default, and each one's limits |
| Schemas visible to your user, system ones flagged |
| Tables with engine, approximate rows, size, primary key, partitioning |
| Columns, types, nullability, key membership, charset, comments |
| Every index with its column order and cardinality |
| Full |
Query:
Tool | Purpose |
| Execute a |
| Preview rows from a table (SQL built server-side) |
| 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 |
| Statements executing now, longest first (needs PROCESS to see other sessions) |
| Statement digests from performance_schema ranked by time, count, rows examined or missing index |
| Tables by size with fragmentation, index-to-data ratio and auto-increment headroom |
| Replica threads, lag, last error, |
| Sessions blocked on row locks and who blocks them |
| Transactions open longer than N seconds, idle-in-transaction included |
| Connections, buffer pool hit rate, temp tables on disk, lock waits, history list |
Cost & validation — these read no table data:
Tool | Purpose |
|
|
|
|
|
|
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:
Statement policy — single statement only; must open with
SELECT,WITH…SELECT,SHOW,DESCRIBEorEXPLAIN; 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.userand the other credential tables are refused, backticked or not. A missingLIMITis appended; an oversized one is clamped.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 ONLYtime 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 = …withsql_big_selects = 0— the optimiser refuses a statement it expects to examine more rows than this, before reading anyMulti-statement execution is off at the protocol level, so
SELECT 1; DROP …is a syntax error to the server.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 asSET GLOBAL: the parser refuses them, but aREAD ONLYtransaction does not, so an account holdingSUPERorSYSTEM_VARIABLES_ADMINis one parser bug away from changing the server. Do not point mysqlpeek atroot:
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 |
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
| Trailing newline stripped |
|
| 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
This server cannot be deployed
Maintenance
Related MCP Connectors
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
- dataOAuthco.thinair
Read-only PostgreSQL, MySQL, SQL Server access via MCP — 24 dialect-aware hosted tools.
Generate, fix, explain and run read-only SQL on PostgreSQL, MySQL and SQL Server
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Related MCP Servers
- AlicenseBqualityDmaintenanceProvides secure, read-only access to MariaDB/MySQL databases, allowing users to list databases, explore table schemas, and execute SQL queries with built-in security measures.459 npm4MIT
- FlicenseNot gradedqualityDmaintenanceEnables safe interaction with MySQL databases through SELECT queries, table structure inspection, and database schema exploration. Provides read-only access to query data and examine database metadata.1-
- AlicenseAqualityDmaintenanceEnables read-only MySQL database access, allowing listing databases, tables, describing schemas, and executing SELECT/SHOW/DESCRIBE/EXPLAIN queries.745 npm4MIT
- AlicenseNot gradedqualityDmaintenanceEnables schema introspection and safe read-only queries on MySQL databases, supporting table/column discovery, relationship exploration, and controlled SELECT queries.10 npmMIT