mysql-readonly-mcp
Provides read-only access to a MySQL database, allowing SELECT, SHOW, DESCRIBE, and EXPLAIN queries, with safety enforcement and table blacklisting.
Click on "Install 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., "@mysql-readonly-mcpShow me all tables in the current database"
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.
MySQL Readonly MCP Server
A Python MCP (Model Context Protocol) server that provides readonly access to a MySQL database.
Chinese documentation:
README.zh-CN.mdTransport: stdio (recommended for MCP clients) and HTTP/SSE (standalone service)
Safety: Only
SELECT,SHOW,DESCRIBE,DESC, andEXPLAINstatements are permittedConfig: stdio — MySQL settings via
mcp.jsonenv; SSE — environment at process start, optionalMCP_BEARER_TOKENfor HTTP authTable 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.
Related MCP server: mysql-mcp
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.batmacOS / Linux:
cd /path/to/MySQL_MCP
python3 install.pyUseful flags:
Flag | Meaning |
| Delete |
| Show the planned venv/pip steps only (still requires 3.10+ to run the script) |
| Do not run the post-install configuration wizard |
| Create |
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.txtAfter 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 8000Then 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 |
| No |
| MySQL hostname or IP |
| No |
| MySQL port |
| Yes | — | MySQL username |
| No |
| MySQL password |
| Yes | — | Target database name |
| No |
| TCP connect timeout (seconds) |
| No |
| Socket read timeout (seconds) |
| No |
| Socket write timeout (seconds) |
| No |
| Per-query server limit (milliseconds); |
| No |
| Enable TLS to MySQL: |
| No |
| Path to CA certificate (when using TLS) |
| No |
| Path to client certificate |
| No |
| Path to client private key |
| No |
| Set |
| No |
| Upper bound on rows for |
| No |
| Comma-separated table names. The |
| No |
| Bind address for SSE transport ( |
| No |
| Bind port for SSE ( |
| No |
| If non-empty, SSE HTTP requests require |
Available Tools
query
Execute a readonly SQL statement and return results as structured JSON.
Parameter | Type | Default | Description |
| str | — | SQL statement ( |
| int |
| Capped at |
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 |
| 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 sseProject 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 fileDocker 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_nameUncomment 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 -dSet 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-serverUsing 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
DBUtilsorSQLAlchemypoolSQL AST validation — use
sqlglotorsqlparsefor structural analysis instead of regexAudit 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.
This server cannot be installed
Maintenance
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
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/IBeanCN/mysql-mcp-only-read'
If you have feedback or need assistance with the MCP directory API, please join our Discord server