@futuretea/mysql-mcp-server 0.0.2 → 0.2.0
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/README.md +96 -13
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -13,8 +13,12 @@ support writes, administration, backups, replication, or dynamic targets.
|
|
|
13
13
|
make build
|
|
14
14
|
./bin/mysql-mcp-server version
|
|
15
15
|
|
|
16
|
-
# Set
|
|
17
|
-
export
|
|
16
|
+
# Set these only in the runtime environment. Do not commit the password.
|
|
17
|
+
export MYSQL_MCP_HOST='127.0.0.1'
|
|
18
|
+
export MYSQL_MCP_DB_PORT='3306'
|
|
19
|
+
export MYSQL_MCP_USERNAME='readonly'
|
|
20
|
+
export MYSQL_MCP_PASSWORD='<runtime-secret>'
|
|
21
|
+
export MYSQL_MCP_DATABASE='inventory'
|
|
18
22
|
|
|
19
23
|
# Start MCP server (stdio, default)
|
|
20
24
|
./bin/mysql-mcp-server mcp
|
|
@@ -52,8 +56,10 @@ blocked session/file functions. Accepted queries run in a read-only transaction
|
|
|
52
56
|
that is always rolled back.
|
|
53
57
|
|
|
54
58
|
This is not proof that stored functions, UDFs, views, or plugins have no side
|
|
55
|
-
effects. Use a least-privilege account
|
|
56
|
-
`
|
|
59
|
+
effects. Use a least-privilege account. Startup rejects `EXECUTE`, schema-wide `ALL`, `FILE`,
|
|
60
|
+
`SHOW_ROUTINE`, grants on the `mysql` schema, global `SELECT`, and other
|
|
61
|
+
administrative privileges unless `allow_unsafe_database_role` is set. See
|
|
62
|
+
[Service account](#service-account).
|
|
57
63
|
|
|
58
64
|
## CLI commands
|
|
59
65
|
|
|
@@ -92,7 +98,14 @@ Cursor / Claude Desktop style config:
|
|
|
92
98
|
"mcpServers": {
|
|
93
99
|
"mysql-mcp-server": {
|
|
94
100
|
"command": "/absolute/path/to/bin/mysql-mcp-server",
|
|
95
|
-
"args": ["mcp"]
|
|
101
|
+
"args": ["mcp"],
|
|
102
|
+
"env": {
|
|
103
|
+
"MYSQL_MCP_HOST": "127.0.0.1",
|
|
104
|
+
"MYSQL_MCP_DB_PORT": "3306",
|
|
105
|
+
"MYSQL_MCP_USERNAME": "readonly",
|
|
106
|
+
"MYSQL_MCP_PASSWORD": "<runtime-secret>",
|
|
107
|
+
"MYSQL_MCP_DATABASE": "inventory"
|
|
108
|
+
}
|
|
96
109
|
}
|
|
97
110
|
}
|
|
98
111
|
}
|
|
@@ -139,10 +152,16 @@ Same process as HTTP mode. Endpoints:
|
|
|
139
152
|
make docker
|
|
140
153
|
|
|
141
154
|
# Stdio (default ENTRYPOINT is `mysql-mcp-server mcp`)
|
|
142
|
-
docker run -i --rm
|
|
155
|
+
docker run -i --rm \
|
|
156
|
+
-e MYSQL_MCP_HOST -e MYSQL_MCP_DB_PORT -e MYSQL_MCP_USERNAME \
|
|
157
|
+
-e MYSQL_MCP_PASSWORD -e MYSQL_MCP_DATABASE \
|
|
158
|
+
ghcr.io/futuretea/mysql-mcp-server:dev
|
|
143
159
|
|
|
144
160
|
# HTTP
|
|
145
|
-
docker run --rm
|
|
161
|
+
docker run --rm \
|
|
162
|
+
-e MYSQL_MCP_HOST -e MYSQL_MCP_DB_PORT -e MYSQL_MCP_USERNAME \
|
|
163
|
+
-e MYSQL_MCP_PASSWORD -e MYSQL_MCP_DATABASE \
|
|
164
|
+
-p 8080:8080 ghcr.io/futuretea/mysql-mcp-server:dev --port 8080 --listen 0.0.0.0
|
|
146
165
|
```
|
|
147
166
|
|
|
148
167
|
HTTP / SSE have **no auth and no TLS**. Default `--listen 127.0.0.1`. Use only on trusted networks; put a reverse proxy in front if you expose the port.
|
|
@@ -155,11 +174,39 @@ Priority: **flags > environment variables > config file > defaults**.
|
|
|
155
174
|
|
|
156
175
|
| Variable | Description | Default |
|
|
157
176
|
|----------|-------------|---------|
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
177
|
+
| `MYSQL_MCP_LOG_LEVEL` | Log level | `info` |
|
|
178
|
+
| `MYSQL_MCP_PORT` | MCP HTTP port (`0` = stdio) | `0` |
|
|
179
|
+
| `MYSQL_MCP_LISTEN` | MCP HTTP listen host | `127.0.0.1` |
|
|
180
|
+
| `MYSQL_MCP_SSE_BASE_URL` | Public SSE base URL | `""` |
|
|
181
|
+
| `MYSQL_MCP_HOST` | MySQL TCP host | `127.0.0.1` |
|
|
182
|
+
| `MYSQL_MCP_DB_PORT` | MySQL TCP port; distinct from `MYSQL_MCP_PORT` | `3306` |
|
|
183
|
+
| `MYSQL_MCP_USERNAME` | Required MySQL username | none |
|
|
184
|
+
| `MYSQL_MCP_PASSWORD` | Optional MySQL password; never logged or returned | none |
|
|
185
|
+
| `MYSQL_MCP_DATABASE` | Optional default MySQL database | none |
|
|
186
|
+
| `MYSQL_MCP_ALLOW_UNSAFE_DATABASE_ROLE` | Skip only the startup unsafe-privilege rejection | `false` |
|
|
187
|
+
|
|
188
|
+
### Service account
|
|
189
|
+
|
|
190
|
+
After the process connects, and before any tool runs SQL, startup reads `SHOW GRANTS` for the current account. Plain `SHOW GRANTS` includes mandatory roles; `SHOW GRANTS FOR CURRENT_USER()` does not. Privileges inherited through those roles and through explicitly granted roles are expanded before any tool runs. The process exits with `[mysql_unsafe_service_role]` when the account has any of:
|
|
191
|
+
|
|
192
|
+
- `SUPER`, `FILE`, `PROCESS`, `SHUTDOWN`, `RELOAD`, `CREATE USER`, `CREATE TABLESPACE`, or `PROXY`
|
|
193
|
+
- `CREATE ROLE`, `DROP ROLE`, or `EXECUTE`
|
|
194
|
+
- `REPLICATION SLAVE` or `REPLICATION CLIENT`
|
|
195
|
+
- `SHOW_ROUTINE` or `RESOURCE_GROUP_USER`
|
|
196
|
+
- global `SELECT`, `ALL`, or `ALL PRIVILEGES` (`ON *.*`)
|
|
197
|
+
- schema-wide `ALL` or `ALL PRIVILEGES` (`ON db.*`). MySQL stores `EXECUTE` on that database privilege row and `SHOW GRANTS` keeps the shorthand, so the word `EXECUTE` never appears. `ALL` on one table (`ON db.table`) does not include `EXECUTE`
|
|
198
|
+
- any privilege on the `mysql` schema, including `SELECT` on `mysql.user`
|
|
199
|
+
- MySQL 8 dynamic privileges that replace `SUPER` or administer the server, including `SYSTEM_VARIABLES_ADMIN`, `SESSION_VARIABLES_ADMIN`, `CONNECTION_ADMIN`, `TP_CONNECTION_ADMIN`, `SYSTEM_USER`, `ROLE_ADMIN`, `BINLOG_ADMIN`, `REPLICATION_SLAVE_ADMIN`, `SET_USER_ID`, `ALLOW_NONEXISTENT_DEFINER`, `BACKUP_ADMIN`, `CLONE_ADMIN`, `FLUSH_PRIVILEGES`, `FLUSH_TABLES`, `FIREWALL_ADMIN`, `OPTIMIZE_LOCAL_TABLE`, and `VERSION_TOKEN_ADMIN`
|
|
200
|
+
|
|
201
|
+
`USAGE` plus `SELECT` on an application schema starts with the default configuration. No opt-out is required. `SELECT` on `*.*` does not start: that grant includes the `mysql` grant tables. An expanded database privilege list that does not include `EXECUTE` also starts. A routine call then fails in MySQL rather than at startup.
|
|
202
|
+
|
|
203
|
+
A view created by a DBA with `SQL SECURITY DEFINER` is published schema. An account that can `SELECT` the view can read its output, including a `mysql.user` count or a function result, without holding `EXECUTE`. Startup does not classify that body. For this account `VIEW_DEFINITION` is empty and `SHOW CREATE VIEW` is denied, so the check cannot tell a normal default-definer application view from one that publishes catalog data. Do not publish a definer view that exposes data the service account should not see. That boundary is not a substitute for rejecting `EXECUTE` or schema-wide `ALL`.
|
|
204
|
+
|
|
205
|
+
Startup allows 5 seconds for the TCP connection and the grant probe. A server that does not answer in that time stops startup. `allow_unsafe_database_role` does not extend the deadline.
|
|
206
|
+
|
|
207
|
+
`allow_unsafe_database_role` defaults to `false`. Set it to `true` in the config file, or export `MYSQL_MCP_ALLOW_UNSAFE_DATABASE_ROLE=true`, only when you cannot provision a least-privilege account. The server logs a warning and skips this privilege rejection. A refused connection, a failed grant probe, a startup timeout, and every other startup check still stop startup. Queries still run in a read-only transaction, but that transaction does not constrain `FILE`, `SHUTDOWN`, `SUPER`, replication privileges, `PROXY`, `EXECUTE`, or the dynamic privileges above. Enable the option only for a MySQL instance you control.
|
|
208
|
+
|
|
209
|
+
Stdio MCP and `tools call` keep logs at error level so protocol output stays intact. The opt-out warning is emitted at warn level and is visible in HTTP mode at the default `info` level.
|
|
163
210
|
|
|
164
211
|
### Config file
|
|
165
212
|
|
|
@@ -174,6 +221,7 @@ enabled_tools: []
|
|
|
174
221
|
disabled_tools: []
|
|
175
222
|
enabled_domains: []
|
|
176
223
|
disabled_domains: []
|
|
224
|
+
allow_unsafe_database_role: false
|
|
177
225
|
```
|
|
178
226
|
|
|
179
227
|
```bash
|
|
@@ -193,6 +241,34 @@ pkg/toolset/ # Toolset interface and filters
|
|
|
193
241
|
pkg/toolset/mysql/ # six MySQL tool definitions and shared parameter decoder
|
|
194
242
|
```
|
|
195
243
|
|
|
244
|
+
## Output masking
|
|
245
|
+
|
|
246
|
+
Masking is off when the `masking` object is absent, when `enabled` is omitted, or when `enabled` is false. Tool results then keep their current JSON. Set `enabled` to true and at least one rule to mask configured data. `MYSQL_MCP_MASKING_JSON` replaces the whole YAML object when the variable is present. An empty value is rejected. Unset leaves the YAML object in place.
|
|
247
|
+
|
|
248
|
+
```yaml
|
|
249
|
+
masking:
|
|
250
|
+
enabled: true
|
|
251
|
+
placeholder: "[masked]"
|
|
252
|
+
names: ["synthetic_field"]
|
|
253
|
+
name_patterns: ["token_.+"]
|
|
254
|
+
values: ["synthetic-token-value"]
|
|
255
|
+
value_patterns: ["sk_live_[0-9]+"]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
export MYSQL_MCP_MASKING_JSON='{"enabled":true,"names":["synthetic_field"],"values":["synthetic-token-value"]}'
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Name rules match a whole column, table, index, or database name, and a decoded JSON object key. The pattern is parsed before it is anchored, so a fragment such as `a)|(b` is rejected instead of matching a prefix or a suffix. A value pattern uses RE2 leftmost-first matching and does not look for a longer alternative. A matched string becomes the placeholder and keeps a MySQL `{"encoding","value"}` cell when the cell was text or base64. A matched number or other non-string becomes JSON `null`. An existing SQL `NULL` stays `null`. A JSON `null` in a masked result can therefore be either a database NULL or a redacted value, and the JSON type can change.
|
|
263
|
+
|
|
264
|
+
Value rules are substring matches on the original text, including one-character literals. They run once and do not search the placeholder. A pattern is rejected when any alternative can match without consuming a character, such as a word-boundary branch. JSON objects and arrays stored in utf8 cells are read as JSON, including escaped keys. A value rule that hits a JSON number or bool, or a MySQL integer or float cell, replaces that whole value with null and does not splice the digits. An unmatched number keeps its original text. Standard base64 cells are decoded, masked, and encoded again, including a value pattern that matches an ASCII secret next to invalid UTF-8. Ordinary prose that looks like base64 is not decoded. One result shares a node and output budget. This is not a detector for every private value, compressed payload, or encoding.
|
|
265
|
+
|
|
266
|
+
`schema_version` and the tool name are server constants and are not masked. Fixed public errors are not rewritten, so a short literal that also occurs in an error sentence or in the placeholder can still appear there. The guarantee is that the original configured data is removed from the covered result text.
|
|
267
|
+
|
|
268
|
+
Invalid masking configuration, including `enabled: true` with no rules, stops startup with `invalid masking config` and does not echo the document. A result that exceeds a bound, has duplicate JSON keys, has two masked names that collide, or has an unsupported cell fails with `[mysql_masking_failed] result was not returned`. The database command is unchanged.
|
|
269
|
+
|
|
270
|
+
HTTP debug logs record a route class (`mcp`, `sse`, `message`, or `other`) instead of the raw path or query. The masker does not write the source value to a log.
|
|
271
|
+
|
|
196
272
|
## Operational limits
|
|
197
273
|
|
|
198
274
|
This slice deliberately does not impose query timeouts, row limits, response
|
|
@@ -240,7 +316,14 @@ Configure an MCP client that supports stdio with npm:
|
|
|
240
316
|
"mcpServers": {
|
|
241
317
|
"mysql-mcp-server": {
|
|
242
318
|
"command": "npx",
|
|
243
|
-
"args": ["-y", "@futuretea/mysql-mcp-server@<version>"]
|
|
319
|
+
"args": ["-y", "@futuretea/mysql-mcp-server@<version>"],
|
|
320
|
+
"env": {
|
|
321
|
+
"MYSQL_MCP_HOST": "127.0.0.1",
|
|
322
|
+
"MYSQL_MCP_DB_PORT": "3306",
|
|
323
|
+
"MYSQL_MCP_USERNAME": "readonly",
|
|
324
|
+
"MYSQL_MCP_PASSWORD": "<runtime-secret>",
|
|
325
|
+
"MYSQL_MCP_DATABASE": "inventory"
|
|
326
|
+
}
|
|
244
327
|
}
|
|
245
328
|
}
|
|
246
329
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futuretea/mysql-mcp-server",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server binary launcher",
|
|
5
5
|
"main": "./bin/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mysql-mcp-server": "bin/index.js"
|
|
8
8
|
},
|
|
9
9
|
"optionalDependencies": {
|
|
10
|
-
"@futuretea/mysql-mcp-server-darwin-amd64": "0.0
|
|
11
|
-
"@futuretea/mysql-mcp-server-darwin-arm64": "0.0
|
|
12
|
-
"@futuretea/mysql-mcp-server-linux-amd64": "0.0
|
|
13
|
-
"@futuretea/mysql-mcp-server-linux-arm64": "0.0
|
|
14
|
-
"@futuretea/mysql-mcp-server-windows-amd64": "0.0
|
|
10
|
+
"@futuretea/mysql-mcp-server-darwin-amd64": "0.2.0",
|
|
11
|
+
"@futuretea/mysql-mcp-server-darwin-arm64": "0.2.0",
|
|
12
|
+
"@futuretea/mysql-mcp-server-linux-amd64": "0.2.0",
|
|
13
|
+
"@futuretea/mysql-mcp-server-linux-arm64": "0.2.0",
|
|
14
|
+
"@futuretea/mysql-mcp-server-windows-amd64": "0.2.0"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT"
|
|
17
17
|
}
|