@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,443 @@
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.CrudTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ const schemas_1 = require("../validation/schemas");
9
+ class CrudTools {
10
+ constructor(security) {
11
+ this.db = connection_1.default.getInstance();
12
+ this.security = security;
13
+ }
14
+ /**
15
+ * Create a new record in the specified table
16
+ */
17
+ async createRecord(params) {
18
+ // Validate input schema
19
+ if (!(0, schemas_1.validateCreateRecord)(params)) {
20
+ return {
21
+ status: 'error',
22
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateCreateRecord.errors)
23
+ };
24
+ }
25
+ try {
26
+ const { table_name, data } = params;
27
+ // Validate table name
28
+ const tableValidation = this.security.validateIdentifier(table_name);
29
+ if (!tableValidation.valid) {
30
+ return {
31
+ status: 'error',
32
+ error: `Invalid table name: ${tableValidation.error}`
33
+ };
34
+ }
35
+ // Validate column names
36
+ const columns = Object.keys(data);
37
+ for (const column of columns) {
38
+ const columnValidation = this.security.validateIdentifier(column);
39
+ if (!columnValidation.valid) {
40
+ return {
41
+ status: 'error',
42
+ error: `Invalid column name '${column}': ${columnValidation.error}`
43
+ };
44
+ }
45
+ }
46
+ // Validate and sanitize parameter values
47
+ const values = Object.values(data);
48
+ const paramValidation = this.security.validateParameters(values);
49
+ if (!paramValidation.valid) {
50
+ return {
51
+ status: 'error',
52
+ error: `Invalid parameter values: ${paramValidation.error}`
53
+ };
54
+ }
55
+ // Build the query with escaped identifiers
56
+ const escapedTableName = this.security.escapeIdentifier(table_name);
57
+ const escapedColumns = columns.map(col => this.security.escapeIdentifier(col));
58
+ const placeholders = columns.map(() => '?').join(', ');
59
+ const query = `INSERT INTO ${escapedTableName} (${escapedColumns.join(', ')}) VALUES (${placeholders})`;
60
+ // Execute the query with sanitized parameters
61
+ const result = await this.db.query(query, paramValidation.sanitizedParams);
62
+ return {
63
+ status: 'success',
64
+ data: {
65
+ insertId: result.insertId,
66
+ affectedRows: result.affectedRows
67
+ }
68
+ };
69
+ }
70
+ catch (error) {
71
+ return {
72
+ status: 'error',
73
+ error: error.message
74
+ };
75
+ }
76
+ }
77
+ /**
78
+ * Read records from the specified table with optional filters, pagination, and sorting
79
+ */
80
+ async readRecords(params) {
81
+ // Validate input schema
82
+ if (!(0, schemas_1.validateReadRecords)(params)) {
83
+ return {
84
+ status: 'error',
85
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateReadRecords.errors)
86
+ };
87
+ }
88
+ try {
89
+ const { table_name, filters, pagination, sorting } = params;
90
+ // Validate table name
91
+ const tableValidation = this.security.validateIdentifier(table_name);
92
+ if (!tableValidation.valid) {
93
+ return {
94
+ status: 'error',
95
+ error: `Invalid table name: ${tableValidation.error}`
96
+ };
97
+ }
98
+ // Validate sorting field if provided
99
+ if (sorting) {
100
+ const sortFieldValidation = this.security.validateIdentifier(sorting.field);
101
+ if (!sortFieldValidation.valid) {
102
+ return {
103
+ status: 'error',
104
+ error: `Invalid sorting field: ${sortFieldValidation.error}`
105
+ };
106
+ }
107
+ }
108
+ // Validate filter fields if provided
109
+ if (filters && filters.length > 0) {
110
+ for (const filter of filters) {
111
+ const fieldValidation = this.security.validateIdentifier(filter.field);
112
+ if (!fieldValidation.valid) {
113
+ return {
114
+ status: 'error',
115
+ error: `Invalid filter field '${filter.field}': ${fieldValidation.error}`
116
+ };
117
+ }
118
+ }
119
+ }
120
+ // Build the WHERE clause if filters are provided
121
+ let whereClause = '';
122
+ let queryParams = [];
123
+ if (filters && filters.length > 0) {
124
+ const conditions = filters.map(filter => {
125
+ const escapedField = this.security.escapeIdentifier(filter.field);
126
+ switch (filter.operator) {
127
+ case 'eq':
128
+ queryParams.push(filter.value);
129
+ return `${escapedField} = ?`;
130
+ case 'neq':
131
+ queryParams.push(filter.value);
132
+ return `${escapedField} != ?`;
133
+ case 'gt':
134
+ queryParams.push(filter.value);
135
+ return `${escapedField} > ?`;
136
+ case 'gte':
137
+ queryParams.push(filter.value);
138
+ return `${escapedField} >= ?`;
139
+ case 'lt':
140
+ queryParams.push(filter.value);
141
+ return `${escapedField} < ?`;
142
+ case 'lte':
143
+ queryParams.push(filter.value);
144
+ return `${escapedField} <= ?`;
145
+ case 'like':
146
+ queryParams.push(`%${filter.value}%`);
147
+ return `${escapedField} LIKE ?`;
148
+ case 'in':
149
+ if (Array.isArray(filter.value)) {
150
+ const placeholders = filter.value.map(() => '?').join(', ');
151
+ queryParams.push(...filter.value);
152
+ return `${escapedField} IN (${placeholders})`;
153
+ }
154
+ return '1=1'; // Default true condition if value is not an array
155
+ default:
156
+ return '1=1'; // Default true condition
157
+ }
158
+ });
159
+ whereClause = 'WHERE ' + conditions.join(' AND ');
160
+ }
161
+ // Validate all query parameters
162
+ const paramValidation = this.security.validateParameters(queryParams);
163
+ if (!paramValidation.valid) {
164
+ return {
165
+ status: 'error',
166
+ error: `Invalid query parameters: ${paramValidation.error}`
167
+ };
168
+ }
169
+ // Build the ORDER BY clause if sorting is provided
170
+ let orderByClause = '';
171
+ if (sorting) {
172
+ const escapedSortField = this.security.escapeIdentifier(sorting.field);
173
+ orderByClause = `ORDER BY ${escapedSortField} ${sorting.direction.toUpperCase()}`;
174
+ }
175
+ // Build the LIMIT clause if pagination is provided
176
+ let limitClause = '';
177
+ const escapedTableName = this.security.escapeIdentifier(table_name);
178
+ if (pagination) {
179
+ const offset = (pagination.page - 1) * pagination.limit;
180
+ limitClause = `LIMIT ${offset}, ${pagination.limit}`;
181
+ // Get total count for pagination
182
+ const countQuery = `SELECT COUNT(*) as total FROM ${escapedTableName} ${whereClause}`;
183
+ const countResult = await this.db.query(countQuery, paramValidation.sanitizedParams);
184
+ const total = countResult[0].total;
185
+ // Execute the main query with pagination
186
+ const query = `SELECT * FROM ${escapedTableName} ${whereClause} ${orderByClause} ${limitClause}`;
187
+ const results = await this.db.query(query, paramValidation.sanitizedParams);
188
+ return {
189
+ status: 'success',
190
+ data: results,
191
+ total
192
+ };
193
+ }
194
+ else {
195
+ // Execute the query without pagination
196
+ const query = `SELECT * FROM ${escapedTableName} ${whereClause} ${orderByClause}`;
197
+ const results = await this.db.query(query, paramValidation.sanitizedParams);
198
+ return {
199
+ status: 'success',
200
+ data: results,
201
+ total: results.length
202
+ };
203
+ }
204
+ }
205
+ catch (error) {
206
+ return {
207
+ status: 'error',
208
+ error: error.message
209
+ };
210
+ }
211
+ }
212
+ /**
213
+ * Update records in the specified table based on conditions
214
+ */
215
+ async updateRecord(params) {
216
+ // Validate input schema
217
+ if (!(0, schemas_1.validateUpdateRecord)(params)) {
218
+ return {
219
+ status: 'error',
220
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateUpdateRecord.errors)
221
+ };
222
+ }
223
+ try {
224
+ const { table_name, data, conditions } = params;
225
+ // Validate table name
226
+ const tableValidation = this.security.validateIdentifier(table_name);
227
+ if (!tableValidation.valid) {
228
+ return {
229
+ status: 'error',
230
+ error: `Invalid table name: ${tableValidation.error}`
231
+ };
232
+ }
233
+ // Validate column names in data
234
+ const columns = Object.keys(data);
235
+ for (const column of columns) {
236
+ const columnValidation = this.security.validateIdentifier(column);
237
+ if (!columnValidation.valid) {
238
+ return {
239
+ status: 'error',
240
+ error: `Invalid column name '${column}': ${columnValidation.error}`
241
+ };
242
+ }
243
+ }
244
+ // Validate condition fields
245
+ for (const condition of conditions) {
246
+ const fieldValidation = this.security.validateIdentifier(condition.field);
247
+ if (!fieldValidation.valid) {
248
+ return {
249
+ status: 'error',
250
+ error: `Invalid condition field '${condition.field}': ${fieldValidation.error}`
251
+ };
252
+ }
253
+ }
254
+ // Build SET clause with escaped identifiers
255
+ const setClause = Object.entries(data)
256
+ .map(([column, _]) => `${this.security.escapeIdentifier(column)} = ?`)
257
+ .join(', ');
258
+ const setValues = Object.values(data);
259
+ // Build the WHERE clause
260
+ const whereConditions = [];
261
+ const whereValues = [];
262
+ conditions.forEach(condition => {
263
+ const escapedField = this.security.escapeIdentifier(condition.field);
264
+ switch (condition.operator) {
265
+ case 'eq':
266
+ whereConditions.push(`${escapedField} = ?`);
267
+ whereValues.push(condition.value);
268
+ break;
269
+ case 'neq':
270
+ whereConditions.push(`${escapedField} != ?`);
271
+ whereValues.push(condition.value);
272
+ break;
273
+ case 'gt':
274
+ whereConditions.push(`${escapedField} > ?`);
275
+ whereValues.push(condition.value);
276
+ break;
277
+ case 'gte':
278
+ whereConditions.push(`${escapedField} >= ?`);
279
+ whereValues.push(condition.value);
280
+ break;
281
+ case 'lt':
282
+ whereConditions.push(`${escapedField} < ?`);
283
+ whereValues.push(condition.value);
284
+ break;
285
+ case 'lte':
286
+ whereConditions.push(`${escapedField} <= ?`);
287
+ whereValues.push(condition.value);
288
+ break;
289
+ case 'like':
290
+ whereConditions.push(`${escapedField} LIKE ?`);
291
+ whereValues.push(`%${condition.value}%`);
292
+ break;
293
+ case 'in':
294
+ if (Array.isArray(condition.value)) {
295
+ const placeholders = condition.value.map(() => '?').join(', ');
296
+ whereConditions.push(`${escapedField} IN (${placeholders})`);
297
+ whereValues.push(...condition.value);
298
+ }
299
+ break;
300
+ }
301
+ });
302
+ const whereClause = whereConditions.length > 0
303
+ ? 'WHERE ' + whereConditions.join(' AND ')
304
+ : '';
305
+ // Validate all parameters
306
+ const allParams = [...setValues, ...whereValues];
307
+ const paramValidation = this.security.validateParameters(allParams);
308
+ if (!paramValidation.valid) {
309
+ return {
310
+ status: 'error',
311
+ error: `Invalid parameters: ${paramValidation.error}`
312
+ };
313
+ }
314
+ // Build the query with escaped table name
315
+ const escapedTableName = this.security.escapeIdentifier(table_name);
316
+ const query = `UPDATE ${escapedTableName} SET ${setClause} ${whereClause}`;
317
+ // Execute the query with sanitized parameters
318
+ const result = await this.db.query(query, paramValidation.sanitizedParams);
319
+ return {
320
+ status: 'success',
321
+ data: {
322
+ affectedRows: result.affectedRows
323
+ }
324
+ };
325
+ }
326
+ catch (error) {
327
+ return {
328
+ status: 'error',
329
+ error: error.message
330
+ };
331
+ }
332
+ }
333
+ /**
334
+ * Delete records from the specified table based on conditions
335
+ */
336
+ async deleteRecord(params) {
337
+ // Validate input schema
338
+ if (!(0, schemas_1.validateDeleteRecord)(params)) {
339
+ return {
340
+ status: 'error',
341
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateDeleteRecord.errors)
342
+ };
343
+ }
344
+ try {
345
+ const { table_name, conditions } = params;
346
+ // Validate table name
347
+ const tableValidation = this.security.validateIdentifier(table_name);
348
+ if (!tableValidation.valid) {
349
+ return {
350
+ status: 'error',
351
+ error: `Invalid table name: ${tableValidation.error}`
352
+ };
353
+ }
354
+ // Ensure there are conditions for safety
355
+ if (!conditions || conditions.length === 0) {
356
+ return {
357
+ status: 'error',
358
+ error: 'DELETE operations require at least one condition for safety'
359
+ };
360
+ }
361
+ // Validate condition fields
362
+ for (const condition of conditions) {
363
+ const fieldValidation = this.security.validateIdentifier(condition.field);
364
+ if (!fieldValidation.valid) {
365
+ return {
366
+ status: 'error',
367
+ error: `Invalid condition field '${condition.field}': ${fieldValidation.error}`
368
+ };
369
+ }
370
+ }
371
+ // Build the WHERE clause
372
+ const whereConditions = [];
373
+ const whereValues = [];
374
+ conditions.forEach(condition => {
375
+ const escapedField = this.security.escapeIdentifier(condition.field);
376
+ switch (condition.operator) {
377
+ case 'eq':
378
+ whereConditions.push(`${escapedField} = ?`);
379
+ whereValues.push(condition.value);
380
+ break;
381
+ case 'neq':
382
+ whereConditions.push(`${escapedField} != ?`);
383
+ whereValues.push(condition.value);
384
+ break;
385
+ case 'gt':
386
+ whereConditions.push(`${escapedField} > ?`);
387
+ whereValues.push(condition.value);
388
+ break;
389
+ case 'gte':
390
+ whereConditions.push(`${escapedField} >= ?`);
391
+ whereValues.push(condition.value);
392
+ break;
393
+ case 'lt':
394
+ whereConditions.push(`${escapedField} < ?`);
395
+ whereValues.push(condition.value);
396
+ break;
397
+ case 'lte':
398
+ whereConditions.push(`${escapedField} <= ?`);
399
+ whereValues.push(condition.value);
400
+ break;
401
+ case 'like':
402
+ whereConditions.push(`${escapedField} LIKE ?`);
403
+ whereValues.push(`%${condition.value}%`);
404
+ break;
405
+ case 'in':
406
+ if (Array.isArray(condition.value)) {
407
+ const placeholders = condition.value.map(() => '?').join(', ');
408
+ whereConditions.push(`${escapedField} IN (${placeholders})`);
409
+ whereValues.push(...condition.value);
410
+ }
411
+ break;
412
+ }
413
+ });
414
+ const whereClause = 'WHERE ' + whereConditions.join(' AND ');
415
+ // Validate all parameters
416
+ const paramValidation = this.security.validateParameters(whereValues);
417
+ if (!paramValidation.valid) {
418
+ return {
419
+ status: 'error',
420
+ error: `Invalid parameters: ${paramValidation.error}`
421
+ };
422
+ }
423
+ // Build the query with escaped table name
424
+ const escapedTableName = this.security.escapeIdentifier(table_name);
425
+ const query = `DELETE FROM ${escapedTableName} ${whereClause}`;
426
+ // Execute the query with sanitized parameters
427
+ const result = await this.db.query(query, paramValidation.sanitizedParams);
428
+ return {
429
+ status: 'success',
430
+ data: {
431
+ affectedRows: result.affectedRows
432
+ }
433
+ };
434
+ }
435
+ catch (error) {
436
+ return {
437
+ status: 'error',
438
+ error: error.message
439
+ };
440
+ }
441
+ }
442
+ }
443
+ exports.CrudTools = CrudTools;
@@ -0,0 +1,33 @@
1
+ import { TableInfo, ColumnInfo } from '../validation/schemas';
2
+ export declare class DatabaseTools {
3
+ private db;
4
+ constructor();
5
+ /**
6
+ * List all available databases
7
+ */
8
+ listDatabases(): Promise<{
9
+ status: string;
10
+ data?: string[];
11
+ error?: string;
12
+ }>;
13
+ /**
14
+ * List all tables in the selected database
15
+ */
16
+ listTables(params: {
17
+ database?: string;
18
+ }): Promise<{
19
+ status: string;
20
+ data?: TableInfo[];
21
+ error?: string;
22
+ }>;
23
+ /**
24
+ * Read table schema (columns, types, keys, etc.)
25
+ */
26
+ readTableSchema(params: {
27
+ table_name: string;
28
+ }): Promise<{
29
+ status: string;
30
+ data?: ColumnInfo[];
31
+ error?: string;
32
+ }>;
33
+ }
@@ -0,0 +1,108 @@
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.DatabaseTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ const schemas_1 = require("../validation/schemas");
9
+ class DatabaseTools {
10
+ constructor() {
11
+ this.db = connection_1.default.getInstance();
12
+ }
13
+ /**
14
+ * List all available databases
15
+ */
16
+ async listDatabases() {
17
+ try {
18
+ const results = await this.db.query('SHOW DATABASES');
19
+ const databases = results.map(row => row.Database);
20
+ return {
21
+ status: 'success',
22
+ data: databases
23
+ };
24
+ }
25
+ catch (error) {
26
+ return {
27
+ status: 'error',
28
+ error: error.message
29
+ };
30
+ }
31
+ }
32
+ /**
33
+ * List all tables in the selected database
34
+ */
35
+ async listTables(params) {
36
+ // Validate input
37
+ if (!(0, schemas_1.validateListTables)(params)) {
38
+ return {
39
+ status: 'error',
40
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateListTables.errors)
41
+ };
42
+ }
43
+ try {
44
+ let query = 'SHOW TABLES';
45
+ // If database is specified, use it
46
+ if (params.database) {
47
+ query = `SHOW TABLES FROM \`${params.database}\``;
48
+ }
49
+ const results = await this.db.query(query);
50
+ const tables = results.map(row => {
51
+ // Extract the table name from the first column (which might have different names)
52
+ const firstColumnName = Object.keys(row)[0];
53
+ return { table_name: row[firstColumnName] };
54
+ });
55
+ return {
56
+ status: 'success',
57
+ data: tables
58
+ };
59
+ }
60
+ catch (error) {
61
+ return {
62
+ status: 'error',
63
+ error: error.message
64
+ };
65
+ }
66
+ }
67
+ /**
68
+ * Read table schema (columns, types, keys, etc.)
69
+ */
70
+ async readTableSchema(params) {
71
+ // Validate input
72
+ if (!(0, schemas_1.validateReadTableSchema)(params)) {
73
+ return {
74
+ status: 'error',
75
+ error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateReadTableSchema.errors)
76
+ };
77
+ }
78
+ try {
79
+ const query = `
80
+ SELECT
81
+ COLUMN_NAME as column_name,
82
+ DATA_TYPE as data_type,
83
+ IS_NULLABLE as is_nullable,
84
+ COLUMN_KEY as column_key,
85
+ COLUMN_DEFAULT as column_default,
86
+ EXTRA as extra
87
+ FROM
88
+ INFORMATION_SCHEMA.COLUMNS
89
+ WHERE
90
+ TABLE_NAME = ?
91
+ ORDER BY
92
+ ORDINAL_POSITION
93
+ `;
94
+ const results = await this.db.query(query, [params.table_name]);
95
+ return {
96
+ status: 'success',
97
+ data: results
98
+ };
99
+ }
100
+ catch (error) {
101
+ return {
102
+ status: 'error',
103
+ error: error.message
104
+ };
105
+ }
106
+ }
107
+ }
108
+ exports.DatabaseTools = DatabaseTools;
@@ -0,0 +1,69 @@
1
+ export declare class DdlTools {
2
+ private db;
3
+ constructor();
4
+ /**
5
+ * Create a new table
6
+ */
7
+ createTable(params: {
8
+ table_name: string;
9
+ columns: Array<{
10
+ name: string;
11
+ type: string;
12
+ nullable?: boolean;
13
+ primary_key?: boolean;
14
+ auto_increment?: boolean;
15
+ default?: string;
16
+ }>;
17
+ indexes?: Array<{
18
+ name: string;
19
+ columns: string[];
20
+ unique?: boolean;
21
+ }>;
22
+ }): Promise<{
23
+ status: string;
24
+ data?: any;
25
+ error?: string;
26
+ }>;
27
+ /**
28
+ * Alter an existing table
29
+ */
30
+ alterTable(params: {
31
+ table_name: string;
32
+ operations: Array<{
33
+ type: 'add_column' | 'drop_column' | 'modify_column' | 'rename_column' | 'add_index' | 'drop_index';
34
+ column_name?: string;
35
+ new_column_name?: string;
36
+ column_type?: string;
37
+ nullable?: boolean;
38
+ default?: string;
39
+ index_name?: string;
40
+ index_columns?: string[];
41
+ unique?: boolean;
42
+ }>;
43
+ }): Promise<{
44
+ status: string;
45
+ data?: any;
46
+ error?: string;
47
+ }>;
48
+ /**
49
+ * Drop a table
50
+ */
51
+ dropTable(params: {
52
+ table_name: string;
53
+ if_exists?: boolean;
54
+ }): Promise<{
55
+ status: string;
56
+ data?: any;
57
+ error?: string;
58
+ }>;
59
+ /**
60
+ * Execute raw DDL SQL
61
+ */
62
+ executeDdl(params: {
63
+ query: string;
64
+ }): Promise<{
65
+ status: string;
66
+ data?: any;
67
+ error?: string;
68
+ }>;
69
+ }