@berthojoris/mcp-mysql-server 1.40.6 → 1.40.7

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/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to the MySQL MCP Server will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.40.7] - 2026-05-06
9
+
10
+ ### Added
11
+ - Implemented `export_query_to_csv` end-to-end for SELECT query CSV exports.
12
+ - Enhanced `list_all_tools` with a live runtime tool catalog, enabled/disabled status, access profile, and AI-agent workflow guidance.
13
+
14
+ ### Fixed
15
+ - Fixed `export_query_to_csv` being advertised but failing with `Unknown tool`.
16
+ - Fixed `list_all_tools` returning stale manifest data instead of the active MCP tool catalog.
17
+ - Fixed `execute_in_transaction` permission checks to match its advertised transaction permission.
18
+ - Hardened DDL tools by validating raw DDL statements and checking structured table, column, and index inputs before building SQL.
19
+ - Added argument validation for Cursor bridge dispatches.
20
+
8
21
  ## [1.40.6] - 2026-05-04
9
22
 
10
23
  ### Added
package/DOCUMENTATIONS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # MySQL MCP Server - Documentation
2
2
 
3
- **Last Updated:** 2026-05-04 18:07:52
4
- **Version:** 1.40.6
3
+ **Last Updated:** 2026-05-06 22:38:42
4
+ **Version:** 1.40.7
5
5
  **Total Tools:** 79
6
6
 
7
7
  Comprehensive documentation for the MySQL MCP Server. For quick start, see [README.md](README.md).
@@ -93,6 +93,14 @@ Supported `mode` values are `auto`, `select`, `write`, and `ddl`. Set `MYSQL_MCP
93
93
 
94
94
  ---
95
95
 
96
+ ### AI Agent Tool Discovery
97
+
98
+ Use `list_all_tools` first when connecting from Codex, Claude Code CLI, Cursor, Droid CLI, or other MCP agents. It returns the live runtime catalog from the server, enabled/disabled status for each tool, the active permission/category profile, and recommended workflows for common agent tasks.
99
+
100
+ For CSV exports, use `export_table_to_csv` for table-based exports and `export_query_to_csv` for SELECT query exports.
101
+
102
+ ---
103
+
96
104
  ## Permission System
97
105
 
98
106
  ### Available Permissions
@@ -120,12 +128,11 @@ Tool enabled = (Has Permission) AND (Has Category OR No categories specified)
120
128
 
121
129
  ## Tool Categories
122
130
 
123
- ### 1. Database Discovery (5 tools)
131
+ ### 1. Database Discovery (4 tools)
124
132
  - `list_databases` - List all databases
125
133
  - `list_tables` - List tables in database
126
134
  - `read_table_schema` - Get table structure
127
135
  - `get_all_tables_relationships` - Get all FK relationships
128
- - `list_all_tools` - List available MCP tools
129
136
 
130
137
  ### 2. Analysis (4 tools)
131
138
  - `get_database_summary` - Database overview with statistics
@@ -153,7 +160,11 @@ Tool enabled = (Has Permission) AND (Has Category OR No categories specified)
153
160
  - `drop_table` - Delete tables
154
161
  - `execute_ddl` - Execute raw DDL
155
162
 
156
- ### 6. Index Management (10 tools)
163
+ ### 6. Data Export (2 tools)
164
+ - `export_table_to_csv` - Export table data to CSV
165
+ - `export_query_to_csv` - Export SELECT query results to CSV
166
+
167
+ ### 7. Index Management (10 tools)
157
168
  - `list_indexes` - List table indexes
158
169
  - `get_index_info` - Get index details
159
170
  - `create_index` - Create indexes
@@ -165,7 +176,7 @@ Tool enabled = (Has Permission) AND (Has Category OR No categories specified)
165
176
  - `get_fulltext_stats` - Get FULLTEXT index statistics
166
177
  - `optimize_fulltext` - Optimize FULLTEXT indexes
167
178
 
168
- ### 7. Constraint Management (7 tools)
179
+ ### 8. Constraint Management (7 tools)
169
180
  - `list_foreign_keys` - List foreign keys
170
181
  - `list_constraints` - List all constraints
171
182
  - `add_foreign_key` - Add foreign key
@@ -174,7 +185,7 @@ Tool enabled = (Has Permission) AND (Has Category OR No categories specified)
174
185
  - `drop_constraint` - Remove constraint
175
186
  - `add_check_constraint` - Add check constraint
176
187
 
177
- ### 8. Stored Procedures (6 tools)
188
+ ### 9. Stored Procedures (6 tools)
178
189
  - `list_stored_procedures` - List procedures
179
190
  - `get_stored_procedure_info` - Get procedure details
180
191
  - `execute_stored_procedure` - Execute procedures
@@ -182,7 +193,7 @@ Tool enabled = (Has Permission) AND (Has Category OR No categories specified)
182
193
  - `drop_stored_procedure` - Remove procedures
183
194
  - `show_create_procedure` - Show CREATE statement
184
195
 
185
- ### 9. Views Management (6 tools)
196
+ ### 10. Views Management (6 tools)
186
197
  - `list_views` - List views
187
198
  - `get_view_info` - Get view details
188
199
  - `create_view` - Create views
@@ -190,14 +201,14 @@ Tool enabled = (Has Permission) AND (Has Category OR No categories specified)
190
201
  - `drop_view` - Remove views
191
202
  - `show_create_view` - Show CREATE statement
192
203
 
193
- ### 10. Triggers Management (5 tools)
204
+ ### 11. Triggers Management (5 tools)
194
205
  - `list_triggers` - List triggers
195
206
  - `get_trigger_info` - Get trigger details
196
207
  - `create_trigger` - Create triggers
197
208
  - `drop_trigger` - Remove triggers
198
209
  - `show_create_trigger` - Show CREATE statement
199
210
 
200
- ### 11. Table Maintenance (8 tools)
211
+ ### 12. Table Maintenance (8 tools)
201
212
  - `analyze_table` - Update statistics
202
213
  - `optimize_table` - Reclaim space
203
214
  - `check_table` - Check for errors
@@ -207,24 +218,24 @@ Tool enabled = (Has Permission) AND (Has Category OR No categories specified)
207
218
  - `flush_table` - Close/reopen table
208
219
  - `get_table_size` - Get size information
209
220
 
210
- ### 12. Transaction Management (5 tools)
221
+ ### 13. Transaction Management (5 tools)
211
222
  - `begin_transaction` - Start transaction
212
223
  - `commit_transaction` - Commit transaction
213
224
  - `rollback_transaction` - Rollback transaction
214
225
  - `get_transaction_status` - Check transaction state
215
226
  - `execute_in_transaction` - Execute within transaction
216
227
 
217
- ### 13. Query Optimization (3 tools)
228
+ ### 14. Query Optimization (3 tools)
218
229
  - `analyze_query` - Analyze query performance
219
230
  - `get_optimization_hints` - Get optimizer hints
220
231
  - `repair_query` - Repair broken SQL queries
221
232
 
222
- ### 14. Utilities (5 tools)
233
+ ### 15. Utilities (5 tools)
223
234
  - `test_connection` - Test connectivity
224
235
  - `describe_connection` - Connection info
225
236
  - `read_changelog` - Read changelog
226
- - `invalidate_table_cache` - Clear table cache
227
237
  - `cursor_execute_request` - Execute a file-backed request for clients that cannot send MCP arguments
238
+ - `list_all_tools` - Runtime tool catalog with agent guidance
228
239
 
229
240
  ---
230
241
 
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **A production-ready Model Context Protocol (MCP) server for MySQL database integration with AI agents**
6
6
 
7
- **Last Updated:** 2026-05-04 18:07:52
7
+ **Last Updated:** 2026-05-06 22:38:42
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/@berthojoris/mcp-mysql-server)](https://www.npmjs.com/package/@berthojoris/mcp-mysql-server)
10
10
  [![npm downloads](https://img.shields.io/npm/dm/@berthojoris/mcp-mysql-server)](https://www.npmjs.com/package/@berthojoris/mcp-mysql-server)
@@ -237,6 +237,14 @@ Set `MYSQL_MCP_CURSOR_REQUEST_FILE` to override the request file path.
237
237
 
238
238
  ---
239
239
 
240
+ ### AI Agent Tool Discovery
241
+
242
+ For Codex, Claude Code CLI, Cursor, Droid CLI, and other MCP agents, call `list_all_tools` first. It returns the live runtime catalog, enabled/disabled status, active permission/category profile, and recommended workflows for schema exploration, safe SELECT queries, CSV exports, transactions, and data changes.
243
+
244
+ Use `export_table_to_csv` for table-based exports and `export_query_to_csv` for SELECT query exports.
245
+
246
+ ---
247
+
240
248
  ## Permission System
241
249
 
242
250
  Control database access with a **dual-layer filtering system** that provides both broad and fine-grained control:
@@ -53,7 +53,8 @@ exports.toolCategoryMap = {
53
53
  listDatabases: ToolCategory.LIST,
54
54
  listTables: ToolCategory.LIST,
55
55
  readTableSchema: ToolCategory.LIST,
56
- list_all_tools: ToolCategory.LIST,
56
+ listAllTools: ToolCategory.UTILITY,
57
+ list_all_tools: ToolCategory.UTILITY,
57
58
  // Analysis tools (added here to group with database tools)
58
59
  getDatabaseSummary: ToolCategory.LIST,
59
60
  getSchemaERD: ToolCategory.LIST,
@@ -194,6 +195,7 @@ exports.toolDocCategoryMap = {
194
195
  exportQueryToCsv: DocCategory.UTILITIES,
195
196
  export_query_to_csv: DocCategory.UTILITIES,
196
197
  read_changelog: DocCategory.UTILITIES,
198
+ listAllTools: DocCategory.UTILITIES,
197
199
  list_all_tools: DocCategory.UTILITIES,
198
200
  // Transaction Management
199
201
  beginTransaction: DocCategory.TRANSACTION_MANAGEMENT,
package/dist/index.d.ts CHANGED
@@ -203,7 +203,19 @@ export declare class MySQLMCP {
203
203
  data?: any;
204
204
  error?: string;
205
205
  }>;
206
- listAllTools(): Promise<{
206
+ listAllTools(params?: {
207
+ tools?: Array<{
208
+ name: string;
209
+ description?: string;
210
+ inputSchema?: any;
211
+ input_schema?: any;
212
+ output_schema?: any;
213
+ }>;
214
+ enabledToolNames?: string[];
215
+ accessProfile?: any;
216
+ serverName?: string;
217
+ serverVersion?: string;
218
+ }): Promise<{
207
219
  status: string;
208
220
  data?: any;
209
221
  error?: string;
@@ -315,6 +327,15 @@ export declare class MySQLMCP {
315
327
  data?: any;
316
328
  error?: string;
317
329
  }>;
330
+ exportQueryToCSV(params: {
331
+ query: string;
332
+ params?: any[];
333
+ include_headers?: boolean;
334
+ }): Promise<{
335
+ status: string;
336
+ data?: any;
337
+ error?: string;
338
+ }>;
318
339
  repairQuery(params: {
319
340
  query: string;
320
341
  error_message?: string;
package/dist/index.js CHANGED
@@ -134,7 +134,7 @@ class MySQLMCP {
134
134
  return { status: "error", error: check.error };
135
135
  }
136
136
  // Additional security check
137
- if (!this.security.isReadOnlyQuery(params.query)) {
137
+ if (!this.security.isReadOnlyQuery(params.query, this.security.hasExecutePermission())) {
138
138
  return {
139
139
  status: "error",
140
140
  error: "Only SELECT queries are allowed with run_select_query. Use execute_write_query for other operations.",
@@ -239,12 +239,12 @@ class MySQLMCP {
239
239
  }
240
240
  return await this.utilityTools.readChangelog(params);
241
241
  }
242
- async listAllTools() {
242
+ async listAllTools(params) {
243
243
  const check = this.checkToolEnabled("list_all_tools");
244
244
  if (!check.enabled) {
245
245
  return { status: "error", error: check.error };
246
246
  }
247
- return await this.utilityTools.listAllTools();
247
+ return await this.utilityTools.listAllTools(params);
248
248
  }
249
249
  // Transaction Tools
250
250
  async beginTransaction(params) {
@@ -276,7 +276,7 @@ class MySQLMCP {
276
276
  return await this.transactionTools.getTransactionStatus();
277
277
  }
278
278
  async executeInTransaction(params) {
279
- const check = this.checkToolEnabled("executeWriteQuery"); // Use executeWriteQuery permission for transaction queries
279
+ const check = this.checkToolEnabled("executeInTransaction");
280
280
  if (!check.enabled) {
281
281
  return { status: "error", error: check.error };
282
282
  }
@@ -333,6 +333,13 @@ class MySQLMCP {
333
333
  }
334
334
  return await this.dataExportTools.exportTableToCSV(params);
335
335
  }
336
+ async exportQueryToCSV(params) {
337
+ const check = this.checkToolEnabled("exportQueryToCSV");
338
+ if (!check.enabled) {
339
+ return { status: "error", error: check.error };
340
+ }
341
+ return await this.dataExportTools.exportQueryToCSV(params);
342
+ }
336
343
  // AI Productivity Tools
337
344
  async repairQuery(params) {
338
345
  const check = this.checkToolEnabled("repairQuery");
@@ -17,6 +17,8 @@ const toolArgumentValidation_js_1 = require("./tools/toolArgumentValidation.js")
17
17
  // Layer 2 (Categories): MCP_CATEGORIES (optional, for fine-grained control)
18
18
  const permissions = process.env.MCP_PERMISSIONS || process.env.MCP_CONFIG || "";
19
19
  const categories = process.env.MCP_CATEGORIES || "";
20
+ const SERVER_NAME = "mysql-mcp-server";
21
+ const SERVER_VERSION = "1.40.7";
20
22
  // Declare the MySQL MCP instance (will be initialized in main())
21
23
  let mysqlMCP;
22
24
  // Define all available tools with their schemas
@@ -1862,8 +1864,8 @@ const TOOLS = [
1862
1864
  ];
1863
1865
  // Create the MCP server
1864
1866
  const server = new index_js_1.Server({
1865
- name: "mysql-mcp-server",
1866
- version: "1.40.6",
1867
+ name: SERVER_NAME,
1868
+ version: SERVER_VERSION,
1867
1869
  }, {
1868
1870
  capabilities: {
1869
1871
  tools: {},
@@ -1884,6 +1886,20 @@ const TOOL_METHOD_OVERRIDES = {
1884
1886
  export_query_to_csv: "exportQueryToCSV",
1885
1887
  };
1886
1888
  const toCamelCase = (value) => value.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
1889
+ const getRuntimeToolCatalog = () => {
1890
+ const enabledTools = (0, toolRegistry_js_1.getEnabledTools)(mysqlMCP, TOOLS);
1891
+ return {
1892
+ tools: TOOLS.map((tool) => ({
1893
+ name: tool.name,
1894
+ description: tool.description,
1895
+ inputSchema: tool.inputSchema,
1896
+ })),
1897
+ enabledToolNames: enabledTools.map((tool) => tool.name),
1898
+ accessProfile: mysqlMCP.getAccessProfile(),
1899
+ serverName: SERVER_NAME,
1900
+ serverVersion: SERVER_VERSION,
1901
+ };
1902
+ };
1887
1903
  const getCursorRequestFilePath = () => {
1888
1904
  const configuredPath = process.env.MYSQL_MCP_CURSOR_REQUEST_FILE ||
1889
1905
  process.env.MCP_MYSQL_REQUEST_FILE ||
@@ -1919,6 +1935,13 @@ const executeToolByName = async (toolName, args = {}) => {
1919
1935
  if (toolName === "cursor_execute_request") {
1920
1936
  throw new Error("cursor_execute_request cannot dispatch to itself");
1921
1937
  }
1938
+ const validation = (0, toolArgumentValidation_js_1.validateToolArguments)(toolName, args);
1939
+ if (!validation.valid) {
1940
+ throw new Error(`Validation Error: ${validation.errors?.join(", ") || "Invalid arguments"}`);
1941
+ }
1942
+ if (toolName === "list_all_tools") {
1943
+ return await mysqlMCP.listAllTools(getRuntimeToolCatalog());
1944
+ }
1922
1945
  const methodName = TOOL_METHOD_OVERRIDES[toolName] || toCamelCase(toolName);
1923
1946
  const method = mysqlMCP[methodName];
1924
1947
  if (typeof method !== "function") {
@@ -2055,7 +2078,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
2055
2078
  result = await mysqlMCP.testConnection();
2056
2079
  break;
2057
2080
  case "list_all_tools":
2058
- result = await mysqlMCP.listAllTools();
2081
+ result = await mysqlMCP.listAllTools(getRuntimeToolCatalog());
2059
2082
  break;
2060
2083
  case "read_changelog":
2061
2084
  result = await mysqlMCP.readChangelog((args || {}));
@@ -2102,6 +2125,9 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
2102
2125
  case "export_table_to_csv":
2103
2126
  result = await mysqlMCP.exportTableToCSV((args || {}));
2104
2127
  break;
2128
+ case "export_query_to_csv":
2129
+ result = await mysqlMCP.exportQueryToCSV((args || {}));
2130
+ break;
2105
2131
  // Query Optimization Tools
2106
2132
  case "analyze_query":
2107
2133
  result = mysqlMCP.analyzeQuery((args || {}));
@@ -71,7 +71,7 @@ export declare class SecurityLayer {
71
71
  /**
72
72
  * Check if a query is a read-only SELECT query or information query (SHOW, DESCRIBE, etc.)
73
73
  */
74
- isReadOnlyQuery(query: string): boolean;
74
+ isReadOnlyQuery(query: string, bypassDangerousCheck?: boolean): boolean;
75
75
  /**
76
76
  * Check if a query contains dangerous operations
77
77
  */
@@ -404,13 +404,13 @@ class SecurityLayer {
404
404
  /**
405
405
  * Check if a query is a read-only SELECT query or information query (SHOW, DESCRIBE, etc.)
406
406
  */
407
- isReadOnlyQuery(query) {
407
+ isReadOnlyQuery(query, bypassDangerousCheck = false) {
408
408
  // Check if it's an information query first (SHOW, DESCRIBE, EXPLAIN, etc.)
409
409
  if (this.isInformationQuery(query)) {
410
410
  return true;
411
411
  }
412
412
  // Check if it's a SELECT query
413
- const validation = this.validateQuery(query);
413
+ const validation = this.validateQuery(query, bypassDangerousCheck);
414
414
  return validation.valid && validation.queryType === "SELECT";
415
415
  }
416
416
  /**
@@ -12,6 +12,8 @@ export declare class DataExportTools {
12
12
  * Escape string value for SQL INSERT statements
13
13
  */
14
14
  private escapeValue;
15
+ private escapeCsvValue;
16
+ private rowsToCSV;
15
17
  /**
16
18
  * Export table data to CSV format
17
19
  */
@@ -26,4 +28,13 @@ export declare class DataExportTools {
26
28
  data?: any;
27
29
  error?: string;
28
30
  }>;
31
+ exportQueryToCSV(queryParams: {
32
+ query: string;
33
+ params?: any[];
34
+ include_headers?: boolean;
35
+ }): Promise<{
36
+ status: string;
37
+ data?: any;
38
+ error?: string;
39
+ }>;
29
40
  }
@@ -62,6 +62,33 @@ class DataExportTools {
62
62
  .replace(/\0/g, "\\0");
63
63
  return `'${escaped}'`;
64
64
  }
65
+ escapeCsvValue(value) {
66
+ if (value === null || value === undefined)
67
+ return "";
68
+ const normalizedValue = value instanceof Date
69
+ ? value.toISOString()
70
+ : Buffer.isBuffer(value)
71
+ ? value.toString("base64")
72
+ : String(value);
73
+ if (/[",\r\n]/.test(normalizedValue)) {
74
+ return `"${normalizedValue.replace(/"/g, '""')}"`;
75
+ }
76
+ return normalizedValue;
77
+ }
78
+ rowsToCSV(rows, includeHeaders) {
79
+ if (rows.length === 0) {
80
+ return "";
81
+ }
82
+ const columns = Object.keys(rows[0]);
83
+ const csvRows = [];
84
+ if (includeHeaders) {
85
+ csvRows.push(columns.map((column) => this.escapeCsvValue(column)).join(","));
86
+ }
87
+ for (const row of rows) {
88
+ csvRows.push(columns.map((column) => this.escapeCsvValue(row[column])).join(","));
89
+ }
90
+ return `${csvRows.join("\n")}\n`;
91
+ }
65
92
  /**
66
93
  * Export table data to CSV format
67
94
  */
@@ -179,33 +206,7 @@ class DataExportTools {
179
206
  },
180
207
  };
181
208
  }
182
- // Generate CSV
183
- let csv = "";
184
- // Add headers if requested
185
- if (include_headers) {
186
- const headers = Object.keys(results[0]).join(",");
187
- csv += headers + "\n";
188
- }
189
- // Add data rows
190
- for (const row of results) {
191
- const values = Object.values(row)
192
- .map((value) => {
193
- if (value === null)
194
- return "";
195
- if (typeof value === "string") {
196
- // Escape quotes and wrap in quotes if contains comma or newline
197
- if (value.includes(",") ||
198
- value.includes("\n") ||
199
- value.includes('"')) {
200
- return `"${value.replace(/"/g, '""')}"`;
201
- }
202
- return value;
203
- }
204
- return String(value);
205
- })
206
- .join(",");
207
- csv += values + "\n";
208
- }
209
+ const csv = this.rowsToCSV(results, include_headers);
209
210
  return {
210
211
  status: "success",
211
212
  data: {
@@ -221,5 +222,45 @@ class DataExportTools {
221
222
  };
222
223
  }
223
224
  }
225
+ async exportQueryToCSV(queryParams) {
226
+ try {
227
+ const { query, params = [], include_headers = true, } = queryParams;
228
+ const queryValidation = this.security.validateQuery(query, this.security.hasExecutePermission());
229
+ if (!queryValidation.valid) {
230
+ return {
231
+ status: "error",
232
+ error: `Query validation failed: ${queryValidation.error}`,
233
+ };
234
+ }
235
+ if (queryValidation.queryType !== "SELECT") {
236
+ return {
237
+ status: "error",
238
+ error: "export_query_to_csv only accepts SELECT queries.",
239
+ };
240
+ }
241
+ const paramValidation = this.security.validateParameters(params);
242
+ if (!paramValidation.valid) {
243
+ return {
244
+ status: "error",
245
+ error: `Parameter validation failed: ${paramValidation.error}`,
246
+ };
247
+ }
248
+ const results = await this.db.query(query, paramValidation.sanitizedParams, false);
249
+ const maskedResults = this.security.masking.processResults(results);
250
+ return {
251
+ status: "success",
252
+ data: {
253
+ csv: this.rowsToCSV(maskedResults, include_headers),
254
+ row_count: maskedResults.length,
255
+ },
256
+ };
257
+ }
258
+ catch (error) {
259
+ return {
260
+ status: "error",
261
+ error: error.message,
262
+ };
263
+ }
264
+ }
224
265
  }
225
266
  exports.DataExportTools = DataExportTools;
@@ -7,6 +7,8 @@ export declare class DdlTools {
7
7
  * Sanitize default value for SQL safety
8
8
  */
9
9
  private sanitizeDefaultValue;
10
+ private validateColumnType;
11
+ private validateIdentifier;
10
12
  /**
11
13
  * Create a new table
12
14
  */