mysql-readonly-mcp
A readonly MCP server for MySQL databases that ensures safety by whitelisting only SELECT, SHOW, DESCRIBE, and EXPLAIN statements, with table blacklist support and configurable limits.
README
MySQL Readonly MCP Server
A Python MCP (Model Context Protocol) server that provides readonly access to a MySQL database.
-
Chinese documentation:
README.zh-CN.md -
Transport: stdio (recommended for MCP clients) and HTTP/SSE (standalone service)
-
Safety: Only
SELECT,SHOW,DESCRIBE,DESC, andEXPLAINstatements are permitted -
Config: stdio — MySQL settings via
mcp.jsonenv; SSE — environment at process start, optionalMCP_BEARER_TOKENfor HTTP auth -
Table blacklist:
QUERY_TABLE_BLACKLISTblocks data access viaquery;describe_tablestill works for schema on listed tables
Requirements
- Installer and MCP runtime: Python 3.10+.
install.pychecks the interpreter that runs it before any other step; if the version is too low, it exits immediately (usepython3.12 install.py,py -3.12 install.py, etc.). - Optional:
install.py --python /path/to/python3.12creates.venvwith that binary instead ofsys.executable(that binary must also be 3.10+). - A reachable MySQL instance.
Installation
First-time install (recommended)
Run install.py with a 3.10+ interpreter (python, python3, or py -3).
The script creates .venv with python -m venv, then python -m pip install -r requirements.txt.
After a successful install, an interactive wizard (if stdin is a TTY) asks for:
transport (stdio or sse), core MySQL fields, QUERY_TABLE_BLACKLIST (always),
optional timeouts / QUERY_DEFAULT_LIMIT / TLS paths, and for SSE MCP_HOST /
MCP_PORT / MCP_BEARER_TOKEN. It prints a complete mcp.json snippet (and for
SSE, shell export lines plus the server command). Use --no-wizard to skip (CI /
automation). Non-interactive stdin skips the wizard automatically.
Windows (CMD or double-click in Explorer):
cd \path\to\MySQL_MCP
install.bat
macOS / Linux:
cd /path/to/MySQL_MCP
python3 install.py
Useful flags:
| Flag | Meaning |
|---|---|
--recreate |
Delete .venv and reinstall |
--dry-run |
Show the planned venv/pip steps only (still requires 3.10+ to run the script) |
--no-wizard |
Do not run the post-install configuration wizard |
--python EXE |
Create .venv with this 3.10+ interpreter (EXE on PATH or full path) |
If the Python Launcher is missing on Windows, install Python 3.10+ from python.org and enable “Add to PATH”.
Manual install (without the script)
cd /path/to/MySQL_MCP
python3 -m venv .venv
# Windows: .venv\Scripts\pip install -r requirements.txt
# Unix: .venv/bin/pip install -r requirements.txt
After either method, set mcp.json command to the venv python / python.exe
absolute path — not a bare python on PATH.
Configuration via mcp.json
All MySQL connection parameters are passed through the standard MCP configuration file. Choose Option A (stdio) or Option B (SSE) depending on your client.
Option A — stdio transport (recommended)
The MCP client launches server.py as a subprocess and injects MySQL credentials via the
env block. No separate server process is needed.
Copy the following block into your client's MCP settings (e.g. Cursor mcp.json,
Claude Desktop claude_desktop_config.json, or a project-level .cursor/mcp.json):
{
"mcpServers": {
"mysql-readonly": {
"command": "/absolute/path/to/MySQL_MCP/.venv/bin/python",
"args": ["/absolute/path/to/MySQL_MCP/server.py"],
"env": {
"MYSQL_HOST": "127.0.0.1",
"MYSQL_PORT": "3306",
"MYSQL_USER": "your_mysql_user",
"MYSQL_PASSWORD": "your_mysql_password",
"MYSQL_DATABASE": "your_database_name",
"MYSQL_CONNECT_TIMEOUT": "10",
"MYSQL_SSL": "false",
"QUERY_DEFAULT_LIMIT": "100",
"QUERY_TABLE_BLACKLIST": "sensitive_table,internal_audit_log"
}
}
}
}
args must contain the absolute path to server.py.
On Windows, use "command": "C:\\path\\to\\MySQL_MCP\\.venv\\Scripts\\python.exe" (escape backslashes in JSON).
Replace the env values with your actual MySQL credentials.
Option B — HTTP/SSE transport
Start the server as a standalone HTTP service first. MySQL settings come from the
process environment (or your shell / systemd / Docker environment block).
export MYSQL_HOST=127.0.0.1
export MYSQL_PORT=3306
export MYSQL_USER=your_mysql_user
export MYSQL_PASSWORD=your_mysql_password
export MYSQL_DATABASE=your_database_name
# Default bind is 127.0.0.1 (safer). Use 0.0.0.0 only on trusted networks or
# behind a reverse proxy; set MCP_BEARER_TOKEN so clients must send
# Authorization: Bearer <token> on SSE and message requests.
export MCP_BEARER_TOKEN=your-long-random-secret # optional but recommended if exposed
# Use the venv interpreter (from repo root after install):
# Unix/macOS: .venv/bin/python server.py --transport sse --port 8000
# Windows: .venv\Scripts\python.exe server.py --transport sse --port 8000
.venv/bin/python server.py --transport sse --port 8000
Then point your MCP client at the SSE endpoint (and configure the client to send
the Bearer token if MCP_BEARER_TOKEN is set):
{
"mcpServers": {
"mysql-readonly": {
"url": "http://localhost:8000/sse"
}
}
}
Environment Variables Reference
| Variable | Required | Default | Description |
|---|---|---|---|
MYSQL_HOST |
No | 127.0.0.1 |
MySQL hostname or IP |
MYSQL_PORT |
No | 3306 |
MySQL port |
MYSQL_USER |
Yes | — | MySQL username |
MYSQL_PASSWORD |
No | "" |
MySQL password |
MYSQL_DATABASE |
Yes | — | Target database name |
MYSQL_CONNECT_TIMEOUT |
No | 10 |
TCP connect timeout (seconds) |
MYSQL_READ_TIMEOUT |
No | 30 |
Socket read timeout (seconds) |
MYSQL_WRITE_TIMEOUT |
No | 30 |
Socket write timeout (seconds) |
MYSQL_MAX_EXECUTION_TIME |
No | 30000 |
Per-query server limit (milliseconds); SET SESSION MAX_EXECUTION_TIME |
MYSQL_SSL |
No | false |
Enable TLS to MySQL: "true" / "false" |
MYSQL_SSL_CA |
No | "" |
Path to CA certificate (when using TLS) |
MYSQL_SSL_CERT |
No | "" |
Path to client certificate |
MYSQL_SSL_KEY |
No | "" |
Path to client private key |
MYSQL_SSL_VERIFY_CERT |
No | true |
Set "false" to skip server cert verification (not recommended) |
QUERY_DEFAULT_LIMIT |
No | 100 |
Upper bound on rows for SELECT; explicit LIMIT is also capped to this (after applying the limit tool argument) |
QUERY_TABLE_BLACKLIST |
No | "" |
Comma-separated table names. The query tool rejects SQL that references them (including JOIN); describe_table still returns schema for those tables. Not a substitute for DB grants. |
MCP_HOST |
No | 127.0.0.1 |
Bind address for SSE transport (CLI --host overrides at runtime) |
MCP_PORT |
No | 8000 |
Bind port for SSE (CLI --port overrides) |
MCP_BEARER_TOKEN |
No | "" |
If non-empty, SSE HTTP requests require Authorization: Bearer <token> |
Available Tools
query
Execute a readonly SQL statement and return results as structured JSON.
| Parameter | Type | Default | Description |
|---|---|---|---|
sql |
str | — | SQL statement (SELECT / SHOW / DESCRIBE / EXPLAIN) |
limit |
int | QUERY_DEFAULT_LIMIT |
Capped at QUERY_DEFAULT_LIMIT; combined with server rewrite so SELECT never returns more rows than this effective cap (even if SQL contains a larger LIMIT) |
Returns:
{
"columns": ["id", "name", "email"],
"rows": [
{"id": 1, "name": "Alice", "email": "alice@example.com"}
],
"row_count": 1
}
list_tables
List all tables in the configured database.
Returns:
{
"database": "mydb",
"tables": ["users", "orders", "products"],
"count": 3
}
describe_table
Get the column schema of a specific table.
For tables in QUERY_TABLE_BLACKLIST, use this tool for schema — the query tool
rejects any SQL (including DESCRIBE) that references those tables.
| Parameter | Type | Description |
|---|---|---|
table_name |
str | Table name (letters, digits, underscores only) |
Returns:
{
"table": "users",
"columns": [
{"Field": "id", "Type": "int", "Null": "NO", "Key": "PRI", "Default": null, "Extra": "auto_increment"},
{"Field": "name", "Type": "varchar(255)", "Null": "YES", "Key": "", "Default": null, "Extra": ""},
{"Field": "email", "Type": "varchar(255)", "Null": "YES", "Key": "UNI", "Default": null, "Extra": ""}
]
}
Security — Readonly Enforcement
The server enforces readonly access at the application layer with a two-stage guard:
- Whitelist — the first keyword must be one of
SELECT,SHOW,DESCRIBE,DESC,EXPLAIN. - Blacklist — the full statement is scanned for forbidden patterns:
INSERT,UPDATE,DELETE,DROP,ALTER,CREATE,TRUNCATE,REPLACE,GRANT,REVOKE,COMMIT,ROLLBACK,LOAD DATA,INTO OUTFILE,SLEEP,BENCHMARK, and more. - Multi-statement rejection — any SQL containing
;(after stripping a single trailing semicolon) is rejected. - Identifier validation — table names passed to
describe_tableare validated to contain only[A-Za-z0-9_]characters before being interpolated into the query. - Table blacklist — tables listed in
QUERY_TABLE_BLACKLISTcannot be used for data access through thequerytool (including subqueries /JOINs that reference them). Thedescribe_tabletool is still allowed for those names so agents can inspect schema. Errors use a bilingual JSON payload (message_en/message_zh).
For production use, also configure the MySQL user with
SELECT-only privileges at the database level as an additional layer of defense. Treat the app-level blacklist as a convenience, not the primary authorization boundary.
Verification
After configuration, test the server with these queries:
-- Should succeed
SHOW TABLES
SELECT * FROM your_table LIMIT 5
DESCRIBE your_table
EXPLAIN SELECT id FROM your_table
-- Should be rejected with an error
DELETE FROM your_table WHERE id = 1
INSERT INTO your_table (name) VALUES ('x')
SELECT 1; DROP TABLE your_table
SELECT SLEEP(5)
Running Locally (without an MCP client)
You can test the server directly from the command line using the MCP CLI:
# Install dev dependency
pip install "mcp[cli]"
# stdio mode — interactive inspector
MYSQL_HOST=127.0.0.1 MYSQL_USER=root MYSQL_PASSWORD=secret MYSQL_DATABASE=mydb \
mcp dev server.py
# SSE mode — start server, then open http://localhost:8000/sse in a browser or curl
MYSQL_HOST=127.0.0.1 MYSQL_USER=root MYSQL_PASSWORD=secret MYSQL_DATABASE=mydb \
.venv/bin/python server.py --transport sse
Project Structure
MySQL_MCP/
├── server.py # MCP server: tools, SQL guard, MySQL connector
├── install.py # First-time setup: venv + pip install (all platforms)
├── install.bat # Windows launcher for install.py
├── mcp.json # MCP configuration template (stdio + SSE examples)
├── requirements.txt # Python dependencies
├── pyproject.toml # Package metadata
├── Dockerfile # Container image definition
├── docker-compose.yml # Compose file (MCP server + optional local MySQL)
└── README.md # This file
Docker Deployment
Build and run with Docker Compose
Create a .env file in the project root with your MySQL credentials:
MYSQL_HOST=host.docker.internal # use host.docker.internal to reach the host machine
MYSQL_PORT=3306
MYSQL_USER=your_mysql_user
MYSQL_PASSWORD=your_mysql_password
MYSQL_DATABASE=your_database_name
Uncomment the ports block in docker-compose.yml (see the
127.0.0.1:${MCP_PORT:-8000}:8000 example) so the host can reach the container; it is
commented out by default for safety.
Then start the container:
docker compose up -d
Set MCP_BEARER_TOKEN in .env when exposing SSE. The MCP SSE endpoint is then
http://localhost:8000/sse (or the mapped host/port you chose).
Point your MCP client at it:
{
"mcpServers": {
"mysql-readonly": {
"url": "http://localhost:8000/sse"
}
}
}
Build and run manually
docker build -t mysql-mcp-server .
docker run -d \
--name mysql-mcp-server \
-p 8000:8000 \
-e MYSQL_HOST=host.docker.internal \
-e MYSQL_USER=your_user \
-e MYSQL_PASSWORD=your_password \
-e MYSQL_DATABASE=your_db \
mysql-mcp-server
Using with a local MySQL container
Uncomment the mysql service block in docker-compose.yml to spin up a local MySQL
alongside the MCP server. The service uses a healthcheck so the MCP server only
starts after MySQL is ready.
Extending
The following improvements are recommended before production use:
- Connection pooling — replace per-request connections with
DBUtilsorSQLAlchemypool - SQL AST validation — use
sqlglotorsqlparsefor structural analysis instead of regex - Audit logging — log every executed query with timestamp, client identity, and row count
- Row-level rate limiting — enforce per-client query frequency limits
- TLS / mTLS at the edge — terminate HTTPS and optional client certificates in a reverse
proxy in front of SSE; combine with
MCP_BEARER_TOKENfor defense in depth
License
This project is licensed under the MIT License. See LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.