@berthojoris/mcp-mysql-server 1.5.0 → 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,108 @@
1
+ import { SecurityLayer } from '../security/securityLayer';
2
+ export declare class ConstraintTools {
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 foreign keys for a table
12
+ */
13
+ listForeignKeys(params: {
14
+ table_name: string;
15
+ database?: string;
16
+ }): Promise<{
17
+ status: string;
18
+ data?: any[];
19
+ error?: string;
20
+ queryLog?: string;
21
+ }>;
22
+ /**
23
+ * List all constraints (PK, FK, UNIQUE, CHECK) for a table
24
+ */
25
+ listConstraints(params: {
26
+ table_name: string;
27
+ database?: string;
28
+ }): Promise<{
29
+ status: string;
30
+ data?: any[];
31
+ error?: string;
32
+ queryLog?: string;
33
+ }>;
34
+ /**
35
+ * Add a foreign key constraint
36
+ */
37
+ addForeignKey(params: {
38
+ table_name: string;
39
+ constraint_name: string;
40
+ columns: string[];
41
+ referenced_table: string;
42
+ referenced_columns: string[];
43
+ on_delete?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | 'SET DEFAULT';
44
+ on_update?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | 'SET DEFAULT';
45
+ database?: string;
46
+ }): Promise<{
47
+ status: string;
48
+ data?: any;
49
+ error?: string;
50
+ queryLog?: string;
51
+ }>;
52
+ /**
53
+ * Drop a foreign key constraint
54
+ */
55
+ dropForeignKey(params: {
56
+ table_name: string;
57
+ constraint_name: string;
58
+ database?: string;
59
+ }): Promise<{
60
+ status: string;
61
+ message?: string;
62
+ error?: string;
63
+ queryLog?: string;
64
+ }>;
65
+ /**
66
+ * Add a unique constraint
67
+ */
68
+ addUniqueConstraint(params: {
69
+ table_name: string;
70
+ constraint_name: string;
71
+ columns: string[];
72
+ database?: string;
73
+ }): Promise<{
74
+ status: string;
75
+ data?: any;
76
+ error?: string;
77
+ queryLog?: string;
78
+ }>;
79
+ /**
80
+ * Drop a constraint (UNIQUE or CHECK)
81
+ */
82
+ dropConstraint(params: {
83
+ table_name: string;
84
+ constraint_name: string;
85
+ constraint_type: 'UNIQUE' | 'CHECK';
86
+ database?: string;
87
+ }): Promise<{
88
+ status: string;
89
+ message?: string;
90
+ error?: string;
91
+ queryLog?: string;
92
+ }>;
93
+ /**
94
+ * Add a check constraint (MySQL 8.0.16+)
95
+ */
96
+ addCheckConstraint(params: {
97
+ table_name: string;
98
+ constraint_name: string;
99
+ expression: string;
100
+ enforced?: boolean;
101
+ database?: string;
102
+ }): Promise<{
103
+ status: string;
104
+ data?: any;
105
+ error?: string;
106
+ queryLog?: string;
107
+ }>;
108
+ }
@@ -0,0 +1,405 @@
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.ConstraintTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ const config_1 = require("../config/config");
9
+ class ConstraintTools {
10
+ constructor(security) {
11
+ this.db = connection_1.default.getInstance();
12
+ this.security = security;
13
+ }
14
+ /**
15
+ * Validate database access - ensures only the connected database can be accessed
16
+ */
17
+ validateDatabaseAccess(requestedDatabase) {
18
+ const connectedDatabase = config_1.dbConfig.database;
19
+ if (!connectedDatabase) {
20
+ return {
21
+ valid: false,
22
+ database: '',
23
+ error: 'No database specified in connection string. Cannot access any database.'
24
+ };
25
+ }
26
+ if (!requestedDatabase) {
27
+ return {
28
+ valid: true,
29
+ database: connectedDatabase
30
+ };
31
+ }
32
+ if (requestedDatabase !== connectedDatabase) {
33
+ return {
34
+ valid: false,
35
+ database: '',
36
+ error: `Access denied. You can only access the connected database '${connectedDatabase}'. Requested database '${requestedDatabase}' is not allowed.`
37
+ };
38
+ }
39
+ return {
40
+ valid: true,
41
+ database: connectedDatabase
42
+ };
43
+ }
44
+ /**
45
+ * List all foreign keys for a table
46
+ */
47
+ async listForeignKeys(params) {
48
+ try {
49
+ const dbValidation = this.validateDatabaseAccess(params?.database);
50
+ if (!dbValidation.valid) {
51
+ return { status: 'error', error: dbValidation.error };
52
+ }
53
+ const { table_name } = params;
54
+ const database = dbValidation.database;
55
+ // Validate table name
56
+ if (!this.security.validateIdentifier(table_name).valid) {
57
+ return { status: 'error', error: 'Invalid table name' };
58
+ }
59
+ const query = `
60
+ SELECT
61
+ kcu.CONSTRAINT_NAME as constraint_name,
62
+ kcu.COLUMN_NAME as column_name,
63
+ kcu.REFERENCED_TABLE_SCHEMA as referenced_schema,
64
+ kcu.REFERENCED_TABLE_NAME as referenced_table,
65
+ kcu.REFERENCED_COLUMN_NAME as referenced_column,
66
+ kcu.ORDINAL_POSITION as ordinal_position,
67
+ rc.UPDATE_RULE as on_update,
68
+ rc.DELETE_RULE as on_delete
69
+ FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
70
+ JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
71
+ ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
72
+ AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
73
+ WHERE kcu.TABLE_SCHEMA = ?
74
+ AND kcu.TABLE_NAME = ?
75
+ AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
76
+ ORDER BY kcu.CONSTRAINT_NAME, kcu.ORDINAL_POSITION
77
+ `;
78
+ const results = await this.db.query(query, [database, table_name]);
79
+ // Group by constraint name
80
+ const fkMap = new Map();
81
+ for (const row of results) {
82
+ const constraintName = row.constraint_name;
83
+ if (!fkMap.has(constraintName)) {
84
+ fkMap.set(constraintName, {
85
+ constraint_name: constraintName,
86
+ table_name: table_name,
87
+ referenced_schema: row.referenced_schema,
88
+ referenced_table: row.referenced_table,
89
+ on_update: row.on_update,
90
+ on_delete: row.on_delete,
91
+ columns: []
92
+ });
93
+ }
94
+ fkMap.get(constraintName).columns.push({
95
+ column_name: row.column_name,
96
+ referenced_column: row.referenced_column,
97
+ ordinal_position: row.ordinal_position
98
+ });
99
+ }
100
+ return {
101
+ status: 'success',
102
+ data: Array.from(fkMap.values()),
103
+ queryLog: this.db.getFormattedQueryLogs(1)
104
+ };
105
+ }
106
+ catch (error) {
107
+ return {
108
+ status: 'error',
109
+ error: error.message,
110
+ queryLog: this.db.getFormattedQueryLogs(1)
111
+ };
112
+ }
113
+ }
114
+ /**
115
+ * List all constraints (PK, FK, UNIQUE, CHECK) for a table
116
+ */
117
+ async listConstraints(params) {
118
+ try {
119
+ const dbValidation = this.validateDatabaseAccess(params?.database);
120
+ if (!dbValidation.valid) {
121
+ return { status: 'error', error: dbValidation.error };
122
+ }
123
+ const { table_name } = params;
124
+ const database = dbValidation.database;
125
+ // Validate table name
126
+ if (!this.security.validateIdentifier(table_name).valid) {
127
+ return { status: 'error', error: 'Invalid table name' };
128
+ }
129
+ const query = `
130
+ SELECT
131
+ tc.CONSTRAINT_NAME as constraint_name,
132
+ tc.CONSTRAINT_TYPE as constraint_type,
133
+ GROUP_CONCAT(kcu.COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION) as columns,
134
+ kcu.REFERENCED_TABLE_NAME as referenced_table,
135
+ GROUP_CONCAT(kcu.REFERENCED_COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION) as referenced_columns
136
+ FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
137
+ LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
138
+ ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
139
+ AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA
140
+ AND tc.TABLE_NAME = kcu.TABLE_NAME
141
+ WHERE tc.TABLE_SCHEMA = ? AND tc.TABLE_NAME = ?
142
+ GROUP BY tc.CONSTRAINT_NAME, tc.CONSTRAINT_TYPE, kcu.REFERENCED_TABLE_NAME
143
+ ORDER BY tc.CONSTRAINT_TYPE, tc.CONSTRAINT_NAME
144
+ `;
145
+ const results = await this.db.query(query, [database, table_name]);
146
+ // Format results
147
+ const constraints = results.map(row => ({
148
+ constraint_name: row.constraint_name,
149
+ constraint_type: row.constraint_type,
150
+ columns: row.columns ? row.columns.split(',') : [],
151
+ referenced_table: row.referenced_table || null,
152
+ referenced_columns: row.referenced_columns ? row.referenced_columns.split(',') : null
153
+ }));
154
+ return {
155
+ status: 'success',
156
+ data: constraints,
157
+ queryLog: this.db.getFormattedQueryLogs(1)
158
+ };
159
+ }
160
+ catch (error) {
161
+ return {
162
+ status: 'error',
163
+ error: error.message,
164
+ queryLog: this.db.getFormattedQueryLogs(1)
165
+ };
166
+ }
167
+ }
168
+ /**
169
+ * Add a foreign key constraint
170
+ */
171
+ async addForeignKey(params) {
172
+ try {
173
+ const dbValidation = this.validateDatabaseAccess(params?.database);
174
+ if (!dbValidation.valid) {
175
+ return { status: 'error', error: dbValidation.error };
176
+ }
177
+ const { table_name, constraint_name, columns, referenced_table, referenced_columns, on_delete = 'RESTRICT', on_update = 'RESTRICT' } = params;
178
+ const database = dbValidation.database;
179
+ // Validate names
180
+ if (!this.security.validateIdentifier(table_name).valid) {
181
+ return { status: 'error', error: 'Invalid table name' };
182
+ }
183
+ if (!this.security.validateIdentifier(constraint_name).valid) {
184
+ return { status: 'error', error: 'Invalid constraint name' };
185
+ }
186
+ if (!this.security.validateIdentifier(referenced_table).valid) {
187
+ return { status: 'error', error: 'Invalid referenced table name' };
188
+ }
189
+ // Validate column names
190
+ for (const col of columns) {
191
+ if (!this.security.validateIdentifier(col).valid) {
192
+ return { status: 'error', error: `Invalid column name: ${col}` };
193
+ }
194
+ }
195
+ for (const col of referenced_columns) {
196
+ if (!this.security.validateIdentifier(col).valid) {
197
+ return { status: 'error', error: `Invalid referenced column name: ${col}` };
198
+ }
199
+ }
200
+ // Column counts must match
201
+ if (columns.length !== referenced_columns.length) {
202
+ return { status: 'error', error: 'Column count must match referenced column count' };
203
+ }
204
+ const columnList = columns.map(c => `\`${c}\``).join(', ');
205
+ const refColumnList = referenced_columns.map(c => `\`${c}\``).join(', ');
206
+ const alterQuery = `
207
+ ALTER TABLE \`${database}\`.\`${table_name}\`
208
+ ADD CONSTRAINT \`${constraint_name}\`
209
+ FOREIGN KEY (${columnList})
210
+ REFERENCES \`${database}\`.\`${referenced_table}\` (${refColumnList})
211
+ ON DELETE ${on_delete}
212
+ ON UPDATE ${on_update}
213
+ `;
214
+ await this.db.query(alterQuery);
215
+ return {
216
+ status: 'success',
217
+ data: {
218
+ message: `Foreign key '${constraint_name}' added successfully`,
219
+ constraint_name,
220
+ table_name,
221
+ referenced_table,
222
+ database
223
+ },
224
+ queryLog: this.db.getFormattedQueryLogs(1)
225
+ };
226
+ }
227
+ catch (error) {
228
+ return {
229
+ status: 'error',
230
+ error: error.message,
231
+ queryLog: this.db.getFormattedQueryLogs(1)
232
+ };
233
+ }
234
+ }
235
+ /**
236
+ * Drop a foreign key constraint
237
+ */
238
+ async dropForeignKey(params) {
239
+ try {
240
+ const dbValidation = this.validateDatabaseAccess(params?.database);
241
+ if (!dbValidation.valid) {
242
+ return { status: 'error', error: dbValidation.error };
243
+ }
244
+ const { table_name, constraint_name } = params;
245
+ const database = dbValidation.database;
246
+ // Validate names
247
+ if (!this.security.validateIdentifier(table_name).valid) {
248
+ return { status: 'error', error: 'Invalid table name' };
249
+ }
250
+ if (!this.security.validateIdentifier(constraint_name).valid) {
251
+ return { status: 'error', error: 'Invalid constraint name' };
252
+ }
253
+ const alterQuery = `ALTER TABLE \`${database}\`.\`${table_name}\` DROP FOREIGN KEY \`${constraint_name}\``;
254
+ await this.db.query(alterQuery);
255
+ return {
256
+ status: 'success',
257
+ message: `Foreign key '${constraint_name}' dropped successfully from table '${table_name}'`,
258
+ queryLog: this.db.getFormattedQueryLogs(1)
259
+ };
260
+ }
261
+ catch (error) {
262
+ return {
263
+ status: 'error',
264
+ error: error.message,
265
+ queryLog: this.db.getFormattedQueryLogs(1)
266
+ };
267
+ }
268
+ }
269
+ /**
270
+ * Add a unique constraint
271
+ */
272
+ async addUniqueConstraint(params) {
273
+ try {
274
+ const dbValidation = this.validateDatabaseAccess(params?.database);
275
+ if (!dbValidation.valid) {
276
+ return { status: 'error', error: dbValidation.error };
277
+ }
278
+ const { table_name, constraint_name, columns } = params;
279
+ const database = dbValidation.database;
280
+ // Validate names
281
+ if (!this.security.validateIdentifier(table_name).valid) {
282
+ return { status: 'error', error: 'Invalid table name' };
283
+ }
284
+ if (!this.security.validateIdentifier(constraint_name).valid) {
285
+ return { status: 'error', error: 'Invalid constraint name' };
286
+ }
287
+ // Validate column names
288
+ for (const col of columns) {
289
+ if (!this.security.validateIdentifier(col).valid) {
290
+ return { status: 'error', error: `Invalid column name: ${col}` };
291
+ }
292
+ }
293
+ const columnList = columns.map(c => `\`${c}\``).join(', ');
294
+ const alterQuery = `ALTER TABLE \`${database}\`.\`${table_name}\` ADD CONSTRAINT \`${constraint_name}\` UNIQUE (${columnList})`;
295
+ await this.db.query(alterQuery);
296
+ return {
297
+ status: 'success',
298
+ data: {
299
+ message: `Unique constraint '${constraint_name}' added successfully`,
300
+ constraint_name,
301
+ table_name,
302
+ columns,
303
+ database
304
+ },
305
+ queryLog: this.db.getFormattedQueryLogs(1)
306
+ };
307
+ }
308
+ catch (error) {
309
+ return {
310
+ status: 'error',
311
+ error: error.message,
312
+ queryLog: this.db.getFormattedQueryLogs(1)
313
+ };
314
+ }
315
+ }
316
+ /**
317
+ * Drop a constraint (UNIQUE or CHECK)
318
+ */
319
+ async dropConstraint(params) {
320
+ try {
321
+ const dbValidation = this.validateDatabaseAccess(params?.database);
322
+ if (!dbValidation.valid) {
323
+ return { status: 'error', error: dbValidation.error };
324
+ }
325
+ const { table_name, constraint_name, constraint_type } = params;
326
+ const database = dbValidation.database;
327
+ // Validate names
328
+ if (!this.security.validateIdentifier(table_name).valid) {
329
+ return { status: 'error', error: 'Invalid table name' };
330
+ }
331
+ if (!this.security.validateIdentifier(constraint_name).valid) {
332
+ return { status: 'error', error: 'Invalid constraint name' };
333
+ }
334
+ let alterQuery;
335
+ if (constraint_type === 'UNIQUE') {
336
+ // UNIQUE constraints are implemented as indexes in MySQL
337
+ alterQuery = `ALTER TABLE \`${database}\`.\`${table_name}\` DROP INDEX \`${constraint_name}\``;
338
+ }
339
+ else if (constraint_type === 'CHECK') {
340
+ alterQuery = `ALTER TABLE \`${database}\`.\`${table_name}\` DROP CHECK \`${constraint_name}\``;
341
+ }
342
+ else {
343
+ return { status: 'error', error: 'Invalid constraint type. Use UNIQUE or CHECK.' };
344
+ }
345
+ await this.db.query(alterQuery);
346
+ return {
347
+ status: 'success',
348
+ message: `${constraint_type} constraint '${constraint_name}' dropped successfully from table '${table_name}'`,
349
+ queryLog: this.db.getFormattedQueryLogs(1)
350
+ };
351
+ }
352
+ catch (error) {
353
+ return {
354
+ status: 'error',
355
+ error: error.message,
356
+ queryLog: this.db.getFormattedQueryLogs(1)
357
+ };
358
+ }
359
+ }
360
+ /**
361
+ * Add a check constraint (MySQL 8.0.16+)
362
+ */
363
+ async addCheckConstraint(params) {
364
+ try {
365
+ const dbValidation = this.validateDatabaseAccess(params?.database);
366
+ if (!dbValidation.valid) {
367
+ return { status: 'error', error: dbValidation.error };
368
+ }
369
+ const { table_name, constraint_name, expression, enforced = true } = params;
370
+ const database = dbValidation.database;
371
+ // Validate names
372
+ if (!this.security.validateIdentifier(table_name).valid) {
373
+ return { status: 'error', error: 'Invalid table name' };
374
+ }
375
+ if (!this.security.validateIdentifier(constraint_name).valid) {
376
+ return { status: 'error', error: 'Invalid constraint name' };
377
+ }
378
+ let alterQuery = `ALTER TABLE \`${database}\`.\`${table_name}\` ADD CONSTRAINT \`${constraint_name}\` CHECK (${expression})`;
379
+ if (!enforced) {
380
+ alterQuery += ' NOT ENFORCED';
381
+ }
382
+ await this.db.query(alterQuery);
383
+ return {
384
+ status: 'success',
385
+ data: {
386
+ message: `Check constraint '${constraint_name}' added successfully`,
387
+ constraint_name,
388
+ table_name,
389
+ expression,
390
+ enforced,
391
+ database
392
+ },
393
+ queryLog: this.db.getFormattedQueryLogs(1)
394
+ };
395
+ }
396
+ catch (error) {
397
+ return {
398
+ status: 'error',
399
+ error: error.message,
400
+ queryLog: this.db.getFormattedQueryLogs(1)
401
+ };
402
+ }
403
+ }
404
+ }
405
+ exports.ConstraintTools = ConstraintTools;
@@ -0,0 +1,93 @@
1
+ import { SecurityLayer } from '../security/securityLayer';
2
+ export declare class FunctionTools {
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 functions in the current database
12
+ */
13
+ listFunctions(params: {
14
+ database?: string;
15
+ }): Promise<{
16
+ status: string;
17
+ data?: any[];
18
+ error?: string;
19
+ queryLog?: string;
20
+ }>;
21
+ /**
22
+ * Get detailed information about a specific function
23
+ */
24
+ getFunctionInfo(params: {
25
+ function_name: string;
26
+ database?: string;
27
+ }): Promise<{
28
+ status: string;
29
+ data?: any;
30
+ error?: string;
31
+ queryLog?: string;
32
+ }>;
33
+ /**
34
+ * Create a new function
35
+ */
36
+ createFunction(params: {
37
+ function_name: string;
38
+ parameters?: Array<{
39
+ name: string;
40
+ data_type: string;
41
+ }>;
42
+ returns: string;
43
+ body: string;
44
+ deterministic?: boolean;
45
+ data_access?: 'CONTAINS SQL' | 'NO SQL' | 'READS SQL DATA' | 'MODIFIES SQL DATA';
46
+ security?: 'DEFINER' | 'INVOKER';
47
+ comment?: string;
48
+ database?: string;
49
+ }): Promise<{
50
+ status: string;
51
+ data?: any;
52
+ error?: string;
53
+ queryLog?: string;
54
+ }>;
55
+ /**
56
+ * Drop a function
57
+ */
58
+ dropFunction(params: {
59
+ function_name: string;
60
+ if_exists?: boolean;
61
+ database?: string;
62
+ }): Promise<{
63
+ status: string;
64
+ message?: string;
65
+ error?: string;
66
+ queryLog?: string;
67
+ }>;
68
+ /**
69
+ * Show the CREATE statement for a function
70
+ */
71
+ showCreateFunction(params: {
72
+ function_name: string;
73
+ database?: string;
74
+ }): Promise<{
75
+ status: string;
76
+ data?: any;
77
+ error?: string;
78
+ queryLog?: string;
79
+ }>;
80
+ /**
81
+ * Execute a function and return its result
82
+ */
83
+ executeFunction(params: {
84
+ function_name: string;
85
+ parameters?: any[];
86
+ database?: string;
87
+ }): Promise<{
88
+ status: string;
89
+ data?: any;
90
+ error?: string;
91
+ queryLog?: string;
92
+ }>;
93
+ }