@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.
@@ -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
+ [![Tests](https://github.com/12zhangyan/mysql_mcp_server/actions/workflows/test.yml/badge.svg)](https://github.com/12zhangyan/mysql_mcp_server/actions)
2
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/mysql-mcp-server)](https://pypi.org/project/mysql-mcp-server/)
3
+ [![AgentAudit Safe](https://img.shields.io/badge/AgentAudit-safe-brightgreen)](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