@yanzhang123/readonly-db-mcp 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ENTERPRISE_DEPLOYMENT.md +211 -0
- package/LICENSE +21 -0
- package/README.md +450 -0
- package/SECURITY.md +214 -0
- package/npm/bin/mysql-mcp-server-readonly.cjs +11 -0
- package/npm/lib/launcher.cjs +294 -0
- package/npm/requirements.lock +521 -0
- package/npm/vendor/mysql_mcp_server-0.7.2-py3-none-any.whl +0 -0
- package/package.json +53 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Enterprise Deployment and Audit Guide
|
|
2
|
+
|
|
3
|
+
This guide describes a defense-in-depth deployment of MySQL MCP Server. The MCP
|
|
4
|
+
enforces its read-only contract even when the supplied database account has
|
|
5
|
+
write privileges. Database least privilege remains a recommended independent
|
|
6
|
+
containment layer rather than a correctness prerequisite.
|
|
7
|
+
|
|
8
|
+
## Required Security Baseline
|
|
9
|
+
|
|
10
|
+
1. Run the MCP as a dedicated operating-system identity.
|
|
11
|
+
2. When organizational controls permit it, use one dedicated MySQL account per
|
|
12
|
+
environment with only `SELECT` and `SHOW VIEW`. Broader credentials remain
|
|
13
|
+
supported and are reported as a defense-in-depth warning.
|
|
14
|
+
3. Configure `allowed_databases` for every profile.
|
|
15
|
+
4. Require verified TLS (`VERIFY_CA` or `VERIFY_IDENTITY`) or an SSH tunnel.
|
|
16
|
+
5. Store database passwords, bearer tokens and audit signing keys in a secret
|
|
17
|
+
manager and expose them only as process environment variables.
|
|
18
|
+
6. Enable durable, signed, fail-closed audit logging for production profiles.
|
|
19
|
+
7. Keep SSE on loopback behind an authenticated gateway, or configure the
|
|
20
|
+
built-in bearer token.
|
|
21
|
+
8. Forward JSONL audit events to an append-only SIEM and alert on denials,
|
|
22
|
+
timeouts, repeated errors and privilege-assessment warnings.
|
|
23
|
+
9. Review `mask_columns` against the organization's data classification catalog
|
|
24
|
+
and expose database views that omit sensitive fields where possible.
|
|
25
|
+
|
|
26
|
+
## Database Account
|
|
27
|
+
|
|
28
|
+
The MCP does not require a read-only account. Its SQL gate, AST policy, unknown
|
|
29
|
+
function/UDF rejection and MySQL read-only transaction are applied regardless
|
|
30
|
+
of grants. If a least-privilege account is available, the following is suggested
|
|
31
|
+
DDL for a DBA to review and execute. The MCP server does not execute privilege
|
|
32
|
+
changes:
|
|
33
|
+
|
|
34
|
+
```sql
|
|
35
|
+
CREATE USER 'mcp_reader'@'approved_source'
|
|
36
|
+
IDENTIFIED BY 'secret-manager-generated-password'
|
|
37
|
+
REQUIRE SSL;
|
|
38
|
+
|
|
39
|
+
GRANT SELECT, SHOW VIEW ON `application_database`.*
|
|
40
|
+
TO 'mcp_reader'@'approved_source';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Prefer not to grant `INSERT`, `UPDATE`, `DELETE`, `CREATE`, `ALTER`, `DROP`, `FILE`,
|
|
44
|
+
`PROCESS`, `EXECUTE`, `CREATE ROUTINE`, `EVENT`, `TRIGGER`, `LOCK TABLES` or
|
|
45
|
+
global privileges. `check_connection` returns a `read_only_assessment`, reports
|
|
46
|
+
any non-read privileges it finds, and continues operating in strict read-only
|
|
47
|
+
mode.
|
|
48
|
+
|
|
49
|
+
## Production Profile
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
[connections.prod]
|
|
53
|
+
host = "prod-read.example.internal"
|
|
54
|
+
port = 3306
|
|
55
|
+
user = "mcp_reader"
|
|
56
|
+
password_env = "MYSQL_PROD_PASSWORD"
|
|
57
|
+
database = "application_database"
|
|
58
|
+
allowed_databases = ["application_database"]
|
|
59
|
+
# Unrecognized stored functions/UDFs are blocked. This list should normally
|
|
60
|
+
# remain empty. Add only reviewed deterministic functions with no side effects.
|
|
61
|
+
allowed_functions = []
|
|
62
|
+
mask_columns = ["password", "passwd", "*secret*", "*token*", "*api_key*", "*private_key*", "ssn", "id_card", "phone", "email"]
|
|
63
|
+
|
|
64
|
+
ssl_mode = "VERIFY_IDENTITY"
|
|
65
|
+
ssl_ca = "/etc/mysql-mcp/company-ca.pem"
|
|
66
|
+
query_timeout_ms = 15000
|
|
67
|
+
max_rows = 200
|
|
68
|
+
max_cell_length = 20000
|
|
69
|
+
pool_size = 5
|
|
70
|
+
result_format = "json"
|
|
71
|
+
|
|
72
|
+
audit_enabled = true
|
|
73
|
+
audit_log_file = "/var/log/mysql-mcp/audit.jsonl"
|
|
74
|
+
audit_log_max_bytes = 10000000
|
|
75
|
+
audit_log_backup_count = 20
|
|
76
|
+
audit_hmac_key_env = "MYSQL_MCP_AUDIT_SIGNING_KEY"
|
|
77
|
+
audit_required_context = ["actor", "purpose", "ticket_id"]
|
|
78
|
+
audit_fail_closed = true
|
|
79
|
+
audit_fsync = true
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
All database tools accept:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"audit_context": {
|
|
87
|
+
"actor": "reporting-service",
|
|
88
|
+
"purpose": "monthly reconciliation",
|
|
89
|
+
"ticket_id": "FIN-2026-042"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The server rejects a request missing a profile-required field before opening a
|
|
95
|
+
database connection. Context values have length and control-character checks.
|
|
96
|
+
Because MCP resource reads cannot carry tool arguments, profiles with required
|
|
97
|
+
audit context must be accessed through the database tools; resource reads are
|
|
98
|
+
intentionally denied instead of bypassing attribution policy.
|
|
99
|
+
|
|
100
|
+
## Write-Capable Credential Model
|
|
101
|
+
|
|
102
|
+
The account privilege assessment is advisory and never weakens query policy.
|
|
103
|
+
With a write-capable or administrative account:
|
|
104
|
+
|
|
105
|
+
- DML, DDL, transaction control, assignment, multiple statements, file output,
|
|
106
|
+
system-schema access and locking reads are still rejected before connection.
|
|
107
|
+
- Every connector session uses `autocommit=false`, sets the transaction
|
|
108
|
+
read-only and starts `START TRANSACTION READ ONLY`.
|
|
109
|
+
- Completion always rolls back; cancellation or truncated results close the
|
|
110
|
+
socket so the server rolls back.
|
|
111
|
+
- Unknown stored functions and native UDFs are rejected because a function call
|
|
112
|
+
inside `SELECT` may write data or invoke external side effects.
|
|
113
|
+
- Known lock, wait, file, sequence and session-mutating functions remain blocked.
|
|
114
|
+
|
|
115
|
+
`allowed_functions` is an explicit escape hatch for a reviewed deterministic
|
|
116
|
+
function. Adding a function moves that function into the organization's trusted
|
|
117
|
+
computing boundary; do not allow a name whose implementation can be changed by
|
|
118
|
+
the same write-capable account.
|
|
119
|
+
|
|
120
|
+
## Audit Event
|
|
121
|
+
|
|
122
|
+
Each line is canonical JSON containing:
|
|
123
|
+
|
|
124
|
+
- `schema_version`, `timestamp`, `event_id`, `request_id`
|
|
125
|
+
- `operation`, optional MCP client metadata and supplied audit context
|
|
126
|
+
- `connection`, `database`, `query_type`, `query_id`
|
|
127
|
+
- `policy`, `internal`, `read_only_enforced`
|
|
128
|
+
- `status`, `duration_ms`, `row_count`, `truncated`, optional `error_type`
|
|
129
|
+
- optional `signature_algorithm` and `signature`
|
|
130
|
+
|
|
131
|
+
The query ID is a non-reversible fingerprint with literals removed. SQL text,
|
|
132
|
+
result rows, credentials, hosts, usernames and private-key paths are excluded.
|
|
133
|
+
Denied SQL receives an audit event even though it never reaches MySQL.
|
|
134
|
+
In fail-closed mode, the server first persists and fsyncs a `started` event
|
|
135
|
+
before opening the database connection, then writes a terminal outcome. Alert
|
|
136
|
+
on unmatched started events and preserve them during incident review.
|
|
137
|
+
|
|
138
|
+
Result masking is a final egress control, not an authorization boundary. Output
|
|
139
|
+
names matching `mask_columns` are redacted, and any sensitive source column
|
|
140
|
+
found through an alias, expression, subquery, or CTE causes conservative
|
|
141
|
+
redaction. Validate patterns with representative queries and prefer restricted
|
|
142
|
+
views or column grants for fields that must never be exposed.
|
|
143
|
+
|
|
144
|
+
When HMAC signing is enabled, verify a line by removing `signature`, serializing
|
|
145
|
+
the remaining object as UTF-8 JSON with sorted keys and compact separators, and
|
|
146
|
+
computing HMAC-SHA256 with the signing key. Rotation protects disk usage; an
|
|
147
|
+
external append-only log service is still required to detect deletion of whole
|
|
148
|
+
files.
|
|
149
|
+
|
|
150
|
+
`actor`, `purpose`, `ticket_id` and MCP client metadata are caller assertions.
|
|
151
|
+
For strong human identity, an authenticated gateway must bind its verified
|
|
152
|
+
principal to the MCP request and protect access to the MCP endpoint.
|
|
153
|
+
|
|
154
|
+
## SSE Deployment
|
|
155
|
+
|
|
156
|
+
The safe default is:
|
|
157
|
+
|
|
158
|
+
```dotenv
|
|
159
|
+
MCP_TRANSPORT=sse
|
|
160
|
+
MCP_SSE_HOST=127.0.0.1
|
|
161
|
+
MCP_SSE_PORT=8000
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
For built-in bearer authentication:
|
|
165
|
+
|
|
166
|
+
```dotenv
|
|
167
|
+
MCP_SSE_BEARER_TOKEN=<secret-manager-value-of-at-least-32-characters>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The token protects both `/sse` and `/messages/`; `/` remains a non-sensitive
|
|
171
|
+
health endpoint. For a reverse proxy, keep the MCP on loopback. A container that
|
|
172
|
+
must bind `0.0.0.0` may set `MCP_SSE_TRUST_PROXY_AUTH=true` only when network
|
|
173
|
+
policy prevents direct access to the container port.
|
|
174
|
+
|
|
175
|
+
## Monitoring
|
|
176
|
+
|
|
177
|
+
Recommended alerts:
|
|
178
|
+
|
|
179
|
+
- any `policy=denied`
|
|
180
|
+
- any `status` of `timeout`, `cancelled`, or `error`
|
|
181
|
+
- an audit sink/signing failure
|
|
182
|
+
- `check_connection.read_only_assessment.account_select_only=false`
|
|
183
|
+
- repeated queries approaching the timeout or row cap
|
|
184
|
+
- unexpected profile, database, client, actor or ticket values
|
|
185
|
+
|
|
186
|
+
Retain audit logs according to organizational and regulatory policy. Restrict
|
|
187
|
+
file permissions to the MCP service account and log collector; keep HMAC keys
|
|
188
|
+
outside the log host when possible.
|
|
189
|
+
|
|
190
|
+
## Acceptance Evidence
|
|
191
|
+
|
|
192
|
+
The 0.7.0 implementation was tested against a real external MySQL
|
|
193
|
+
`8.0.46-0ubuntu0.22.04.3` instance whose account had non-read privileges. Only
|
|
194
|
+
read-only operations were sent:
|
|
195
|
+
|
|
196
|
+
- configuration validation, connection health and grants inspection
|
|
197
|
+
- allowed-database discovery and system-schema filtering
|
|
198
|
+
- table/schema metadata reads
|
|
199
|
+
- normal query execution and pagination
|
|
200
|
+
- pre-connection rejection of DML, DDL, multiple statements, locking reads and
|
|
201
|
+
system-schema access
|
|
202
|
+
- durable JSONL events, required context, HMAC signatures and denial auditing
|
|
203
|
+
- two-phase fail-closed audit records persisted before database connection
|
|
204
|
+
- output/source-column masking, including alias and CTE cases
|
|
205
|
+
- absence of SQL text, database host and credentials from audit records
|
|
206
|
+
- encrypted transport verification (`TLS_AES_128_GCM_SHA256` on the tested
|
|
207
|
+
connection)
|
|
208
|
+
|
|
209
|
+
No write or DDL statement was sent to the database during acceptance testing.
|
|
210
|
+
Timeout/cancellation socket termination remains covered by deterministic
|
|
211
|
+
automated tests rather than an intentionally expensive production query.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Dana K. Williams
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
[](https://github.com/12zhangyan/mysql_mcp_server/actions)
|
|
2
|
+
[](https://pypi.org/project/mysql-mcp-server/)
|
|
3
|
+
[](https://www.agentaudit.dev/packages/mysql-mcp-server)
|
|
4
|
+
# MySQL MCP Server
|
|
5
|
+
A Model Context Protocol (MCP) implementation that enables secure interaction with MySQL databases. This server component facilitates communication between AI applications (hosts/clients) and MySQL databases, making database exploration and analysis safer and more structured through a controlled interface.
|
|
6
|
+
|
|
7
|
+
> **Note**: MySQL MCP Server supports both standard input/output (STDIO) and Streamable HTTP (SSE) transport modes. The SSE mode is recommended for remote/self-hosted deployments.
|
|
8
|
+
|
|
9
|
+
## Deployment options
|
|
10
|
+
- **Hosted** — [Fronteir AI](https://fronteir.ai/mcp/designcomputer-mysql-mcp-server) runs the server for you; no local setup required.
|
|
11
|
+
- **Local** — [Smithery](https://smithery.ai/server/designcomputer/mysql-mcp-server) installs and runs the server on your own machine.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
- **Named connection profiles** for dev/test/staging/prod and multiple servers
|
|
15
|
+
- Select a `connection` and `database` independently on every tool call
|
|
16
|
+
- **Strictly read-only SQL** with fail-closed validation, a MySQL read-only transaction, and unconditional rollback
|
|
17
|
+
- Discover connections, databases, tables, schemas, and sample rows
|
|
18
|
+
- Return-size protection (`MYSQL_MAX_ROWS`, hard maximum 1000)
|
|
19
|
+
- Legacy single-connection `MYSQL_*` configuration remains supported
|
|
20
|
+
- **SSE/HTTP transport support** (`MCP_TRANSPORT=sse`)
|
|
21
|
+
- Per-profile SSL/TLS and SSH tunneling
|
|
22
|
+
- Passwords can stay in environment variables instead of the profiles file
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### npm / npx
|
|
27
|
+
|
|
28
|
+
The npm package contains the matching Python wheel and exposes a cross-platform
|
|
29
|
+
launcher:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx -y @yanzhang123/readonly-db-mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Python 3.11 or newer is required. On first use, the launcher creates a
|
|
36
|
+
versioned virtual environment in the user cache and installs the bundled wheel
|
|
37
|
+
plus exact Python dependencies from a version-controlled SHA-256 lock file.
|
|
38
|
+
Downloads use the configured pip index and the completed environment is cached
|
|
39
|
+
by wheel-and-lock fingerprint. Override Python with `MYSQL_MCP_PYTHON` or the
|
|
40
|
+
cache location with `MYSQL_MCP_NPM_CACHE_DIR`.
|
|
41
|
+
|
|
42
|
+
For MCP clients, use `npx` as the command and
|
|
43
|
+
`["-y", "@yanzhang123/readonly-db-mcp"]` as its arguments.
|
|
44
|
+
|
|
45
|
+
### Manual Installation
|
|
46
|
+
```bash
|
|
47
|
+
pip install mysql-mcp-server
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Installing via Smithery
|
|
51
|
+
To install MySQL MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/designcomputer/mysql-mcp-server):
|
|
52
|
+
```bash
|
|
53
|
+
npx -y @smithery/cli install designcomputer/mysql-mcp-server --client claude
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Installing via Claude Code CLI
|
|
57
|
+
```bash
|
|
58
|
+
claude mcp add --transport stdio designcomputer-mysql_mcp_server uvx mysql_mcp_server
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
### Recommended: named connections
|
|
64
|
+
|
|
65
|
+
Copy [`mysql-connections.example.toml`](mysql-connections.example.toml) to an ignored local file named `mysql-connections.toml`, then define all environments in one place:
|
|
66
|
+
|
|
67
|
+
```toml
|
|
68
|
+
default = "dev"
|
|
69
|
+
|
|
70
|
+
[connections.dev]
|
|
71
|
+
description = "Local development"
|
|
72
|
+
host = "127.0.0.1"
|
|
73
|
+
port = 3306
|
|
74
|
+
user = "readonly_user"
|
|
75
|
+
password_env = "MYSQL_DEV_PASSWORD"
|
|
76
|
+
database = "app_dev"
|
|
77
|
+
allowed_databases = ["app_dev", "analytics_dev"]
|
|
78
|
+
query_timeout_ms = 30000
|
|
79
|
+
max_rows = 500
|
|
80
|
+
max_cell_length = 20000
|
|
81
|
+
result_format = "json"
|
|
82
|
+
mask_columns = ["password", "passwd", "*secret*", "*token*", "*api_key*", "*private_key*", "ssn", "id_card", "phone", "email"]
|
|
83
|
+
pool_size = 3
|
|
84
|
+
audit_enabled = true
|
|
85
|
+
|
|
86
|
+
[connections.prod]
|
|
87
|
+
description = "Production read replica"
|
|
88
|
+
host = "prod-read.example.internal"
|
|
89
|
+
user = "readonly_user"
|
|
90
|
+
password_env = "MYSQL_PROD_PASSWORD"
|
|
91
|
+
database = "app"
|
|
92
|
+
allowed_databases = ["app", "reporting"]
|
|
93
|
+
allowed_functions = [] # reviewed deterministic UDF/stored-function names only
|
|
94
|
+
query_timeout_ms = 15000
|
|
95
|
+
max_rows = 200
|
|
96
|
+
result_format = "json"
|
|
97
|
+
mask_columns = ["password", "passwd", "*secret*", "*token*", "*api_key*", "*private_key*", "ssn", "id_card", "phone", "email"]
|
|
98
|
+
pool_size = 5
|
|
99
|
+
ssl_mode = "VERIFY_CA"
|
|
100
|
+
ssl_ca = "C:/certs/company-ca.pem"
|
|
101
|
+
|
|
102
|
+
# Enterprise audit controls
|
|
103
|
+
audit_enabled = true
|
|
104
|
+
audit_log_file = "C:/var/log/mysql-mcp/audit.jsonl"
|
|
105
|
+
audit_log_max_bytes = 10000000
|
|
106
|
+
audit_log_backup_count = 10
|
|
107
|
+
audit_hmac_key_env = "MYSQL_MCP_AUDIT_SIGNING_KEY"
|
|
108
|
+
audit_required_context = ["actor", "purpose", "ticket_id"]
|
|
109
|
+
audit_fail_closed = true
|
|
110
|
+
audit_fsync = true
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Point the MCP process at that file and supply only the secrets through environment variables:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
MYSQL_PROFILES_FILE=C:/absolute/path/mysql-connections.toml
|
|
117
|
+
MYSQL_DEV_PASSWORD=...
|
|
118
|
+
MYSQL_PROD_PASSWORD=...
|
|
119
|
+
MYSQL_MCP_AUDIT_SIGNING_KEY=... # secret-manager supplied; never commit
|
|
120
|
+
MYSQL_DEFAULT_CONNECTION=dev # optional; overrides `default` in TOML
|
|
121
|
+
MYSQL_MAX_ROWS=500 # optional; 1-1000, default 500
|
|
122
|
+
MYSQL_QUERY_TIMEOUT_MS=30000 # full-call timeout, 100-300000 ms
|
|
123
|
+
MYSQL_MAX_CELL_LENGTH=20000 # truncate oversized values
|
|
124
|
+
MYSQL_RESULT_FORMAT=json # csv or json
|
|
125
|
+
MYSQL_MASK_COLUMNS=password,passwd,*secret*,*token*,*api_key*,*private_key*,ssn,id_card,phone,email
|
|
126
|
+
MYSQL_POOL_SIZE=0 # legacy mode; named profiles default to 5
|
|
127
|
+
MYSQL_ALLOWED_DATABASES=app,reporting
|
|
128
|
+
MYSQL_ALLOWED_FUNCTIONS= # optional reviewed function names
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`password_env` is recommended. A `password` field is accepted for local-only setups, but `mysql-connections.toml` is gitignored because it may contain secrets. Use an absolute `MYSQL_PROFILES_FILE` path in desktop MCP clients because their working directory is not guaranteed.
|
|
132
|
+
|
|
133
|
+
Each tool call can now target an environment without restarting the server:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"connection": "prod",
|
|
138
|
+
"database": "app",
|
|
139
|
+
"query": "SELECT COUNT(*) FROM orders",
|
|
140
|
+
"audit_context": {
|
|
141
|
+
"actor": "reporting-service",
|
|
142
|
+
"purpose": "month-end reconciliation",
|
|
143
|
+
"ticket_id": "FIN-2026-042"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
There is deliberately no process-global “switch” tool: explicit per-call selection prevents one concurrent MCP client from silently changing another client's active environment.
|
|
149
|
+
|
|
150
|
+
The profiles file is hot-reloaded when its size or modification time changes.
|
|
151
|
+
One invalid profile is reported by `validate_connections` without disabling other
|
|
152
|
+
valid profiles. Password environment variables are resolved only when their
|
|
153
|
+
profile is used, so an unavailable production credential does not break local
|
|
154
|
+
development.
|
|
155
|
+
|
|
156
|
+
### Legacy single connection
|
|
157
|
+
|
|
158
|
+
If `MYSQL_PROFILES_FILE` is not set, the original environment variables still work:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
MYSQL_HOST=localhost # Database host
|
|
162
|
+
MYSQL_PORT=3306 # Optional: Database port (defaults to 3306 if not specified)
|
|
163
|
+
MYSQL_USER=readonly_user
|
|
164
|
+
MYSQL_PASSWORD=your_password
|
|
165
|
+
MYSQL_DATABASE=your_database # Optional: Omit for multi-database mode
|
|
166
|
+
|
|
167
|
+
# Advanced Configuration
|
|
168
|
+
MYSQL_SSL_MODE=REQUIRED # DISABLED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY
|
|
169
|
+
MYSQL_SSL_CA= # required for VERIFY_CA / VERIFY_IDENTITY
|
|
170
|
+
MYSQL_CONNECT_TIMEOUT=10 # Timeout in seconds
|
|
171
|
+
MYSQL_QUERY_TIMEOUT_MS=30000
|
|
172
|
+
MYSQL_MAX_ROWS=500
|
|
173
|
+
MYSQL_MAX_CELL_LENGTH=20000
|
|
174
|
+
MYSQL_RESULT_FORMAT=json
|
|
175
|
+
MYSQL_POOL_SIZE=0
|
|
176
|
+
MYSQL_AUDIT_ENABLED=true
|
|
177
|
+
MYSQL_AUDIT_LOG_FILE=C:/var/log/mysql-mcp/audit.jsonl
|
|
178
|
+
MYSQL_AUDIT_LOG_MAX_BYTES=10000000
|
|
179
|
+
MYSQL_AUDIT_LOG_BACKUP_COUNT=10
|
|
180
|
+
MYSQL_AUDIT_HMAC_KEY_ENV=MYSQL_MCP_AUDIT_SIGNING_KEY
|
|
181
|
+
MYSQL_MCP_AUDIT_SIGNING_KEY=replace-with-secret-manager-value
|
|
182
|
+
MYSQL_AUDIT_REQUIRED_CONTEXT=actor,purpose,ticket_id
|
|
183
|
+
MYSQL_AUDIT_FAIL_CLOSED=true
|
|
184
|
+
MYSQL_AUDIT_FSYNC=true
|
|
185
|
+
MYSQL_ALLOWED_DATABASES=your_database
|
|
186
|
+
|
|
187
|
+
# Connection behaviour (Optional)
|
|
188
|
+
MYSQL_SQL_MODE=TRADITIONAL # SQL mode applied to the connection (default: TRADITIONAL)
|
|
189
|
+
|
|
190
|
+
# Compatibility (Optional)
|
|
191
|
+
MYSQL_CHARSET=utf8mb4
|
|
192
|
+
MYSQL_COLLATION=utf8mb4_unicode_ci
|
|
193
|
+
MYSQL_AUTH_PLUGIN= # e.g., mysql_native_password for older MySQL versions
|
|
194
|
+
MYSQL_USE_PURE=false # Force the pure-Python connector (default: false)
|
|
195
|
+
MYSQL_RAISE_ON_WARNINGS=false # Raise on SQL warnings (default: false)
|
|
196
|
+
|
|
197
|
+
# SSE Transport (Optional)
|
|
198
|
+
MCP_TRANSPORT=stdio # stdio or sse
|
|
199
|
+
MCP_SSE_HOST=127.0.0.1 # Safe default: loopback only
|
|
200
|
+
PORT=8000 # HTTP port (fallback for MCP_SSE_PORT)
|
|
201
|
+
MCP_SSE_ALLOWED_HOSTS= # Comma-separated allowed Host headers (default: localhost:{port},127.0.0.1:{port})
|
|
202
|
+
MCP_SSE_BEARER_TOKEN= # Optional; at least 32 characters
|
|
203
|
+
# Set only when an authenticated reverse proxy is the sole network entry point:
|
|
204
|
+
MCP_SSE_TRUST_PROXY_AUTH=false
|
|
205
|
+
|
|
206
|
+
# SSH Tunneling (Optional)
|
|
207
|
+
MYSQL_SSH_ENABLE=false # Set to true to enable
|
|
208
|
+
MYSQL_SSH_HOST= # SSH jump host
|
|
209
|
+
MYSQL_SSH_PORT=22 # SSH port
|
|
210
|
+
MYSQL_SSH_USER= # SSH username
|
|
211
|
+
MYSQL_SSH_KEY_PATH= # Path to SSH private key
|
|
212
|
+
MYSQL_SSH_REMOTE_HOST=localhost # Host from the perspective of the jump host
|
|
213
|
+
MYSQL_SSH_REMOTE_PORT=3306
|
|
214
|
+
MYSQL_LOCAL_PORT=0 # 0 selects a free port; the tunnel is reused
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### `.env` file loading
|
|
218
|
+
|
|
219
|
+
On startup the server automatically loads a `.env` file via `python-dotenv`, so for local use you can simply:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
cp .env.example .env # then edit with your credentials
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The file is read from the **process working directory** (and parent directories), which works when you run the server yourself from the project folder.
|
|
226
|
+
|
|
227
|
+
> ⚠️ **Claude Code / Claude Desktop:** these hosts launch the server from their own working directory, so the project's `.env` will **not** be found and you'll see `Missing required database configuration`. Put your `MYSQL_*` values in the `env` block of the MCP config (shown in the Usage section below) rather than relying on `.env`.
|
|
228
|
+
|
|
229
|
+
### Multi-database mode
|
|
230
|
+
|
|
231
|
+
When `MYSQL_DATABASE` is not set, the server operates in multi-database mode:
|
|
232
|
+
- `list_resources` returns all user databases (system databases are filtered out)
|
|
233
|
+
- Pass `database` to any data tool, or use fully qualified names such as `mydb.mytable`
|
|
234
|
+
- `USE` and multiple statements are intentionally blocked
|
|
235
|
+
|
|
236
|
+
## Available Tools
|
|
237
|
+
|
|
238
|
+
All tools are declared with `readOnlyHint=true` and `destructiveHint=false`.
|
|
239
|
+
|
|
240
|
+
### `list_connections`
|
|
241
|
+
Lists named profiles, default database, policy limits and readiness. It never returns hosts, usernames, passwords, or SSH key paths.
|
|
242
|
+
|
|
243
|
+
### `validate_connections`
|
|
244
|
+
Forces a configuration reload and reports valid/invalid profiles plus missing
|
|
245
|
+
password environment variables without opening database connections.
|
|
246
|
+
|
|
247
|
+
### `check_connection`
|
|
248
|
+
Runs `SELECT`/`SHOW GRANTS` health checks and returns MySQL version, current
|
|
249
|
+
database, an irreversible account fingerprint, global read-only state, grant
|
|
250
|
+
count, latency and active profile policy. Raw usernames and grant statements are
|
|
251
|
+
not returned. It classifies non-read privileges and warns when the account does
|
|
252
|
+
not provide read-only defense in depth.
|
|
253
|
+
|
|
254
|
+
### `list_databases`
|
|
255
|
+
Lists accessible non-system databases.
|
|
256
|
+
- **Arguments:** `connection` (optional)
|
|
257
|
+
|
|
258
|
+
### `list_tables`
|
|
259
|
+
Lists tables and views in a database.
|
|
260
|
+
- **Arguments:** `connection`, `database` (both optional)
|
|
261
|
+
|
|
262
|
+
### `execute_sql`
|
|
263
|
+
Executes exactly one read-only statement.
|
|
264
|
+
- **Arguments:** `query` (required); `connection`, `database`, `max_rows`, `offset`, `timeout_ms`, `result_format`, `audit_context` (optional unless required by the profile)
|
|
265
|
+
- **Allowed statement families:** `SELECT`, `WITH`, `SHOW`, `DESCRIBE`, `DESC`, `EXPLAIN`, `TABLE`
|
|
266
|
+
- **Always blocked:** DML, DDL, `USE`, transaction control, locks, `SELECT ... INTO`, session-variable assignment, MySQL executable comments, and multiple statements
|
|
267
|
+
- **Function policy:** unresolved stored functions/UDFs are blocked by default because `SELECT function(...)` can hide database or external side effects. `allowed_functions` is an explicit reviewed-function exception.
|
|
268
|
+
- **Defense in depth:** validation occurs before connecting; MySQL then executes inside `START TRANSACTION READ ONLY`. Completed calls roll back; truncated/cancelled calls close the socket so MySQL rolls back and stops producing unread rows.
|
|
269
|
+
- **Cross-database:** use the `database` argument or a qualified `database.table` name
|
|
270
|
+
- **Timeout/cancellation:** profile timeout, Connector socket timeout and MySQL/MariaDB statement timeout are combined. MCP cancellation closes the active socket; no `KILL` privilege is required.
|
|
271
|
+
- **Pagination:** JSON output includes `truncated` and `next_offset`; pass that value back as `offset`.
|
|
272
|
+
- **Formats:** `json` preserves structured rows and metadata; `csv` uses RFC-style quoting and explicit `NULL`.
|
|
273
|
+
- **Attribution:** `audit_context` supports `actor`, `purpose`, and `ticket_id`. A profile can require any of these fields before a connection is opened.
|
|
274
|
+
|
|
275
|
+
### `get_schema_info`
|
|
276
|
+
Provides detailed metadata about database structures.
|
|
277
|
+
- **Arguments:** `table_name`, `connection`, `database` (all optional)
|
|
278
|
+
- **Output:** Column names, types, nullability, default values, and comments.
|
|
279
|
+
- **Cross-database:** Pass `database.table` to query a table outside `MYSQL_DATABASE`; bare names use the configured database.
|
|
280
|
+
- **Identifier rules:** Names must contain only alphanumeric characters, underscores, and `$` (dots are allowed as a separator between database and table names).
|
|
281
|
+
|
|
282
|
+
### `get_table_sample`
|
|
283
|
+
Fetches a representative sample of data.
|
|
284
|
+
- **Arguments:** `table_name` (required), `limit` (optional integer, max 100), `offset`, `connection`, `database`, `timeout_ms`, `result_format`
|
|
285
|
+
- **Use Case:** Quickly understand data formats and content without fetching large result sets.
|
|
286
|
+
- **Cross-database:** Pass `database.table` to sample a table outside `MYSQL_DATABASE`; bare names use the configured database.
|
|
287
|
+
- **Identifier rules:** Names must contain only alphanumeric characters, underscores, and `$` (dots are allowed as a separator between database and table names).
|
|
288
|
+
|
|
289
|
+
## Available Prompts
|
|
290
|
+
|
|
291
|
+
In addition to tools, the server exposes **MCP prompts** — guided, multi-step workflows that a client can launch on demand. In Claude Code they appear as slash commands (`/mcp__<server>__<prompt>`); in Claude Desktop they appear in the prompts (`+`) menu.
|
|
292
|
+
|
|
293
|
+
| Prompt | Arguments | Description |
|
|
294
|
+
| --- | --- | --- |
|
|
295
|
+
| `explore_database` | `connection`, `database` *(optional)* | Systematically explore the selected database: discover tables, inspect schemas, sample data, and summarize it. |
|
|
296
|
+
| `analyze_table` | `table_name` *(required)*; `connection`, `database` *(optional)* | Deep-dive into a specific table. Accepts `database.table` notation. |
|
|
297
|
+
|
|
298
|
+
**Example (Claude Code):**
|
|
299
|
+
```
|
|
300
|
+
/mcp__mysql__explore_database
|
|
301
|
+
/mcp__mysql__analyze_table customers
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Both prompts orchestrate the read-only discovery, schema, and sampling tools.
|
|
305
|
+
|
|
306
|
+
## Usage
|
|
307
|
+
### With Claude Desktop
|
|
308
|
+
Add this to your `claude_desktop_config.json`:
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"mcpServers": {
|
|
312
|
+
"mysql": {
|
|
313
|
+
"command": "uv",
|
|
314
|
+
"args": [
|
|
315
|
+
"--directory",
|
|
316
|
+
"path/to/mysql_mcp_server",
|
|
317
|
+
"run",
|
|
318
|
+
"mysql_mcp_server"
|
|
319
|
+
],
|
|
320
|
+
"env": {
|
|
321
|
+
"MYSQL_PROFILES_FILE": "C:/absolute/path/mysql-connections.toml",
|
|
322
|
+
"MYSQL_DEV_PASSWORD": "your_dev_password",
|
|
323
|
+
"MYSQL_PROD_PASSWORD": "your_prod_password"
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
For more detailed examples and agent-specific guidance, see [MCP_USECASES.md](MCP_USECASES.md).
|
|
331
|
+
|
|
332
|
+
### With Visual Studio Code
|
|
333
|
+
Add this to your `mcp.json`:
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"mcpServers": {
|
|
337
|
+
"mysql": {
|
|
338
|
+
"type": "stdio",
|
|
339
|
+
"command": "uvx",
|
|
340
|
+
"args": [
|
|
341
|
+
"--from",
|
|
342
|
+
"mysql-mcp-server",
|
|
343
|
+
"mysql_mcp_server"
|
|
344
|
+
],
|
|
345
|
+
"env": {
|
|
346
|
+
"MYSQL_PROFILES_FILE": "C:/absolute/path/mysql-connections.toml",
|
|
347
|
+
"MYSQL_DEV_PASSWORD": "your_dev_password",
|
|
348
|
+
"MYSQL_PROD_PASSWORD": "your_prod_password"
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
Note: Will need to install uv for this to work
|
|
355
|
+
|
|
356
|
+
### Debugging with MCP Inspector
|
|
357
|
+
While MySQL MCP Server isn't intended to be run standalone or directly from the command line with Python, you can use the MCP Inspector to debug it.
|
|
358
|
+
|
|
359
|
+
The MCP Inspector provides a convenient way to test and debug your MCP implementation:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
# Install dependencies
|
|
363
|
+
pip install -r requirements.txt
|
|
364
|
+
# Use the MCP Inspector for debugging (do not run directly with Python)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
The MySQL MCP Server is designed to be integrated with AI applications like Claude Desktop and should not be run directly as a standalone Python program.
|
|
368
|
+
|
|
369
|
+
## Development
|
|
370
|
+
```bash
|
|
371
|
+
# Clone the repository
|
|
372
|
+
git clone https://github.com/12zhangyan/mysql_mcp_server.git
|
|
373
|
+
cd mysql_mcp_server
|
|
374
|
+
# Create virtual environment
|
|
375
|
+
python -m venv venv
|
|
376
|
+
source venv/bin/activate # or `venv\Scripts\activate` on Windows
|
|
377
|
+
# Install development dependencies
|
|
378
|
+
pip install -r requirements-dev.txt
|
|
379
|
+
# Copy the example config and edit with your credentials
|
|
380
|
+
cp .env.example .env
|
|
381
|
+
# Edit .env with your MySQL connection details
|
|
382
|
+
# Run tests
|
|
383
|
+
pytest
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Security Considerations
|
|
387
|
+
- **Read-only SQL gate:** Only result-producing read statements are accepted; write/DDL/transaction/locking constructs are rejected before a connection is opened.
|
|
388
|
+
- **Database enforcement:** Every exposed query runs with `autocommit=false` in a read-only transaction. Normal completion rolls back; cancellation or truncation closes the socket and forces server-side rollback.
|
|
389
|
+
- **Write-capable credentials:** Read-only behavior does not depend on account grants. Unrecognized stored functions/UDFs, side-effecting functions, sequence advancement and locking reads are rejected because they can hide effects inside `SELECT`.
|
|
390
|
+
- **Database allowlist:** `allowed_databases` is enforced with a MySQL AST parser for tools and resource URIs; system schemas are blocked by default.
|
|
391
|
+
- **Resource limits:** Calls have full-operation, socket and server statement timeouts; at most 1000 rows are returned and oversized cells are truncated.
|
|
392
|
+
- **Cancellation:** Cancelling the MCP request closes the active connector socket. The worker does not continue silently after the response is abandoned.
|
|
393
|
+
- **Enterprise audit:** Versioned UTC JSONL events include an event ID, MCP request ID, operation, caller-supplied attribution, policy decision, target database, literal-free query fingerprint, duration, result size and outcome. SQL text and result data are never logged. Optional rotation, fsync, HMAC signatures, required context and fail-closed behavior are supported.
|
|
394
|
+
- **Two-phase audit:** In fail-closed mode, an fsynced `started` event is persisted before opening a database connection, followed by the terminal outcome event.
|
|
395
|
+
- **Result masking:** Sensitive output names are redacted by default. Queries whose expression tree references a sensitive source column are conservatively redacted even when aliases or CTEs hide the original name. Review `mask_columns` for each schema; setting it to an empty list explicitly disables masking.
|
|
396
|
+
- **Encrypted by default:** Database TLS defaults to `REQUIRED` and the connected session is rejected if the connector reports plaintext. Production profiles should use `VERIFY_CA` or `VERIFY_IDENTITY` with `ssl_ca`.
|
|
397
|
+
- **Parser hardening:** MySQL `/*!...*/` and MariaDB `/*M!...*/` executable comments are rejected, and server-wide `SHOW` variants are blocked unless they can be scoped to an allowed database.
|
|
398
|
+
- **Identifier Validation:** Table and database names passed to `get_schema_info` and `get_table_sample` are validated against a strict whitelist (alphanumeric, underscore, and `$` only; a single dot is allowed as a `database.table` separator). Other special characters are rejected to prevent SQL injection.
|
|
399
|
+
- **Encrypted Access:** Full support for SSL/TLS and SSH Tunneling for secure remote connections.
|
|
400
|
+
- **Log Privacy:** SQL text, passwords, hosts, usernames and SSH private-key paths are not included in tool discovery or audit events.
|
|
401
|
+
- **Diagnostic Privacy:** Connection diagnostics expose an account fingerprint and privilege summary, not raw usernames or `SHOW GRANTS`; configuration paths and database-supplied error text are redacted.
|
|
402
|
+
- **Least Privilege:** A dedicated `SELECT` account is recommended as additional defense, but it is not assumed by the MCP read-only enforcement. `check_connection` warns about broader grants without disabling the profile.
|
|
403
|
+
- **SSE is loopback-only by default.** Configure `MCP_SSE_BEARER_TOKEN` with at least 32 characters to enable built-in bearer authentication. A non-loopback bind is refused unless bearer authentication is enabled or `MCP_SSE_TRUST_PROXY_AUTH=true` explicitly confirms that an authenticated reverse proxy is the only network entry point. Example with nginx and HTTP Basic Auth:
|
|
404
|
+
|
|
405
|
+
```nginx
|
|
406
|
+
location /sse {
|
|
407
|
+
auth_basic "MCP";
|
|
408
|
+
auth_basic_user_file /etc/nginx/.htpasswd;
|
|
409
|
+
proxy_pass http://127.0.0.1:8000;
|
|
410
|
+
proxy_set_header Host $host;
|
|
411
|
+
proxy_buffering off;
|
|
412
|
+
}
|
|
413
|
+
location /messages/ {
|
|
414
|
+
auth_basic "MCP";
|
|
415
|
+
auth_basic_user_file /etc/nginx/.htpasswd;
|
|
416
|
+
proxy_pass http://127.0.0.1:8000;
|
|
417
|
+
proxy_set_header Host $host;
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Keep `MCP_SSE_HOST=127.0.0.1` so the proxy is the sole public entry point. If a container requires `0.0.0.0`, set `MCP_SSE_TRUST_PROXY_AUTH=true` only after network policy prevents direct access. Set `MCP_SSE_ALLOWED_HOSTS` to the hostname forwarded by the proxy.
|
|
422
|
+
|
|
423
|
+
See [SECURITY.md](SECURITY.md) and [ENTERPRISE_DEPLOYMENT.md](ENTERPRISE_DEPLOYMENT.md) for deployment and audit guidance.
|
|
424
|
+
Maintainers should follow [RELEASING.md](RELEASING.md) for synchronized Python/npm version releases.
|
|
425
|
+
|
|
426
|
+
## Security Best Practices
|
|
427
|
+
This MCP implementation requires database access to function. For security:
|
|
428
|
+
1. **Create a dedicated MySQL user** with `SELECT` only
|
|
429
|
+
2. **Never use root credentials** or administrative accounts
|
|
430
|
+
3. **Restrict database access** to only necessary operations
|
|
431
|
+
4. **Enable logging** for audit purposes
|
|
432
|
+
5. **Regular security reviews** of database access
|
|
433
|
+
|
|
434
|
+
See [MySQL Security Configuration Guide](https://github.com/12zhangyan/mysql_mcp_server/blob/main/SECURITY.md) for detailed instructions on:
|
|
435
|
+
- Creating a restricted MySQL user
|
|
436
|
+
- Setting appropriate permissions
|
|
437
|
+
- Monitoring database access
|
|
438
|
+
- Security best practices
|
|
439
|
+
|
|
440
|
+
⚠️ IMPORTANT: Always follow the principle of least privilege when configuring database access.
|
|
441
|
+
|
|
442
|
+
## License
|
|
443
|
+
MIT License - see LICENSE file for details.
|
|
444
|
+
|
|
445
|
+
## Contributing
|
|
446
|
+
1. Fork the repository
|
|
447
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
448
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
449
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
450
|
+
5. Open a Pull Request
|