@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.
- package/DOCUMENTATIONS.md +664 -22
- package/README.md +102 -5
- package/dist/cache/queryCache.d.ts +126 -0
- package/dist/cache/queryCache.js +337 -0
- package/dist/config/featureConfig.js +82 -71
- package/dist/db/connection.d.ts +21 -2
- package/dist/db/connection.js +73 -7
- package/dist/db/queryLogger.d.ts +3 -2
- package/dist/db/queryLogger.js +64 -43
- package/dist/index.d.ts +477 -3
- package/dist/index.js +472 -70
- package/dist/mcp-server.js +1352 -124
- package/dist/optimization/queryOptimizer.d.ts +125 -0
- package/dist/optimization/queryOptimizer.js +509 -0
- package/dist/tools/constraintTools.d.ts +108 -0
- package/dist/tools/constraintTools.js +405 -0
- package/dist/tools/functionTools.d.ts +93 -0
- package/dist/tools/functionTools.js +351 -0
- package/dist/tools/indexTools.d.ts +81 -0
- package/dist/tools/indexTools.js +345 -0
- package/dist/tools/maintenanceTools.d.ts +111 -0
- package/dist/tools/maintenanceTools.js +371 -0
- package/dist/tools/processTools.d.ts +106 -0
- package/dist/tools/processTools.js +305 -0
- package/dist/tools/queryTools.d.ts +14 -1
- package/dist/tools/queryTools.js +27 -3
- package/dist/tools/triggerTools.d.ts +76 -0
- package/dist/tools/triggerTools.js +294 -0
- package/dist/tools/viewTools.d.ts +91 -0
- package/dist/tools/viewTools.js +330 -0
- package/package.json +1 -1
|
@@ -0,0 +1,351 @@
|
|
|
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.FunctionTools = void 0;
|
|
7
|
+
const connection_1 = __importDefault(require("../db/connection"));
|
|
8
|
+
const config_1 = require("../config/config");
|
|
9
|
+
class FunctionTools {
|
|
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 functions in the current database
|
|
46
|
+
*/
|
|
47
|
+
async listFunctions(params) {
|
|
48
|
+
try {
|
|
49
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
50
|
+
if (!dbValidation.valid) {
|
|
51
|
+
return { status: 'error', error: dbValidation.error };
|
|
52
|
+
}
|
|
53
|
+
const database = dbValidation.database;
|
|
54
|
+
const query = `
|
|
55
|
+
SELECT
|
|
56
|
+
ROUTINE_NAME as function_name,
|
|
57
|
+
DATA_TYPE as return_type,
|
|
58
|
+
DTD_IDENTIFIER as return_type_full,
|
|
59
|
+
ROUTINE_DEFINITION as definition,
|
|
60
|
+
IS_DETERMINISTIC as is_deterministic,
|
|
61
|
+
SQL_DATA_ACCESS as data_access,
|
|
62
|
+
SECURITY_TYPE as security_type,
|
|
63
|
+
DEFINER as definer,
|
|
64
|
+
CREATED as created,
|
|
65
|
+
LAST_ALTERED as last_altered,
|
|
66
|
+
ROUTINE_COMMENT as comment
|
|
67
|
+
FROM INFORMATION_SCHEMA.ROUTINES
|
|
68
|
+
WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'FUNCTION'
|
|
69
|
+
ORDER BY ROUTINE_NAME
|
|
70
|
+
`;
|
|
71
|
+
const results = await this.db.query(query, [database]);
|
|
72
|
+
return {
|
|
73
|
+
status: 'success',
|
|
74
|
+
data: results,
|
|
75
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
status: 'error',
|
|
81
|
+
error: error.message,
|
|
82
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get detailed information about a specific function
|
|
88
|
+
*/
|
|
89
|
+
async getFunctionInfo(params) {
|
|
90
|
+
try {
|
|
91
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
92
|
+
if (!dbValidation.valid) {
|
|
93
|
+
return { status: 'error', error: dbValidation.error };
|
|
94
|
+
}
|
|
95
|
+
const { function_name } = params;
|
|
96
|
+
const database = dbValidation.database;
|
|
97
|
+
// Validate function name
|
|
98
|
+
const identifierValidation = this.security.validateIdentifier(function_name);
|
|
99
|
+
if (!identifierValidation.valid) {
|
|
100
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid function name' };
|
|
101
|
+
}
|
|
102
|
+
// Get function information
|
|
103
|
+
const functionQuery = `
|
|
104
|
+
SELECT
|
|
105
|
+
ROUTINE_NAME as function_name,
|
|
106
|
+
DATA_TYPE as return_type,
|
|
107
|
+
DTD_IDENTIFIER as return_type_full,
|
|
108
|
+
ROUTINE_DEFINITION as definition,
|
|
109
|
+
IS_DETERMINISTIC as is_deterministic,
|
|
110
|
+
SQL_DATA_ACCESS as data_access,
|
|
111
|
+
SECURITY_TYPE as security_type,
|
|
112
|
+
DEFINER as definer,
|
|
113
|
+
CREATED as created,
|
|
114
|
+
LAST_ALTERED as last_altered,
|
|
115
|
+
ROUTINE_COMMENT as comment,
|
|
116
|
+
SQL_MODE as sql_mode,
|
|
117
|
+
CHARACTER_SET_CLIENT as charset,
|
|
118
|
+
COLLATION_CONNECTION as collation,
|
|
119
|
+
DATABASE_COLLATION as db_collation
|
|
120
|
+
FROM INFORMATION_SCHEMA.ROUTINES
|
|
121
|
+
WHERE ROUTINE_SCHEMA = ? AND ROUTINE_NAME = ? AND ROUTINE_TYPE = 'FUNCTION'
|
|
122
|
+
`;
|
|
123
|
+
// Get function parameters
|
|
124
|
+
const parametersQuery = `
|
|
125
|
+
SELECT
|
|
126
|
+
PARAMETER_NAME as name,
|
|
127
|
+
DATA_TYPE as data_type,
|
|
128
|
+
DTD_IDENTIFIER as data_type_full,
|
|
129
|
+
CHARACTER_MAXIMUM_LENGTH as max_length,
|
|
130
|
+
NUMERIC_PRECISION as numeric_precision,
|
|
131
|
+
NUMERIC_SCALE as numeric_scale,
|
|
132
|
+
ORDINAL_POSITION as position
|
|
133
|
+
FROM INFORMATION_SCHEMA.PARAMETERS
|
|
134
|
+
WHERE SPECIFIC_SCHEMA = ? AND SPECIFIC_NAME = ? AND PARAMETER_MODE IS NOT NULL
|
|
135
|
+
ORDER BY ORDINAL_POSITION
|
|
136
|
+
`;
|
|
137
|
+
const [functionInfo, parameters] = await Promise.all([
|
|
138
|
+
this.db.query(functionQuery, [database, function_name]),
|
|
139
|
+
this.db.query(parametersQuery, [database, function_name])
|
|
140
|
+
]);
|
|
141
|
+
if (functionInfo.length === 0) {
|
|
142
|
+
return {
|
|
143
|
+
status: 'error',
|
|
144
|
+
error: `Function '${function_name}' not found in database '${database}'`,
|
|
145
|
+
queryLog: this.db.getFormattedQueryLogs(2)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
status: 'success',
|
|
150
|
+
data: {
|
|
151
|
+
...functionInfo[0],
|
|
152
|
+
parameters: parameters
|
|
153
|
+
},
|
|
154
|
+
queryLog: this.db.getFormattedQueryLogs(2)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return {
|
|
159
|
+
status: 'error',
|
|
160
|
+
error: error.message,
|
|
161
|
+
queryLog: this.db.getFormattedQueryLogs(2)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Create a new function
|
|
167
|
+
*/
|
|
168
|
+
async createFunction(params) {
|
|
169
|
+
try {
|
|
170
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
171
|
+
if (!dbValidation.valid) {
|
|
172
|
+
return { status: 'error', error: dbValidation.error };
|
|
173
|
+
}
|
|
174
|
+
const { function_name, parameters = [], returns, body, deterministic = false, data_access, security, comment } = params;
|
|
175
|
+
const database = dbValidation.database;
|
|
176
|
+
// Validate function name
|
|
177
|
+
const identifierValidation = this.security.validateIdentifier(function_name);
|
|
178
|
+
if (!identifierValidation.valid) {
|
|
179
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid function name' };
|
|
180
|
+
}
|
|
181
|
+
// Build parameter list
|
|
182
|
+
const parameterList = parameters.map(param => {
|
|
183
|
+
if (!this.security.validateIdentifier(param.name).valid) {
|
|
184
|
+
throw new Error(`Invalid parameter name: ${param.name}`);
|
|
185
|
+
}
|
|
186
|
+
return `\`${param.name}\` ${param.data_type}`;
|
|
187
|
+
}).join(', ');
|
|
188
|
+
// Build CREATE FUNCTION statement
|
|
189
|
+
let createQuery = `CREATE FUNCTION \`${database}\`.\`${function_name}\`(${parameterList})\n`;
|
|
190
|
+
createQuery += `RETURNS ${returns}\n`;
|
|
191
|
+
if (deterministic) {
|
|
192
|
+
createQuery += `DETERMINISTIC\n`;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
createQuery += `NOT DETERMINISTIC\n`;
|
|
196
|
+
}
|
|
197
|
+
if (data_access) {
|
|
198
|
+
createQuery += `${data_access}\n`;
|
|
199
|
+
}
|
|
200
|
+
if (security) {
|
|
201
|
+
createQuery += `SQL SECURITY ${security}\n`;
|
|
202
|
+
}
|
|
203
|
+
if (comment) {
|
|
204
|
+
createQuery += `COMMENT '${comment.replace(/'/g, "''")}'\n`;
|
|
205
|
+
}
|
|
206
|
+
// Check if body already contains BEGIN/END
|
|
207
|
+
const trimmedBody = body.trim();
|
|
208
|
+
if (trimmedBody.toUpperCase().startsWith('BEGIN') && trimmedBody.toUpperCase().endsWith('END')) {
|
|
209
|
+
createQuery += body;
|
|
210
|
+
}
|
|
211
|
+
else if (trimmedBody.toUpperCase().startsWith('RETURN')) {
|
|
212
|
+
// Simple one-liner function
|
|
213
|
+
createQuery += body;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
createQuery += `BEGIN\n${body}\nEND`;
|
|
217
|
+
}
|
|
218
|
+
await this.db.query(createQuery);
|
|
219
|
+
return {
|
|
220
|
+
status: 'success',
|
|
221
|
+
data: {
|
|
222
|
+
message: `Function '${function_name}' created successfully`,
|
|
223
|
+
function_name,
|
|
224
|
+
database
|
|
225
|
+
},
|
|
226
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
return {
|
|
231
|
+
status: 'error',
|
|
232
|
+
error: error.message,
|
|
233
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Drop a function
|
|
239
|
+
*/
|
|
240
|
+
async dropFunction(params) {
|
|
241
|
+
try {
|
|
242
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
243
|
+
if (!dbValidation.valid) {
|
|
244
|
+
return { status: 'error', error: dbValidation.error };
|
|
245
|
+
}
|
|
246
|
+
const { function_name, if_exists = false } = params;
|
|
247
|
+
const database = dbValidation.database;
|
|
248
|
+
// Validate function name
|
|
249
|
+
const identifierValidation = this.security.validateIdentifier(function_name);
|
|
250
|
+
if (!identifierValidation.valid) {
|
|
251
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid function name' };
|
|
252
|
+
}
|
|
253
|
+
const dropQuery = `DROP FUNCTION ${if_exists ? 'IF EXISTS' : ''} \`${database}\`.\`${function_name}\``;
|
|
254
|
+
await this.db.query(dropQuery);
|
|
255
|
+
return {
|
|
256
|
+
status: 'success',
|
|
257
|
+
message: `Function '${function_name}' dropped successfully`,
|
|
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
|
+
* Show the CREATE statement for a function
|
|
271
|
+
*/
|
|
272
|
+
async showCreateFunction(params) {
|
|
273
|
+
try {
|
|
274
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
275
|
+
if (!dbValidation.valid) {
|
|
276
|
+
return { status: 'error', error: dbValidation.error };
|
|
277
|
+
}
|
|
278
|
+
const { function_name } = params;
|
|
279
|
+
const database = dbValidation.database;
|
|
280
|
+
// Validate function name
|
|
281
|
+
const identifierValidation = this.security.validateIdentifier(function_name);
|
|
282
|
+
if (!identifierValidation.valid) {
|
|
283
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid function name' };
|
|
284
|
+
}
|
|
285
|
+
const query = `SHOW CREATE FUNCTION \`${database}\`.\`${function_name}\``;
|
|
286
|
+
const results = await this.db.query(query);
|
|
287
|
+
if (results.length === 0) {
|
|
288
|
+
return {
|
|
289
|
+
status: 'error',
|
|
290
|
+
error: `Function '${function_name}' not found`,
|
|
291
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
status: 'success',
|
|
296
|
+
data: results[0],
|
|
297
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
return {
|
|
302
|
+
status: 'error',
|
|
303
|
+
error: error.message,
|
|
304
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Execute a function and return its result
|
|
310
|
+
*/
|
|
311
|
+
async executeFunction(params) {
|
|
312
|
+
try {
|
|
313
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
314
|
+
if (!dbValidation.valid) {
|
|
315
|
+
return { status: 'error', error: dbValidation.error };
|
|
316
|
+
}
|
|
317
|
+
const { function_name, parameters = [] } = params;
|
|
318
|
+
const database = dbValidation.database;
|
|
319
|
+
// Validate function name
|
|
320
|
+
const identifierValidation = this.security.validateIdentifier(function_name);
|
|
321
|
+
if (!identifierValidation.valid) {
|
|
322
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid function name' };
|
|
323
|
+
}
|
|
324
|
+
// Validate parameters
|
|
325
|
+
const paramValidation = this.security.validateParameters(parameters);
|
|
326
|
+
if (!paramValidation.valid) {
|
|
327
|
+
return { status: 'error', error: `Parameter validation failed: ${paramValidation.error}` };
|
|
328
|
+
}
|
|
329
|
+
// Build SELECT query to call the function
|
|
330
|
+
const placeholders = parameters.map(() => '?').join(', ');
|
|
331
|
+
const selectQuery = `SELECT \`${database}\`.\`${function_name}\`(${placeholders}) AS result`;
|
|
332
|
+
const results = await this.db.query(selectQuery, paramValidation.sanitizedParams);
|
|
333
|
+
return {
|
|
334
|
+
status: 'success',
|
|
335
|
+
data: {
|
|
336
|
+
function_name,
|
|
337
|
+
result: results[0]?.result
|
|
338
|
+
},
|
|
339
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
return {
|
|
344
|
+
status: 'error',
|
|
345
|
+
error: error.message,
|
|
346
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
exports.FunctionTools = FunctionTools;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { SecurityLayer } from '../security/securityLayer';
|
|
2
|
+
export declare class IndexTools {
|
|
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 indexes for a table
|
|
12
|
+
*/
|
|
13
|
+
listIndexes(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
|
+
* Get detailed information about a specific index
|
|
24
|
+
*/
|
|
25
|
+
getIndexInfo(params: {
|
|
26
|
+
table_name: string;
|
|
27
|
+
index_name: string;
|
|
28
|
+
database?: string;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
status: string;
|
|
31
|
+
data?: any;
|
|
32
|
+
error?: string;
|
|
33
|
+
queryLog?: string;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Create a new index
|
|
37
|
+
*/
|
|
38
|
+
createIndex(params: {
|
|
39
|
+
table_name: string;
|
|
40
|
+
index_name: string;
|
|
41
|
+
columns: Array<string | {
|
|
42
|
+
column: string;
|
|
43
|
+
length?: number;
|
|
44
|
+
order?: 'ASC' | 'DESC';
|
|
45
|
+
}>;
|
|
46
|
+
unique?: boolean;
|
|
47
|
+
index_type?: 'BTREE' | 'HASH' | 'FULLTEXT' | 'SPATIAL';
|
|
48
|
+
comment?: string;
|
|
49
|
+
database?: string;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
status: string;
|
|
52
|
+
data?: any;
|
|
53
|
+
error?: string;
|
|
54
|
+
queryLog?: string;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Drop an index
|
|
58
|
+
*/
|
|
59
|
+
dropIndex(params: {
|
|
60
|
+
table_name: string;
|
|
61
|
+
index_name: string;
|
|
62
|
+
database?: string;
|
|
63
|
+
}): Promise<{
|
|
64
|
+
status: string;
|
|
65
|
+
message?: string;
|
|
66
|
+
error?: string;
|
|
67
|
+
queryLog?: string;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Analyze index usage and statistics
|
|
71
|
+
*/
|
|
72
|
+
analyzeIndex(params: {
|
|
73
|
+
table_name: string;
|
|
74
|
+
database?: string;
|
|
75
|
+
}): Promise<{
|
|
76
|
+
status: string;
|
|
77
|
+
data?: any;
|
|
78
|
+
error?: string;
|
|
79
|
+
queryLog?: string;
|
|
80
|
+
}>;
|
|
81
|
+
}
|