easy-mysql-mcp 1.1.1 → 1.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/MANUAL.md ADDED
@@ -0,0 +1,120 @@
1
+ # easy-mysql-mcp Manual
2
+
3
+ `easy-mysql-mcp` 是一個用來操作 MySQL 的 MCP server,提供查詢、寫入、批次執行、CSV 匯入,以及 schema / 權限檢查工具。
4
+
5
+ ## Overview
6
+
7
+ 適合用在:
8
+ - 讀取資料
9
+ - 新增、更新、刪除資料
10
+ - 批次執行 SQL
11
+ - CSV 匯入與匯出
12
+ - 需要查看 schema、index、trigger、權限與 query plan 的場景
13
+
14
+ 不適合用在:
15
+ - 多語句 SQL
16
+ - 未驗證字串直接拼接 SQL
17
+ - 繞過 denylist / allowlist / policy hook
18
+
19
+ ## When to Consult This Manual
20
+
21
+ 如果你遇到以下情況,先看這份手冊:
22
+ - 不確定 `mysql_query`、`mysql_execute`、`mysql_batch_execute` 要怎麼用
23
+ - SQL 被拒絕或回傳錯誤
24
+ - 不確定 `?` 參數要怎麼綁定
25
+ - 不確定哪些 SQL 寫法是安全且允許的
26
+ - 不確定應該先查 schema 還是直接執行
27
+
28
+ ## Modes
29
+
30
+ - `readonly`: 只提供讀取相關工具
31
+ - `readwrite`: 預設模式,允許一般讀寫,但不包含 DDL
32
+ - `advanced`: 允許 schema / DDL 類工具
33
+
34
+ ## Tools
35
+
36
+ - `mysql_query`: 讀取資料
37
+ - `mysql_execute`: 執行單一寫入或變更 SQL
38
+ - `mysql_batch_execute`: 以多組參數批次執行同一段 SQL
39
+ - `mysql_import_csv`: 匯入 UTF-8 CSV
40
+ - `mysql_export_csv`: 匯出表格為 UTF-8 CSV
41
+ - `mysql_schema_execute`: 執行 schema / DDL 變更,僅 advanced 模式可用
42
+ - `mysql_list_pending_approvals`: 列出待審核命令
43
+ - `mysql_run_approved_command`: 執行已核准命令
44
+ - `mysql_cancel_approval`: 取消審核
45
+ - `explain_query`: 檢查 query plan
46
+ - `list_tables`, `list_views`, `describe_table`, `describe_index`, `list_triggers`
47
+ - `get_current_privileges`
48
+
49
+ ## Execute Usage
50
+
51
+ `mysql_execute` 只應使用單一 SQL 指令。適合:
52
+ - `INSERT`
53
+ - `UPDATE`
54
+ - `DELETE`
55
+
56
+ `mysql_batch_execute` 適合:
57
+ - 同一個 SQL 搭配多組參數重複執行
58
+ - 大量資料寫入
59
+
60
+ 不要這樣用:
61
+ - 多語句連寫
62
+ - 把使用者輸入直接拼到 SQL 字串裡
63
+
64
+ 參數寫法以 `mysql2` 的 binding 方式為準:
65
+ - 位置型參數使用 `?`
66
+ - 批次執行時,`paramsList` 裡每一組參數都會依序對應 SQL 中的 `?`
67
+ - 建議不要手動拼接字串值,改用參數綁定
68
+
69
+ ## SQL Algebra / Composition Rules
70
+
71
+ MySQL 查詢可視為一個由上而下組裝的代數式:
72
+
73
+ 1. 先決定資料來源:`FROM`
74
+ 2. 再加條件:`WHERE`
75
+ 3. 需要關聯時使用 `JOIN ... ON`
76
+ 4. 有聚合時使用 `GROUP BY`
77
+ 5. 聚合後條件放在 `HAVING`
78
+ 6. 排序使用 `ORDER BY`
79
+ 7. 最後限制筆數:`LIMIT` / `OFFSET`
80
+
81
+ 常見正確組合:
82
+ - 單表查詢:`SELECT ... FROM ... WHERE ...`
83
+ - 聚合查詢:`SELECT ... COUNT(*) ... GROUP BY ... HAVING ...`
84
+ - 多表查詢:`SELECT ... FROM a JOIN b ON ... WHERE ...`
85
+
86
+ 值與欄位的規則:
87
+ - 欄位名稱必要時用反引號包住
88
+ - 字串值使用單引號
89
+ - 數值不要加引號
90
+ - 日期與時間要用正確的 SQL 字面值
91
+ - 不要把未驗證內容直接串進條件式
92
+
93
+ ## Safety Rules
94
+
95
+ - 不支援 multi-statement
96
+ - 不支援 `SELECT ... INTO`
97
+ - 不支援 locking reads
98
+ - 不支援 `CREATE TABLE ... AS SELECT`
99
+ - 寫入前先確認 schema
100
+ - deny tables 的優先級高於 allow tables
101
+
102
+ ## Examples
103
+
104
+ - 查詢單一使用者:
105
+ - `SELECT id, name FROM users WHERE id = ?`
106
+ - 聚合統計:
107
+ - `SELECT status, COUNT(*) FROM orders GROUP BY status`
108
+ - join 查詢:
109
+ - `SELECT o.id, u.name FROM orders o JOIN users u ON o.user_id = u.id`
110
+ - 更新資料:
111
+ - `UPDATE users SET name = ? WHERE id = ?`
112
+ - 分頁查詢:
113
+ - `SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 0`
114
+
115
+ ## Troubleshooting
116
+
117
+ - 查不到欄位時,先用 `describe_table`
118
+ - SQL 被拒絕時,先檢查是否是多語句或受限語法
119
+ - 結果不如預期時,先確認 `WHERE` 與 `JOIN ON`
120
+ - 效能不好時,先看 `explain_query`
package/README.md CHANGED
@@ -16,7 +16,7 @@ This project uses Node.js, TypeScript, the official MCP SDK, and `mysql2/promise
16
16
 
17
17
  ## Requirements
18
18
 
19
- - Node.js 18 or newer
19
+ - Node.js 20 or newer
20
20
  - npm
21
21
  - A reachable MySQL-compatible database
22
22
 
@@ -55,7 +55,7 @@ Configure the server with environment variables. You can provide them through yo
55
55
  | `MYSQL_ENABLE_KEEP_ALIVE` | No | `true` | Whether TCP keep-alive is enabled |
56
56
  | `MYSQL_KEEP_ALIVE_INITIAL_DELAY` | No | `0` | Initial TCP keep-alive delay in milliseconds |
57
57
  | `MYSQL_READ_ONLY` | No | `false` | When `true`, enables read-only mode and does not register `mysql_execute` |
58
- | `MYSQL_MCP_MODE` | No | `readwrite` | MCP policy mode. Use `readonly` to disable write execution |
58
+ | `MYSQL_MCP_MODE` | No | `readwrite` | MCP policy mode. Use `readonly` to disable write execution or `advanced` to enable schema modification tools |
59
59
  | `MYSQL_MCP_ALLOW_TABLES` | No | - | Comma-separated table allowlist, such as `users,orders` |
60
60
  | `MYSQL_MCP_DENY_TABLES` | No | - | Comma-separated table denylist, such as `payments,secrets` |
61
61
  | `MYSQL_BATCH_MAX_SIZE` | No | `100` | Maximum number of parameter sets per internal batch for `mysql_batch_execute` |
@@ -91,6 +91,8 @@ npm start
91
91
 
92
92
  The server communicates over stdio and is normally launched by an MCP client rather than run manually.
93
93
 
94
+ If you are unsure how a tool should be used, or an operation fails, call `mysql_manual` first. It returns the built-in manual with safe usage rules, parameter binding guidance, and SQL composition notes.
95
+
94
96
  ## Claude Desktop Example
95
97
 
96
98
  ```json
@@ -155,8 +157,10 @@ MYSQL_DATABASE = "YOUR DB NAME"
155
157
 
156
158
  | Tool | Description |
157
159
  | --- | --- |
160
+ | `mysql_manual` | Return the built-in manual. Use this first when you are unsure how to use MySQL tools or need help diagnosing an operation error |
158
161
  | `mysql_query` | Execute a SQL query intended for data retrieval, such as `SELECT` |
159
162
  | `mysql_execute` | Execute a data modification statement, such as `INSERT`, `UPDATE`, or `DELETE` |
163
+ | `mysql_schema_execute` | Execute schema modification statements in advanced mode, such as `CREATE TABLE`, `ALTER TABLE`, `CREATE VIEW`, `CREATE TRIGGER`, and `CREATE INDEX` |
160
164
  | `mysql_batch_execute` | Execute one data modification statement repeatedly with multiple parameter sets |
161
165
  | `mysql_import_csv` | Import a UTF-8 CSV file into a table using the header row as column names |
162
166
  | `mysql_export_csv` | Export all rows from a table to a UTF-8 CSV file |
@@ -173,6 +177,8 @@ MYSQL_DATABASE = "YOUR DB NAME"
173
177
 
174
178
  When `MYSQL_READ_ONLY=true` or `MYSQL_MCP_MODE=readonly`, the `mysql_execute`, `mysql_batch_execute`, and `mysql_import_csv` tools are not registered.
175
179
 
180
+ When `MYSQL_MCP_MODE=advanced`, `mysql_schema_execute` is registered. Existing write tools remain available because advanced mode includes read/write behavior.
181
+
176
182
  ### Batch Execute
177
183
 
178
184
  `mysql_batch_execute` runs the same parameterized write statement with multiple parameter arrays. It is useful for bulk inserts or repeated updates without enabling multi-statement SQL.
@@ -237,10 +243,12 @@ The server applies a lightweight SQL policy before executing user-provided SQL:
237
243
  - `mysql_query` allows only single-statement `SELECT`, `SHOW`, `DESCRIBE`, and `EXPLAIN` queries.
238
244
  - `explain_query` accepts only a single `SELECT` statement and runs `EXPLAIN` for it.
239
245
  - `mysql_execute` allows only single-statement `INSERT`, `UPDATE`, `DELETE`, and `REPLACE` statements when write mode is enabled.
246
+ - `mysql_schema_execute` is only registered when `MYSQL_MCP_MODE=advanced`, and allows single-statement schema changes for tables, views, triggers, and indexes.
240
247
  - `mysql_batch_execute` uses the same SQL policy as `mysql_execute` and applies the statement repeatedly with parameter arrays.
241
248
  - `mysql_import_csv` uses table policy and the same batch execution path as `mysql_batch_execute`.
242
249
  - `mysql_export_csv` uses table policy before exporting table data.
243
250
  - Multi-statement SQL is rejected.
251
+ - `CREATE TABLE ... AS SELECT` is rejected because it copies data while creating a table.
244
252
  - `SELECT ... INTO` and locking reads are rejected for read-query tools.
245
253
  - `MYSQL_MCP_DENY_TABLES` rejects matching tables before `MYSQL_MCP_ALLOW_TABLES` is evaluated.
246
254
  - If `MYSQL_MCP_ALLOW_TABLES` is set, every detected table must be included in the allowlist.
@@ -267,6 +275,23 @@ In this configuration, `users` and `orders` are allowed, `payments` is rejected,
267
275
 
268
276
  `MYSQL_POLICY_HOOK` cannot override built-in rejections. It can only decide what happens after a command has already passed local policy: `accept`, `reject`, or `approval_required`.
269
277
 
278
+ ### Advanced Schema Mode
279
+
280
+ Set `MYSQL_MCP_MODE=advanced` to enable `mysql_schema_execute` for schema changes. This is an explicit opt-in mode for database structure operations.
281
+
282
+ Allowed schema statements:
283
+
284
+ | Object | Statements |
285
+ | --- | --- |
286
+ | Tables | `CREATE TABLE`, `ALTER TABLE`, `DROP TABLE`, `RENAME TABLE` |
287
+ | Views | `CREATE VIEW`, `CREATE OR REPLACE VIEW`, `DROP VIEW` |
288
+ | Triggers | `CREATE TRIGGER`, `DROP TRIGGER` |
289
+ | Indexes | `CREATE INDEX`, `DROP INDEX`, plus index changes through `ALTER TABLE` |
290
+
291
+ The same table allow/deny policy applies to detected schema objects and referenced tables. `MYSQL_MCP_DENY_TABLES` still takes precedence over `MYSQL_MCP_ALLOW_TABLES`.
292
+
293
+ Advanced mode still rejects multi-statement SQL, unsupported DDL object types, and `CREATE TABLE ... AS SELECT`.
294
+
270
295
  ## Policy Hook and Approvals
271
296
 
272
297
  When `MYSQL_POLICY_HOOK` is configured, the server posts each tool action to the hook after built-in policy checks pass and before the command runs.
@@ -322,9 +347,11 @@ This is an approval-friendly protocol. The server cannot verify that a human app
322
347
  - Use a dedicated MySQL user with the minimum permissions your assistant needs.
323
348
  - Prefer read-only database credentials if you only need inspection and reporting.
324
349
  - Use `MYSQL_READ_ONLY=true` or `MYSQL_MCP_MODE=readonly` to hide write execution from MCP clients.
350
+ - Use `MYSQL_MCP_MODE=advanced` only when the assistant should be able to modify schema objects such as tables, views, triggers, and indexes.
325
351
  - Use `MYSQL_MCP_ALLOW_TABLES` and `MYSQL_MCP_DENY_TABLES` as MCP-level guardrails, not as a replacement for MySQL grants.
326
352
  - Use `MYSQL_POLICY_HOOK` when you need an external policy or approval workflow.
327
353
  - Be careful with `mysql_execute`, because it can modify data.
354
+ - Be careful with `mysql_schema_execute`, because it can create, alter, or drop database structure.
328
355
  - Be careful with `mysql_import_csv`, because it can insert many rows.
329
356
  - Batch execution and CSV import logs include parameter values and per-row results. Treat files under `MYSQL_LOG_PATH` as sensitive.
330
357
  - CSV export writes table data to the local filesystem. Treat exported files as sensitive.
package/README.zh-TW.md CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  ## 需求
18
18
 
19
- - Node.js 18 或更新版本
19
+ - Node.js 20 或更新版本
20
20
  - npm
21
21
  - 可連線的 MySQL-compatible database
22
22
 
@@ -55,7 +55,7 @@ npm run build
55
55
  | `MYSQL_ENABLE_KEEP_ALIVE` | 否 | `true` | 是否啟用 TCP keep-alive |
56
56
  | `MYSQL_KEEP_ALIVE_INITIAL_DELAY` | 否 | `0` | TCP keep-alive 初始延遲,單位毫秒 |
57
57
  | `MYSQL_READ_ONLY` | 否 | `false` | 設為 `true` 時啟用唯讀模式,且不註冊 `mysql_execute` |
58
- | `MYSQL_MCP_MODE` | 否 | `readwrite` | MCP policy mode。使用 `readonly` 可停用寫入執行 |
58
+ | `MYSQL_MCP_MODE` | 否 | `readwrite` | MCP policy mode。使用 `readonly` 可停用寫入執行,或使用 `advanced` 啟用結構修改工具 |
59
59
  | `MYSQL_MCP_ALLOW_TABLES` | 否 | - | table allowlist,逗號分隔,例如 `users,orders` |
60
60
  | `MYSQL_MCP_DENY_TABLES` | 否 | - | table denylist,逗號分隔,例如 `payments,secrets` |
61
61
  | `MYSQL_BATCH_MAX_SIZE` | 否 | `100` | `mysql_batch_execute` 每個內部分批最多處理的參數組數 |
@@ -153,10 +153,14 @@ MYSQL_DATABASE = "YOUR DB NAME"
153
153
 
154
154
  ## 可用工具
155
155
 
156
+ 如果你不確定某個工具該怎麼用,或操作失敗,請先呼叫 `mysql_manual`。它會回傳內建手冊,包含安全使用規則、參數綁定指引,以及 SQL 組合方式。
157
+
156
158
  | 工具 | 說明 |
157
159
  | --- | --- |
160
+ | `mysql_manual` | 回傳內建手冊。當你不確定如何使用 MySQL 工具,或需要排查操作錯誤時,請先查這個工具 |
158
161
  | `mysql_query` | 執行用於資料讀取的 SQL query,例如 `SELECT` |
159
162
  | `mysql_execute` | 執行資料修改 statement,例如 `INSERT`、`UPDATE`、`DELETE` |
163
+ | `mysql_schema_execute` | 在 advanced mode 執行 schema 修改 statement,例如 `CREATE TABLE`、`ALTER TABLE`、`CREATE VIEW`、`CREATE TRIGGER`、`CREATE INDEX` |
160
164
  | `mysql_batch_execute` | 使用多組參數重複執行同一個資料修改 statement |
161
165
  | `mysql_import_csv` | 使用 CSV header row 作為欄位名稱,將 UTF-8 CSV 匯入 table |
162
166
  | `mysql_export_csv` | 將 table 的所有 rows 匯出為 UTF-8 CSV |
@@ -173,6 +177,8 @@ MYSQL_DATABASE = "YOUR DB NAME"
173
177
 
174
178
  當 `MYSQL_READ_ONLY=true` 或 `MYSQL_MCP_MODE=readonly` 時,`mysql_execute`、`mysql_batch_execute`、`mysql_import_csv` 不會被註冊。
175
179
 
180
+ 當 `MYSQL_MCP_MODE=advanced` 時,會註冊 `mysql_schema_execute`。advanced mode 包含 read/write 行為,因此既有寫入工具仍會提供。
181
+
176
182
  ### Batch Execute
177
183
 
178
184
  `mysql_batch_execute` 會使用多組參數重複執行同一個 parameterized write statement。它適合 bulk insert 或 repeated update,而且不需要啟用 multi-statement SQL。
@@ -237,10 +243,12 @@ server 在執行使用者提供的 SQL 前,會套用輕量 SQL policy:
237
243
  - `mysql_query` 只允許單一 statement 的 `SELECT`、`SHOW`、`DESCRIBE`、`EXPLAIN` queries。
238
244
  - `explain_query` 只接受單一 `SELECT` statement,並對它執行 `EXPLAIN`。
239
245
  - `mysql_execute` 在 write mode 啟用時,只允許單一 statement 的 `INSERT`、`UPDATE`、`DELETE`、`REPLACE`。
246
+ - `mysql_schema_execute` 只有在 `MYSQL_MCP_MODE=advanced` 時註冊,允許 tables、views、triggers、indexes 的單一 schema 修改 statement。
240
247
  - `mysql_batch_execute` 使用與 `mysql_execute` 相同的 SQL policy,並用多組參數重複執行。
241
248
  - `mysql_import_csv` 使用 table policy,並走與 `mysql_batch_execute` 相同的 batch execution path。
242
249
  - `mysql_export_csv` 會在匯出 table data 前套用 table policy。
243
250
  - multi-statement SQL 會被拒絕。
251
+ - `CREATE TABLE ... AS SELECT` 會被拒絕,因為它會在建立 table 時複製資料。
244
252
  - read-query tools 會拒絕 `SELECT ... INTO` 與 locking reads。
245
253
  - `MYSQL_MCP_DENY_TABLES` 優先於 `MYSQL_MCP_ALLOW_TABLES`。
246
254
  - 如果設定 `MYSQL_MCP_ALLOW_TABLES`,每個偵測到的 table 都必須包含在 allowlist 中。
@@ -267,6 +275,23 @@ MYSQL_MCP_DENY_TABLES=payments
267
275
 
268
276
  `MYSQL_POLICY_HOOK` 不能覆蓋內建 policy 的拒絕結果。它只能在 command 已經通過本機 policy 後,決定接下來是 `accept`、`reject`,或 `approval_required`。
269
277
 
278
+ ### Advanced Schema Mode
279
+
280
+ 設定 `MYSQL_MCP_MODE=advanced` 可啟用 `mysql_schema_execute`,用於 schema changes。這是明確 opt-in 的資料庫結構操作模式。
281
+
282
+ 允許的 schema statements:
283
+
284
+ | 物件 | Statements |
285
+ | --- | --- |
286
+ | Tables | `CREATE TABLE`、`ALTER TABLE`、`DROP TABLE`、`RENAME TABLE` |
287
+ | Views | `CREATE VIEW`、`CREATE OR REPLACE VIEW`、`DROP VIEW` |
288
+ | Triggers | `CREATE TRIGGER`、`DROP TRIGGER` |
289
+ | Indexes | `CREATE INDEX`、`DROP INDEX`,以及透過 `ALTER TABLE` 修改 index |
290
+
291
+ 相同的 table allow/deny policy 會套用到偵測到的 schema objects 與 referenced tables。`MYSQL_MCP_DENY_TABLES` 仍然優先於 `MYSQL_MCP_ALLOW_TABLES`。
292
+
293
+ Advanced mode 仍會拒絕 multi-statement SQL、不支援的 DDL object types,以及 `CREATE TABLE ... AS SELECT`。
294
+
270
295
  ## Policy Hook 與 Approval
271
296
 
272
297
  設定 `MYSQL_POLICY_HOOK` 後,server 會在內建 policy 通過後、command 真正執行前,將每個 tool action POST 到 hook。
@@ -322,9 +347,11 @@ Pending approvals 是 one-time use,並會在 `MYSQL_APPROVAL_TTL_SECONDS` 後
322
347
  - 使用 dedicated MySQL user,並只給 assistant 所需的最小權限。
323
348
  - 如果只需要 inspection/reporting,建議使用 read-only database credentials。
324
349
  - 使用 `MYSQL_READ_ONLY=true` 或 `MYSQL_MCP_MODE=readonly`,可以避免 write execution tools 暴露給 MCP clients。
350
+ - 只有在 assistant 需要修改 tables、views、triggers、indexes 等 schema objects 時,才使用 `MYSQL_MCP_MODE=advanced`。
325
351
  - `MYSQL_MCP_ALLOW_TABLES` 與 `MYSQL_MCP_DENY_TABLES` 是 MCP 層 guardrails,不能取代 MySQL grants。
326
352
  - 需要外部 policy 或 approval workflow 時,可使用 `MYSQL_POLICY_HOOK`。
327
353
  - 請小心使用 `mysql_execute`,它可以修改資料。
354
+ - 請小心使用 `mysql_schema_execute`,它可以建立、修改或刪除資料庫結構。
328
355
  - 請小心使用 `mysql_import_csv`,它可以插入大量資料。
329
356
  - batch execution 與 CSV import logs 會包含參數值與逐筆結果。請將 `MYSQL_LOG_PATH` 下的檔案視為敏感資料。
330
357
  - CSV export 會將 table data 寫到本機 filesystem。請將匯出檔視為敏感資料。
package/build/config.js CHANGED
@@ -27,6 +27,9 @@ function resolveMode() {
27
27
  if (readOnly || explicitMode === 'readonly' || explicitMode === 'read-only') {
28
28
  return 'readonly';
29
29
  }
30
+ if (explicitMode === 'advanced') {
31
+ return 'advanced';
32
+ }
30
33
  return 'readwrite';
31
34
  }
32
35
  export const config = {
@@ -41,6 +44,9 @@ export const config = {
41
44
  export function isReadOnlyMode() {
42
45
  return config.mode === 'readonly';
43
46
  }
47
+ export function isAdvancedMode() {
48
+ return config.mode === 'advanced';
49
+ }
44
50
  export function isPolicyHookEnabled() {
45
51
  return Boolean(config.policyHookUrl);
46
52
  }
package/build/index.js CHANGED
@@ -1,20 +1,29 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
2
4
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
6
  import { z } from 'zod';
5
- import { isPolicyHookEnabled, isReadOnlyMode } from './config.js';
7
+ import { isAdvancedMode, isPolicyHookEnabled, isReadOnlyMode } from './config.js';
6
8
  import { cleanupOldLogs } from './logs.js';
7
9
  import { cancelApproval, listPendingApprovals, runApprovedCommand } from './approvalStore.js';
8
- import { describeIndex, describeTable, explainQuery, getCurrentPrivileges, listTables, listTriggers, listViews, mysqlBatchExecute, mysqlExecute, mysqlExportCsv, mysqlImportCsv, mysqlQuery, } from './toolHandlers.js';
10
+ import { describeIndex, describeTable, explainQuery, getCurrentPrivileges, listTables, listTriggers, listViews, mysqlBatchExecute, mysqlExecute, mysqlExportCsv, mysqlImportCsv, mysqlSchemaExecute, mysqlQuery, } from './toolHandlers.js';
9
11
  const { MYSQL_HOST, MYSQL_PORT, MYSQL_DATABASE, } = process.env;
10
12
  // Initialize MCP Server/mcp
11
13
  const server = new McpServer({
12
14
  name: 'easy-mysql-mcp',
13
- version: '1.1.1',
15
+ version: '1.1.2',
14
16
  description: `MySQL Database: ${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}`,
15
17
  });
18
+ const manualText = readFileSync(fileURLToPath(new URL('../MANUAL.md', import.meta.url)), 'utf8');
16
19
  // --- Register Tools ---
17
20
  const transactionModeSchema = z.enum(['all', 'batch', 'each', 'none']);
21
+ server.registerTool('mysql_manual', {
22
+ description: 'Return the MySQL MCP manual. Use this first when you are unsure how to use mysql_query, mysql_execute, mysql_batch_execute, or when an operation fails and you need the safe usage rules, parameter rules, or SQL composition guidance.',
23
+ inputSchema: z.object({}),
24
+ }, async () => ({
25
+ content: [{ type: 'text', text: manualText }],
26
+ }));
18
27
  server.registerTool('mysql_query', {
19
28
  description: 'Execute a read-only SQL query (e.g., SELECT). Use this for data retrieval.',
20
29
  inputSchema: z.object({
@@ -66,6 +75,19 @@ if (!isReadOnlyMode()) {
66
75
  };
67
76
  });
68
77
  }
78
+ if (isAdvancedMode()) {
79
+ server.registerTool('mysql_schema_execute', {
80
+ description: 'Execute an advanced schema modification SQL statement, such as CREATE TABLE, ALTER TABLE, DROP VIEW, CREATE TRIGGER, or CREATE INDEX.',
81
+ inputSchema: z.object({
82
+ sql: z.string().describe('The single schema modification SQL statement to execute.'),
83
+ }),
84
+ }, async ({ sql }) => {
85
+ const result = await mysqlSchemaExecute(sql);
86
+ return {
87
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
88
+ };
89
+ });
90
+ }
69
91
  if (isPolicyHookEnabled()) {
70
92
  server.registerTool('mysql_run_approved_command', {
71
93
  description: 'Run a pending command after the host has obtained user approval.',
@@ -1,11 +1,14 @@
1
1
  import { createRequire } from 'node:module';
2
- import { config, isReadOnlyMode, normalizeTableName } from './config.js';
2
+ import { config, isAdvancedMode, isReadOnlyMode, normalizeTableName } from './config.js';
3
3
  const require = createRequire(import.meta.url);
4
4
  const { Parser } = require('node-sql-parser/build/mysql.js');
5
5
  const parser = new Parser();
6
6
  const parserOptions = { database: 'MySQL' };
7
7
  const readQueryTypes = new Set(['select', 'show', 'desc', 'describe', 'explain']);
8
8
  const executeTypes = new Set(['insert', 'update', 'delete', 'replace']);
9
+ const schemaExecuteTypes = new Set(['create', 'alter', 'drop', 'rename']);
10
+ const createSchemaKeywords = new Set(['table', 'view', 'trigger', 'index']);
11
+ const dropSchemaKeywords = new Set(['table', 'view', 'trigger', 'index']);
9
12
  export class SqlPolicyError extends Error {
10
13
  constructor(message) {
11
14
  super(message);
@@ -45,6 +48,18 @@ export function assertExecuteAllowed(sql) {
45
48
  }
46
49
  assertTablePolicy(parsed.tables);
47
50
  }
51
+ export function assertSchemaExecuteAllowed(sql) {
52
+ if (!isAdvancedMode()) {
53
+ throw new SqlPolicyError('SQL rejected: schema execution requires MYSQL_MCP_MODE=advanced.');
54
+ }
55
+ const parsed = parseSingleStatement(sql);
56
+ if (!schemaExecuteTypes.has(parsed.type)) {
57
+ throw new SqlPolicyError(`SQL rejected: mysql_schema_execute only allows CREATE, ALTER, DROP, and RENAME statements. Received ${parsed.type.toUpperCase()}.`);
58
+ }
59
+ assertSchemaAstAllowed(parsed.ast);
60
+ assertNoUnsafeReadOptions(parsed.ast);
61
+ assertTablePolicy(parsed.tables);
62
+ }
48
63
  export function assertTablesAllowed(tables) {
49
64
  assertTablePolicy(tables.map((table) => normalizeTableName(table)));
50
65
  }
@@ -111,6 +126,39 @@ function assertNoUnsafeReadOptions(ast) {
111
126
  if (ast?.type === 'explain') {
112
127
  assertNoUnsafeReadOptions(ast.expr);
113
128
  }
129
+ if (ast?.type === 'create' && ast.keyword === 'view') {
130
+ assertNoUnsafeReadOptions(ast.select);
131
+ }
132
+ }
133
+ function assertSchemaAstAllowed(ast) {
134
+ switch (ast?.type) {
135
+ case 'create': {
136
+ const keyword = normalizeKeyword(ast.keyword);
137
+ if (!createSchemaKeywords.has(keyword)) {
138
+ throw new SqlPolicyError(`SQL rejected: mysql_schema_execute does not allow CREATE ${keyword.toUpperCase()}.`);
139
+ }
140
+ if (keyword === 'table' && ast.query_expr) {
141
+ throw new SqlPolicyError('SQL rejected: CREATE TABLE ... AS SELECT is not allowed by mysql_schema_execute.');
142
+ }
143
+ return;
144
+ }
145
+ case 'alter':
146
+ if (!ast.table) {
147
+ throw new SqlPolicyError('SQL rejected: mysql_schema_execute only allows ALTER TABLE statements.');
148
+ }
149
+ return;
150
+ case 'drop': {
151
+ const keyword = normalizeKeyword(ast.keyword);
152
+ if (!dropSchemaKeywords.has(keyword)) {
153
+ throw new SqlPolicyError(`SQL rejected: mysql_schema_execute does not allow DROP ${keyword.toUpperCase()}.`);
154
+ }
155
+ return;
156
+ }
157
+ case 'rename':
158
+ return;
159
+ default:
160
+ throw new SqlPolicyError(`SQL rejected: mysql_schema_execute does not allow ${String(ast?.type ?? 'unknown').toUpperCase()} statements.`);
161
+ }
114
162
  }
115
163
  function assertTablePolicy(tables) {
116
164
  for (const table of tables) {
@@ -175,6 +223,12 @@ function collectTablesFromAst(ast) {
175
223
  return [];
176
224
  }
177
225
  switch (ast.type) {
226
+ case 'create':
227
+ return collectCreateTablesFromAst(ast);
228
+ case 'drop':
229
+ return collectDropTablesFromAst(ast);
230
+ case 'rename':
231
+ return collectRenameTablesFromAst(ast);
178
232
  case 'desc':
179
233
  case 'describe':
180
234
  return typeof ast.table === 'string' ? [ast.table] : [];
@@ -184,3 +238,47 @@ function collectTablesFromAst(ast) {
184
238
  return [];
185
239
  }
186
240
  }
241
+ function collectCreateTablesFromAst(ast) {
242
+ const tables = [
243
+ ...collectTableLikeNames(ast.table),
244
+ ...collectTableLikeNames(ast.view),
245
+ ...collectTableLikeNames(ast.trigger),
246
+ ...collectTableLikeNames(ast.like?.table),
247
+ ];
248
+ if (ast.keyword === 'view') {
249
+ tables.push(...collectTablesFromAst(ast.select));
250
+ }
251
+ return tables;
252
+ }
253
+ function collectDropTablesFromAst(ast) {
254
+ if (ast.keyword === 'index') {
255
+ return collectTableLikeNames(ast.table);
256
+ }
257
+ return collectTableLikeNames(ast.name);
258
+ }
259
+ function collectRenameTablesFromAst(ast) {
260
+ return collectTableLikeNames(ast.table);
261
+ }
262
+ function collectTableLikeNames(value) {
263
+ if (!value) {
264
+ return [];
265
+ }
266
+ if (Array.isArray(value)) {
267
+ return value.flatMap((item) => collectTableLikeNames(item));
268
+ }
269
+ if (typeof value === 'string') {
270
+ return [value];
271
+ }
272
+ if (typeof value === 'object') {
273
+ const objectName = value.table ?? value.view ?? value.trigger;
274
+ if (typeof objectName === 'string') {
275
+ return value.db || value.schema
276
+ ? [`${value.db ?? value.schema}.${objectName}`]
277
+ : [objectName];
278
+ }
279
+ }
280
+ return [];
281
+ }
282
+ function normalizeKeyword(keyword) {
283
+ return typeof keyword === 'string' ? keyword.toLowerCase() : 'unknown';
284
+ }
@@ -3,7 +3,7 @@ import { exportCsv, importCsv } from './csvTools.js';
3
3
  import * as db from './db.js';
4
4
  import { writeBatchExecuteLog } from './logs.js';
5
5
  import { runWithPolicy } from './policyHook.js';
6
- import { analyzeSql, assertExecuteAllowed, assertExplainQueryAllowed, assertReadQueryAllowed, assertTablesAllowed, isTableAllowed, } from './sqlPolicy.js';
6
+ import { analyzeSql, assertExecuteAllowed, assertExplainQueryAllowed, assertReadQueryAllowed, assertSchemaExecuteAllowed, assertTablesAllowed, isTableAllowed, } from './sqlPolicy.js';
7
7
  export async function mysqlQuery(sql) {
8
8
  assertReadQueryAllowed(sql);
9
9
  const analysis = analyzeSql(sql);
@@ -26,6 +26,17 @@ export async function mysqlExecute(sql, params) {
26
26
  summary: { sql, paramsPreview: params ?? null },
27
27
  }, () => db.execute(sql, params));
28
28
  }
29
+ export async function mysqlSchemaExecute(sql) {
30
+ assertSchemaExecuteAllowed(sql);
31
+ const analysis = analyzeSql(sql);
32
+ return runWithPolicy({
33
+ functionName: 'mysql_schema_execute',
34
+ sql,
35
+ statementType: analysis.statementType,
36
+ tableNames: analysis.tableNames,
37
+ summary: { sql },
38
+ }, () => db.query(sql));
39
+ }
29
40
  export async function mysqlBatchExecute(sql, paramsList, transaction) {
30
41
  assertExecuteAllowed(sql);
31
42
  const analysis = analyzeSql(sql);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-mysql-mcp",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "High performance MySQL MCP Server using mysql2",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -9,10 +9,12 @@
9
9
  },
10
10
  "files": [
11
11
  "build",
12
- "README.md"
12
+ "README.md",
13
+ "README.zh-TW.md",
14
+ "MANUAL.md"
13
15
  ],
14
16
  "engines": {
15
- "node": ">=18"
17
+ "node": ">=20"
16
18
  },
17
19
  "keywords": [
18
20
  "mcp",
@@ -34,12 +36,12 @@
34
36
  },
35
37
  "dependencies": {
36
38
  "@modelcontextprotocol/sdk": "^1.29.0",
37
- "mysql2": "^3.22.4",
39
+ "mysql2": "^3.22.5",
38
40
  "node-sql-parser": "^5.4.0",
39
41
  "zod": "^4.4.3"
40
42
  },
41
43
  "devDependencies": {
42
- "@types/node": "^25.6.0",
44
+ "@types/node": "^25.9.3",
43
45
  "typescript": "^6.0.3"
44
46
  },
45
47
  "repository": {