@berthojoris/mcp-mysql-server 1.4.15 → 1.6.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.
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ProcessTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ class ProcessTools {
9
+ constructor(security) {
10
+ this.db = connection_1.default.getInstance();
11
+ this.security = security;
12
+ }
13
+ /**
14
+ * Show all running processes/connections
15
+ */
16
+ async showProcessList(params) {
17
+ try {
18
+ const query = params?.full ? 'SHOW FULL PROCESSLIST' : 'SHOW PROCESSLIST';
19
+ const results = await this.db.query(query);
20
+ // Format results for better readability
21
+ const formattedResults = results.map(row => ({
22
+ id: row.Id,
23
+ user: row.User,
24
+ host: row.Host,
25
+ database: row.db,
26
+ command: row.Command,
27
+ time: row.Time,
28
+ state: row.State,
29
+ info: row.Info,
30
+ progress: row.Progress
31
+ }));
32
+ return {
33
+ status: 'success',
34
+ data: formattedResults,
35
+ queryLog: this.db.getFormattedQueryLogs(1)
36
+ };
37
+ }
38
+ catch (error) {
39
+ return {
40
+ status: 'error',
41
+ error: error.message,
42
+ queryLog: this.db.getFormattedQueryLogs(1)
43
+ };
44
+ }
45
+ }
46
+ /**
47
+ * Kill a specific process/connection
48
+ */
49
+ async killProcess(params) {
50
+ try {
51
+ const { process_id, type = 'CONNECTION' } = params;
52
+ // Validate process_id is a positive integer
53
+ if (!Number.isInteger(process_id) || process_id <= 0) {
54
+ return { status: 'error', error: 'Process ID must be a positive integer' };
55
+ }
56
+ const query = type === 'QUERY'
57
+ ? `KILL QUERY ${process_id}`
58
+ : `KILL ${process_id}`;
59
+ await this.db.query(query);
60
+ return {
61
+ status: 'success',
62
+ message: type === 'QUERY'
63
+ ? `Query for process ${process_id} killed successfully`
64
+ : `Process ${process_id} killed successfully`,
65
+ queryLog: this.db.getFormattedQueryLogs(1)
66
+ };
67
+ }
68
+ catch (error) {
69
+ return {
70
+ status: 'error',
71
+ error: error.message,
72
+ queryLog: this.db.getFormattedQueryLogs(1)
73
+ };
74
+ }
75
+ }
76
+ /**
77
+ * Show server status variables
78
+ */
79
+ async showStatus(params) {
80
+ try {
81
+ let query = params?.global ? 'SHOW GLOBAL STATUS' : 'SHOW STATUS';
82
+ if (params?.like) {
83
+ // Validate the LIKE pattern (basic check)
84
+ if (params.like.includes(';') || params.like.includes('--')) {
85
+ return { status: 'error', error: 'Invalid pattern' };
86
+ }
87
+ query += ` LIKE '${params.like}'`;
88
+ }
89
+ const results = await this.db.query(query);
90
+ // Convert to object for easier access
91
+ const statusObj = {};
92
+ for (const row of results) {
93
+ statusObj[row.Variable_name] = row.Value;
94
+ }
95
+ return {
96
+ status: 'success',
97
+ data: params?.like ? statusObj : {
98
+ variables: statusObj,
99
+ count: results.length
100
+ },
101
+ queryLog: this.db.getFormattedQueryLogs(1)
102
+ };
103
+ }
104
+ catch (error) {
105
+ return {
106
+ status: 'error',
107
+ error: error.message,
108
+ queryLog: this.db.getFormattedQueryLogs(1)
109
+ };
110
+ }
111
+ }
112
+ /**
113
+ * Show server variables
114
+ */
115
+ async showVariables(params) {
116
+ try {
117
+ let query = params?.global ? 'SHOW GLOBAL VARIABLES' : 'SHOW VARIABLES';
118
+ if (params?.like) {
119
+ // Validate the LIKE pattern (basic check)
120
+ if (params.like.includes(';') || params.like.includes('--')) {
121
+ return { status: 'error', error: 'Invalid pattern' };
122
+ }
123
+ query += ` LIKE '${params.like}'`;
124
+ }
125
+ const results = await this.db.query(query);
126
+ // Convert to object for easier access
127
+ const varsObj = {};
128
+ for (const row of results) {
129
+ varsObj[row.Variable_name] = row.Value;
130
+ }
131
+ return {
132
+ status: 'success',
133
+ data: params?.like ? varsObj : {
134
+ variables: varsObj,
135
+ count: results.length
136
+ },
137
+ queryLog: this.db.getFormattedQueryLogs(1)
138
+ };
139
+ }
140
+ catch (error) {
141
+ return {
142
+ status: 'error',
143
+ error: error.message,
144
+ queryLog: this.db.getFormattedQueryLogs(1)
145
+ };
146
+ }
147
+ }
148
+ /**
149
+ * Explain a query (show execution plan)
150
+ */
151
+ async explainQuery(params) {
152
+ try {
153
+ const { query, format = 'TRADITIONAL', analyze = false } = params;
154
+ // Only allow SELECT, UPDATE, DELETE, INSERT queries to be explained
155
+ const normalizedQuery = query.trim().toUpperCase();
156
+ if (!normalizedQuery.startsWith('SELECT') &&
157
+ !normalizedQuery.startsWith('UPDATE') &&
158
+ !normalizedQuery.startsWith('DELETE') &&
159
+ !normalizedQuery.startsWith('INSERT')) {
160
+ return { status: 'error', error: 'EXPLAIN only supports SELECT, UPDATE, DELETE, and INSERT statements' };
161
+ }
162
+ let explainQuery = analyze ? 'EXPLAIN ANALYZE ' : 'EXPLAIN ';
163
+ if (format !== 'TRADITIONAL') {
164
+ explainQuery += `FORMAT=${format} `;
165
+ }
166
+ explainQuery += query;
167
+ const results = await this.db.query(explainQuery);
168
+ return {
169
+ status: 'success',
170
+ data: format === 'JSON' ? JSON.parse(results[0]['EXPLAIN']) : results,
171
+ queryLog: this.db.getFormattedQueryLogs(1)
172
+ };
173
+ }
174
+ catch (error) {
175
+ return {
176
+ status: 'error',
177
+ error: error.message,
178
+ queryLog: this.db.getFormattedQueryLogs(1)
179
+ };
180
+ }
181
+ }
182
+ /**
183
+ * Show engine status (InnoDB, etc.)
184
+ */
185
+ async showEngineStatus(params) {
186
+ try {
187
+ const engine = params?.engine || 'INNODB';
188
+ // Validate engine name
189
+ const validEngines = ['INNODB', 'PERFORMANCE_SCHEMA', 'NDB', 'NDBCLUSTER'];
190
+ if (!validEngines.includes(engine.toUpperCase())) {
191
+ return { status: 'error', error: `Invalid engine. Supported: ${validEngines.join(', ')}` };
192
+ }
193
+ const query = `SHOW ENGINE ${engine.toUpperCase()} STATUS`;
194
+ const results = await this.db.query(query);
195
+ return {
196
+ status: 'success',
197
+ data: results,
198
+ queryLog: this.db.getFormattedQueryLogs(1)
199
+ };
200
+ }
201
+ catch (error) {
202
+ return {
203
+ status: 'error',
204
+ error: error.message,
205
+ queryLog: this.db.getFormattedQueryLogs(1)
206
+ };
207
+ }
208
+ }
209
+ /**
210
+ * Get server information
211
+ */
212
+ async getServerInfo() {
213
+ try {
214
+ // Get various server info
215
+ const queries = [
216
+ { key: 'version', query: 'SELECT VERSION() as value' },
217
+ { key: 'connection_id', query: 'SELECT CONNECTION_ID() as value' },
218
+ { key: 'current_user', query: 'SELECT CURRENT_USER() as value' },
219
+ { key: 'database', query: 'SELECT DATABASE() as value' }
220
+ ];
221
+ const info = {};
222
+ for (const q of queries) {
223
+ const result = await this.db.query(q.query);
224
+ info[q.key] = result[0]?.value;
225
+ }
226
+ // Get uptime and other status
227
+ const statusQuery = `SHOW GLOBAL STATUS WHERE Variable_name IN ('Uptime', 'Threads_connected', 'Threads_running', 'Questions', 'Slow_queries', 'Opens', 'Flush_commands', 'Open_tables', 'Queries')`;
228
+ const statusResult = await this.db.query(statusQuery);
229
+ for (const row of statusResult) {
230
+ info[row.Variable_name.toLowerCase()] = row.Value;
231
+ }
232
+ // Format uptime
233
+ if (info.uptime) {
234
+ const uptimeSec = parseInt(info.uptime);
235
+ const days = Math.floor(uptimeSec / 86400);
236
+ const hours = Math.floor((uptimeSec % 86400) / 3600);
237
+ const minutes = Math.floor((uptimeSec % 3600) / 60);
238
+ info.uptime_formatted = `${days}d ${hours}h ${minutes}m`;
239
+ }
240
+ return {
241
+ status: 'success',
242
+ data: info,
243
+ queryLog: this.db.getFormattedQueryLogs(queries.length + 1)
244
+ };
245
+ }
246
+ catch (error) {
247
+ return {
248
+ status: 'error',
249
+ error: error.message,
250
+ queryLog: this.db.getFormattedQueryLogs(1)
251
+ };
252
+ }
253
+ }
254
+ /**
255
+ * Show binary logs
256
+ */
257
+ async showBinaryLogs() {
258
+ try {
259
+ const query = 'SHOW BINARY LOGS';
260
+ const results = await this.db.query(query);
261
+ return {
262
+ status: 'success',
263
+ data: results,
264
+ queryLog: this.db.getFormattedQueryLogs(1)
265
+ };
266
+ }
267
+ catch (error) {
268
+ return {
269
+ status: 'error',
270
+ error: error.message,
271
+ queryLog: this.db.getFormattedQueryLogs(1)
272
+ };
273
+ }
274
+ }
275
+ /**
276
+ * Show master/replica status
277
+ */
278
+ async showReplicationStatus(params) {
279
+ try {
280
+ const type = params?.type || 'REPLICA';
281
+ let query;
282
+ if (type === 'MASTER') {
283
+ query = 'SHOW MASTER STATUS';
284
+ }
285
+ else {
286
+ // MySQL 8.0.22+ uses REPLICA, older versions use SLAVE
287
+ query = type === 'SLAVE' ? 'SHOW SLAVE STATUS' : 'SHOW REPLICA STATUS';
288
+ }
289
+ const results = await this.db.query(query);
290
+ return {
291
+ status: 'success',
292
+ data: results.length > 0 ? results[0] : null,
293
+ queryLog: this.db.getFormattedQueryLogs(1)
294
+ };
295
+ }
296
+ catch (error) {
297
+ return {
298
+ status: 'error',
299
+ error: error.message,
300
+ queryLog: this.db.getFormattedQueryLogs(1)
301
+ };
302
+ }
303
+ }
304
+ }
305
+ exports.ProcessTools = ProcessTools;
@@ -1,20 +1,33 @@
1
1
  import SecurityLayer from "../security/securityLayer";
2
+ import { QueryHints, QueryAnalysis } from "../optimization/queryOptimizer";
2
3
  export declare class QueryTools {
3
4
  private db;
4
5
  private security;
6
+ private optimizer;
5
7
  constructor(security: SecurityLayer);
6
8
  /**
7
- * Execute a safe read-only SELECT query
9
+ * Execute a safe read-only SELECT query with optional optimizer hints
8
10
  */
9
11
  runQuery(queryParams: {
10
12
  query: string;
11
13
  params?: any[];
14
+ hints?: QueryHints;
15
+ useCache?: boolean;
12
16
  }): Promise<{
13
17
  status: string;
14
18
  data?: any[];
15
19
  error?: string;
16
20
  queryLog?: string;
21
+ optimizedQuery?: string;
17
22
  }>;
23
+ /**
24
+ * Analyze a query and get optimization suggestions
25
+ */
26
+ analyzeQuery(query: string): QueryAnalysis;
27
+ /**
28
+ * Get suggested hints for a specific optimization goal
29
+ */
30
+ getSuggestedHints(goal: "SPEED" | "MEMORY" | "STABILITY"): QueryHints;
18
31
  /**
19
32
  * Execute write operations (INSERT, UPDATE, DELETE) with validation
20
33
  * Note: DDL operations are blocked by the security layer for safety
@@ -6,13 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.QueryTools = void 0;
7
7
  const connection_1 = __importDefault(require("../db/connection"));
8
8
  const schemas_1 = require("../validation/schemas");
9
+ const queryOptimizer_1 = require("../optimization/queryOptimizer");
9
10
  class QueryTools {
10
11
  constructor(security) {
11
12
  this.db = connection_1.default.getInstance();
12
13
  this.security = security;
14
+ this.optimizer = queryOptimizer_1.QueryOptimizer.getInstance();
13
15
  }
14
16
  /**
15
- * Execute a safe read-only SELECT query
17
+ * Execute a safe read-only SELECT query with optional optimizer hints
16
18
  */
17
19
  async runQuery(queryParams) {
18
20
  // Validate input schema
@@ -23,7 +25,7 @@ class QueryTools {
23
25
  };
24
26
  }
25
27
  try {
26
- const { query, params = [] } = queryParams;
28
+ const { query, params = [], hints, useCache = true } = queryParams;
27
29
  // Check if user has execute permission to bypass dangerous keyword checks
28
30
  const hasExecutePermission = this.security.hasExecutePermission();
29
31
  // Validate query using security layer
@@ -50,12 +52,22 @@ class QueryTools {
50
52
  error: `Parameter validation failed: ${paramValidation.error}`,
51
53
  };
52
54
  }
55
+ // Apply optimizer hints if provided
56
+ let finalQuery = query;
57
+ let optimizedQuery;
58
+ if (hints) {
59
+ finalQuery = this.optimizer.applyHints(query, hints);
60
+ if (finalQuery !== query) {
61
+ optimizedQuery = finalQuery;
62
+ }
63
+ }
53
64
  // Execute the query with sanitized parameters
54
- const results = await this.db.query(query, paramValidation.sanitizedParams);
65
+ const results = await this.db.query(finalQuery, paramValidation.sanitizedParams, useCache);
55
66
  return {
56
67
  status: "success",
57
68
  data: results,
58
69
  queryLog: this.db.getFormattedQueryLogs(1),
70
+ optimizedQuery,
59
71
  };
60
72
  }
61
73
  catch (error) {
@@ -66,6 +78,18 @@ class QueryTools {
66
78
  };
67
79
  }
68
80
  }
81
+ /**
82
+ * Analyze a query and get optimization suggestions
83
+ */
84
+ analyzeQuery(query) {
85
+ return this.optimizer.analyzeQuery(query);
86
+ }
87
+ /**
88
+ * Get suggested hints for a specific optimization goal
89
+ */
90
+ getSuggestedHints(goal) {
91
+ return this.optimizer.getSuggestedHints(goal);
92
+ }
69
93
  /**
70
94
  * Execute write operations (INSERT, UPDATE, DELETE) with validation
71
95
  * Note: DDL operations are blocked by the security layer for safety
@@ -0,0 +1,76 @@
1
+ import { SecurityLayer } from '../security/securityLayer';
2
+ export declare class TriggerTools {
3
+ private db;
4
+ private security;
5
+ constructor(security: SecurityLayer);
6
+ /**
7
+ * Validate database access - ensures only the connected database can be accessed
8
+ */
9
+ private validateDatabaseAccess;
10
+ /**
11
+ * List all triggers in the current database
12
+ */
13
+ listTriggers(params: {
14
+ database?: string;
15
+ table_name?: string;
16
+ }): Promise<{
17
+ status: string;
18
+ data?: any[];
19
+ error?: string;
20
+ queryLog?: string;
21
+ }>;
22
+ /**
23
+ * Get detailed information about a specific trigger
24
+ */
25
+ getTriggerInfo(params: {
26
+ trigger_name: string;
27
+ database?: string;
28
+ }): Promise<{
29
+ status: string;
30
+ data?: any;
31
+ error?: string;
32
+ queryLog?: string;
33
+ }>;
34
+ /**
35
+ * Create a new trigger
36
+ */
37
+ createTrigger(params: {
38
+ trigger_name: string;
39
+ table_name: string;
40
+ timing: 'BEFORE' | 'AFTER';
41
+ event: 'INSERT' | 'UPDATE' | 'DELETE';
42
+ body: string;
43
+ definer?: string;
44
+ database?: string;
45
+ }): Promise<{
46
+ status: string;
47
+ data?: any;
48
+ error?: string;
49
+ queryLog?: string;
50
+ }>;
51
+ /**
52
+ * Drop a trigger
53
+ */
54
+ dropTrigger(params: {
55
+ trigger_name: string;
56
+ if_exists?: boolean;
57
+ database?: string;
58
+ }): Promise<{
59
+ status: string;
60
+ message?: string;
61
+ error?: string;
62
+ queryLog?: string;
63
+ }>;
64
+ /**
65
+ * Show the CREATE statement for a trigger
66
+ */
67
+ showCreateTrigger(params: {
68
+ trigger_name: string;
69
+ database?: string;
70
+ }): Promise<{
71
+ status: string;
72
+ data?: any;
73
+ error?: string;
74
+ queryLog?: string;
75
+ }>;
76
+ }