@berthojoris/mcp-mysql-server 1.0.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,411 @@
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.StoredProcedureTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ const config_1 = require("../config/config");
9
+ const schemas_1 = require("../validation/schemas");
10
+ class StoredProcedureTools {
11
+ constructor(security) {
12
+ this.db = connection_1.default.getInstance();
13
+ this.security = security;
14
+ }
15
+ /**
16
+ * List all stored procedures in the current database
17
+ */
18
+ async listStoredProcedures(params) {
19
+ try {
20
+ // Validate input
21
+ if (!(0, schemas_1.validateListStoredProcedures)(params)) {
22
+ return {
23
+ status: 'error',
24
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateListStoredProcedures.errors)
25
+ };
26
+ }
27
+ const database = params.database || config_1.dbConfig.database;
28
+ if (!database) {
29
+ return {
30
+ status: 'error',
31
+ error: 'No database specified and no current database selected'
32
+ };
33
+ }
34
+ const query = `
35
+ SELECT
36
+ ROUTINE_NAME as name,
37
+ ROUTINE_TYPE as type,
38
+ DATA_TYPE as return_type,
39
+ ROUTINE_DEFINITION as definition,
40
+ CREATED,
41
+ LAST_ALTERED,
42
+ ROUTINE_COMMENT as comment,
43
+ DEFINER,
44
+ SQL_MODE,
45
+ SECURITY_TYPE
46
+ FROM INFORMATION_SCHEMA.ROUTINES
47
+ WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'PROCEDURE'
48
+ ORDER BY ROUTINE_NAME
49
+ `;
50
+ const results = await this.db.query(query, [database]);
51
+ return {
52
+ status: 'success',
53
+ data: results
54
+ };
55
+ }
56
+ catch (error) {
57
+ return {
58
+ status: 'error',
59
+ error: error.message
60
+ };
61
+ }
62
+ }
63
+ /**
64
+ * Get detailed information about a specific stored procedure
65
+ */
66
+ async getStoredProcedureInfo(params) {
67
+ try {
68
+ // Validate input
69
+ if (!(0, schemas_1.validateGetStoredProcedureInfo)(params)) {
70
+ return {
71
+ status: 'error',
72
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateGetStoredProcedureInfo.errors)
73
+ };
74
+ }
75
+ const { procedure_name } = params;
76
+ const database = params.database || config_1.dbConfig.database;
77
+ if (!database) {
78
+ return {
79
+ status: 'error',
80
+ error: 'No database specified and no current database selected'
81
+ };
82
+ }
83
+ // Get procedure information
84
+ const procedureQuery = `
85
+ SELECT
86
+ ROUTINE_NAME as name,
87
+ ROUTINE_TYPE as type,
88
+ DATA_TYPE as return_type,
89
+ ROUTINE_DEFINITION as definition,
90
+ CREATED,
91
+ LAST_ALTERED,
92
+ ROUTINE_COMMENT as comment,
93
+ DEFINER,
94
+ SQL_MODE,
95
+ SECURITY_TYPE,
96
+ IS_DETERMINISTIC,
97
+ SQL_DATA_ACCESS,
98
+ ROUTINE_BODY
99
+ FROM INFORMATION_SCHEMA.ROUTINES
100
+ WHERE ROUTINE_SCHEMA = ? AND ROUTINE_NAME = ? AND ROUTINE_TYPE = 'PROCEDURE'
101
+ `;
102
+ // Get procedure parameters
103
+ const parametersQuery = `
104
+ SELECT
105
+ PARAMETER_NAME as name,
106
+ PARAMETER_MODE as mode,
107
+ DATA_TYPE as data_type,
108
+ CHARACTER_MAXIMUM_LENGTH as max_length,
109
+ ORDINAL_POSITION as position
110
+ FROM INFORMATION_SCHEMA.PARAMETERS
111
+ WHERE SPECIFIC_SCHEMA = ? AND SPECIFIC_NAME = ?
112
+ ORDER BY ORDINAL_POSITION
113
+ `;
114
+ const [procedureInfo, parameters] = await Promise.all([
115
+ this.db.query(procedureQuery, [database, procedure_name]),
116
+ this.db.query(parametersQuery, [database, procedure_name])
117
+ ]);
118
+ if (procedureInfo.length === 0) {
119
+ return {
120
+ status: 'error',
121
+ error: `Stored procedure '${procedure_name}' not found in database '${database}'`
122
+ };
123
+ }
124
+ return {
125
+ status: 'success',
126
+ data: {
127
+ ...procedureInfo[0],
128
+ parameters: parameters
129
+ }
130
+ };
131
+ }
132
+ catch (error) {
133
+ return {
134
+ status: 'error',
135
+ error: error.message
136
+ };
137
+ }
138
+ }
139
+ /**
140
+ * Execute a stored procedure with parameters
141
+ */
142
+ async executeStoredProcedure(params) {
143
+ // Validate input schema
144
+ if (!(0, schemas_1.validateStoredProcedureExecution)(params)) {
145
+ return {
146
+ status: 'error',
147
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateStoredProcedureExecution.errors)
148
+ };
149
+ }
150
+ try {
151
+ const { procedure_name, parameters = [] } = params;
152
+ const database = params.database || config_1.dbConfig.database;
153
+ if (!database) {
154
+ return {
155
+ status: 'error',
156
+ error: 'No database specified and no current database selected'
157
+ };
158
+ }
159
+ // Validate procedure name
160
+ const identifierValidation = this.security.validateIdentifier(procedure_name);
161
+ if (!identifierValidation.valid) {
162
+ return {
163
+ status: 'error',
164
+ error: identifierValidation.error || 'Invalid procedure name'
165
+ };
166
+ }
167
+ // Get procedure parameter information to handle OUT/INOUT parameters
168
+ const procInfo = await this.getStoredProcedureInfo({ procedure_name, database });
169
+ if (procInfo.status !== 'success' || !procInfo.data) {
170
+ return {
171
+ status: 'error',
172
+ error: `Could not retrieve procedure information: ${procInfo.error || 'Unknown error'}`
173
+ };
174
+ }
175
+ const procedureParams = procInfo.data.parameters || [];
176
+ // Validate parameter count
177
+ if (parameters.length > procedureParams.length) {
178
+ return {
179
+ status: 'error',
180
+ error: `Too many parameters provided. Expected ${procedureParams.length}, got ${parameters.length}`
181
+ };
182
+ }
183
+ // Validate parameters
184
+ const paramValidation = this.security.validateParameters(parameters);
185
+ if (!paramValidation.valid) {
186
+ return {
187
+ status: 'error',
188
+ error: `Parameter validation failed: ${paramValidation.error}`
189
+ };
190
+ }
191
+ // Build parameter list for CALL statement
192
+ const callParams = [];
193
+ const sessionVars = [];
194
+ let paramIndex = 0;
195
+ for (let i = 0; i < procedureParams.length; i++) {
196
+ const procParam = procedureParams[i];
197
+ if (procParam.mode === 'IN') {
198
+ // IN parameters use provided values or NULL if not provided
199
+ if (paramIndex < parameters.length) {
200
+ callParams.push('?');
201
+ paramIndex++;
202
+ }
203
+ else {
204
+ callParams.push('NULL');
205
+ }
206
+ }
207
+ else if (procParam.mode === 'OUT' || procParam.mode === 'INOUT') {
208
+ // OUT/INOUT parameters use session variables
209
+ const varName = `@${procParam.name}_${Date.now()}_${i}`;
210
+ sessionVars.push(varName);
211
+ if (procParam.mode === 'INOUT' && paramIndex < parameters.length) {
212
+ // For INOUT, set the session variable to the input value first
213
+ await this.db.query(`SET ${varName} = ?`, [paramValidation.sanitizedParams[paramIndex]]);
214
+ paramIndex++;
215
+ }
216
+ callParams.push(varName);
217
+ }
218
+ }
219
+ // Build and execute CALL statement
220
+ const callQuery = `CALL \`${database}\`.\`${procedure_name}\`(${callParams.join(', ')})`;
221
+ const callResults = await this.db.query(callQuery, paramValidation.sanitizedParams.slice(0, paramIndex));
222
+ // Get OUT/INOUT parameter values
223
+ const outputValues = {};
224
+ if (sessionVars.length > 0) {
225
+ const selectQuery = `SELECT ${sessionVars.join(', ')}`;
226
+ const outputResults = await this.db.query(selectQuery);
227
+ if (outputResults && outputResults.length > 0) {
228
+ const outputRow = outputResults[0];
229
+ sessionVars.forEach((varName, index) => {
230
+ const paramName = procedureParams.find((p) => (p.mode === 'OUT' || p.mode === 'INOUT') &&
231
+ varName.includes(p.name))?.name || `param_${index}`;
232
+ outputValues[paramName] = outputRow[varName];
233
+ });
234
+ }
235
+ }
236
+ return {
237
+ status: 'success',
238
+ data: {
239
+ results: callResults,
240
+ outputParameters: Object.keys(outputValues).length > 0 ? outputValues : undefined
241
+ }
242
+ };
243
+ }
244
+ catch (error) {
245
+ return {
246
+ status: 'error',
247
+ error: error.message
248
+ };
249
+ }
250
+ }
251
+ /**
252
+ * Create a new stored procedure
253
+ */
254
+ async createStoredProcedure(params) {
255
+ // Validate input schema
256
+ if (!(0, schemas_1.validateStoredProcedureCreation)(params)) {
257
+ return {
258
+ status: 'error',
259
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateStoredProcedureCreation.errors)
260
+ };
261
+ }
262
+ try {
263
+ const { procedure_name, parameters = [], body, comment } = params;
264
+ const database = params.database || config_1.dbConfig.database;
265
+ if (!database) {
266
+ return {
267
+ status: 'error',
268
+ error: 'No database specified and no current database selected'
269
+ };
270
+ }
271
+ // Validate procedure name
272
+ const identifierValidation = this.security.validateIdentifier(procedure_name);
273
+ if (!identifierValidation.valid) {
274
+ return {
275
+ status: 'error',
276
+ error: identifierValidation.error || 'Invalid procedure name'
277
+ };
278
+ }
279
+ // Build parameter list
280
+ const parameterList = parameters.map(param => {
281
+ if (!this.security.validateIdentifier(param.name).valid) {
282
+ throw new Error(`Invalid parameter name: ${param.name}`);
283
+ }
284
+ return `${param.mode} \`${param.name}\` ${param.data_type}`;
285
+ }).join(', ');
286
+ // Build CREATE PROCEDURE statement
287
+ let createQuery = `CREATE PROCEDURE \`${database}\`.\`${procedure_name}\`(${parameterList})\n`;
288
+ if (comment) {
289
+ createQuery += `COMMENT '${comment.replace(/'/g, "''")}'
290
+ `;
291
+ }
292
+ // Check if body already contains BEGIN/END, if not add them
293
+ const trimmedBody = body.trim();
294
+ if (trimmedBody.toUpperCase().startsWith('BEGIN') && trimmedBody.toUpperCase().endsWith('END')) {
295
+ createQuery += `\n${body}`;
296
+ }
297
+ else {
298
+ createQuery += `BEGIN\n${body}\nEND`;
299
+ }
300
+ // Execute the CREATE PROCEDURE statement
301
+ await this.db.query(createQuery);
302
+ return {
303
+ status: 'success',
304
+ data: {
305
+ message: `Stored procedure '${procedure_name}' created successfully`,
306
+ procedure_name,
307
+ database
308
+ }
309
+ };
310
+ }
311
+ catch (error) {
312
+ return {
313
+ status: 'error',
314
+ error: error.message
315
+ };
316
+ }
317
+ }
318
+ /**
319
+ * Drop a stored procedure
320
+ */
321
+ async dropStoredProcedure(params) {
322
+ try {
323
+ // Validate input
324
+ if (!(0, schemas_1.validateDropStoredProcedure)(params)) {
325
+ return {
326
+ status: 'error',
327
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateDropStoredProcedure.errors)
328
+ };
329
+ }
330
+ const { procedure_name, if_exists = false } = params;
331
+ const database = params.database || config_1.dbConfig.database;
332
+ if (!database) {
333
+ return {
334
+ status: 'error',
335
+ error: 'No database specified and no current database selected'
336
+ };
337
+ }
338
+ // Validate procedure name
339
+ const identifierValidation = this.security.validateIdentifier(procedure_name);
340
+ if (!identifierValidation.valid) {
341
+ return {
342
+ status: 'error',
343
+ error: identifierValidation.error || 'Invalid procedure name'
344
+ };
345
+ }
346
+ // Build DROP PROCEDURE statement
347
+ const dropQuery = `DROP PROCEDURE ${if_exists ? 'IF EXISTS' : ''} \`${database}\`.\`${procedure_name}\``;
348
+ // Execute the DROP PROCEDURE statement
349
+ await this.db.query(dropQuery);
350
+ return {
351
+ status: 'success',
352
+ message: `Stored procedure '${procedure_name}' dropped successfully`
353
+ };
354
+ }
355
+ catch (error) {
356
+ return {
357
+ status: 'error',
358
+ error: error.message
359
+ };
360
+ }
361
+ }
362
+ /**
363
+ * Show the CREATE statement for a stored procedure
364
+ */
365
+ async showCreateProcedure(params) {
366
+ try {
367
+ // Validate input
368
+ if (!(0, schemas_1.validateShowCreateProcedure)(params)) {
369
+ return {
370
+ status: 'error',
371
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateShowCreateProcedure.errors)
372
+ };
373
+ }
374
+ const { procedure_name } = params;
375
+ const database = params.database || config_1.dbConfig.database;
376
+ if (!database) {
377
+ return {
378
+ status: 'error',
379
+ error: 'No database specified and no current database selected'
380
+ };
381
+ }
382
+ // Validate procedure name
383
+ const identifierValidation = this.security.validateIdentifier(procedure_name);
384
+ if (!identifierValidation.valid) {
385
+ return {
386
+ status: 'error',
387
+ error: identifierValidation.error || 'Invalid procedure name'
388
+ };
389
+ }
390
+ const query = `SHOW CREATE PROCEDURE \`${database}\`.\`${procedure_name}\``;
391
+ const results = await this.db.query(query);
392
+ if (results.length === 0) {
393
+ return {
394
+ status: 'error',
395
+ error: `Stored procedure '${procedure_name}' not found`
396
+ };
397
+ }
398
+ return {
399
+ status: 'success',
400
+ data: results[0]
401
+ };
402
+ }
403
+ catch (error) {
404
+ return {
405
+ status: 'error',
406
+ error: error.message
407
+ };
408
+ }
409
+ }
410
+ }
411
+ exports.StoredProcedureTools = StoredProcedureTools;
@@ -0,0 +1,45 @@
1
+ export interface TransactionResult {
2
+ status: 'success' | 'error';
3
+ transactionId?: string;
4
+ message?: string;
5
+ activeTransactions?: string[];
6
+ error?: string;
7
+ }
8
+ export declare class TransactionTools {
9
+ private db;
10
+ constructor();
11
+ /**
12
+ * Begin a new transaction
13
+ */
14
+ beginTransaction(params?: {
15
+ transactionId?: string;
16
+ }): Promise<TransactionResult>;
17
+ /**
18
+ * Commit a transaction
19
+ */
20
+ commitTransaction(params: {
21
+ transactionId: string;
22
+ }): Promise<TransactionResult>;
23
+ /**
24
+ * Rollback a transaction
25
+ */
26
+ rollbackTransaction(params: {
27
+ transactionId: string;
28
+ }): Promise<TransactionResult>;
29
+ /**
30
+ * Get status of active transactions
31
+ */
32
+ getTransactionStatus(): Promise<TransactionResult>;
33
+ /**
34
+ * Execute a query within a transaction
35
+ */
36
+ executeInTransaction(params: {
37
+ transactionId: string;
38
+ query: string;
39
+ params?: any[];
40
+ }): Promise<{
41
+ status: 'success' | 'error';
42
+ data?: any;
43
+ error?: string;
44
+ }>;
45
+ }
@@ -0,0 +1,130 @@
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.TransactionTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ class TransactionTools {
9
+ constructor() {
10
+ this.db = connection_1.default.getInstance();
11
+ }
12
+ /**
13
+ * Begin a new transaction
14
+ */
15
+ async beginTransaction(params) {
16
+ try {
17
+ const transactionId = params?.transactionId || `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
18
+ await this.db.beginTransaction(transactionId);
19
+ return {
20
+ status: 'success',
21
+ transactionId,
22
+ message: `Transaction ${transactionId} started successfully`
23
+ };
24
+ }
25
+ catch (error) {
26
+ return {
27
+ status: 'error',
28
+ error: error.message
29
+ };
30
+ }
31
+ }
32
+ /**
33
+ * Commit a transaction
34
+ */
35
+ async commitTransaction(params) {
36
+ try {
37
+ if (!params.transactionId) {
38
+ return {
39
+ status: 'error',
40
+ error: 'Transaction ID is required'
41
+ };
42
+ }
43
+ await this.db.commitTransaction(params.transactionId);
44
+ return {
45
+ status: 'success',
46
+ message: `Transaction ${params.transactionId} committed successfully`
47
+ };
48
+ }
49
+ catch (error) {
50
+ return {
51
+ status: 'error',
52
+ error: error.message
53
+ };
54
+ }
55
+ }
56
+ /**
57
+ * Rollback a transaction
58
+ */
59
+ async rollbackTransaction(params) {
60
+ try {
61
+ if (!params.transactionId) {
62
+ return {
63
+ status: 'error',
64
+ error: 'Transaction ID is required'
65
+ };
66
+ }
67
+ await this.db.rollbackTransaction(params.transactionId);
68
+ return {
69
+ status: 'success',
70
+ message: `Transaction ${params.transactionId} rolled back successfully`
71
+ };
72
+ }
73
+ catch (error) {
74
+ return {
75
+ status: 'error',
76
+ error: error.message
77
+ };
78
+ }
79
+ }
80
+ /**
81
+ * Get status of active transactions
82
+ */
83
+ async getTransactionStatus() {
84
+ try {
85
+ const activeTransactions = this.db.getActiveTransactionIds();
86
+ return {
87
+ status: 'success',
88
+ activeTransactions,
89
+ message: `Found ${activeTransactions.length} active transaction(s)`
90
+ };
91
+ }
92
+ catch (error) {
93
+ return {
94
+ status: 'error',
95
+ error: error.message
96
+ };
97
+ }
98
+ }
99
+ /**
100
+ * Execute a query within a transaction
101
+ */
102
+ async executeInTransaction(params) {
103
+ try {
104
+ if (!params.transactionId) {
105
+ return {
106
+ status: 'error',
107
+ error: 'Transaction ID is required'
108
+ };
109
+ }
110
+ if (!params.query) {
111
+ return {
112
+ status: 'error',
113
+ error: 'Query is required'
114
+ };
115
+ }
116
+ const result = await this.db.executeInTransaction(params.transactionId, params.query, params.params);
117
+ return {
118
+ status: 'success',
119
+ data: result
120
+ };
121
+ }
122
+ catch (error) {
123
+ return {
124
+ status: 'error',
125
+ error: error.message
126
+ };
127
+ }
128
+ }
129
+ }
130
+ exports.TransactionTools = TransactionTools;
@@ -0,0 +1,30 @@
1
+ export declare class UtilityTools {
2
+ private db;
3
+ constructor();
4
+ /**
5
+ * Returns the current database connection info
6
+ */
7
+ describeConnection(): Promise<{
8
+ status: string;
9
+ data?: any;
10
+ error?: string;
11
+ }>;
12
+ /**
13
+ * Tests the DB connection and returns latency
14
+ */
15
+ testConnection(): Promise<{
16
+ status: string;
17
+ data?: any;
18
+ error?: string;
19
+ }>;
20
+ /**
21
+ * Detects and describes foreign key relationships between tables
22
+ */
23
+ getTableRelationships(params: {
24
+ table_name: string;
25
+ }): Promise<{
26
+ status: string;
27
+ data?: any;
28
+ error?: string;
29
+ }>;
30
+ }