@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,294 @@
|
|
|
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.TriggerTools = void 0;
|
|
7
|
+
const connection_1 = __importDefault(require("../db/connection"));
|
|
8
|
+
const config_1 = require("../config/config");
|
|
9
|
+
class TriggerTools {
|
|
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 triggers in the current database
|
|
46
|
+
*/
|
|
47
|
+
async listTriggers(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
|
+
let query = `
|
|
55
|
+
SELECT
|
|
56
|
+
TRIGGER_NAME as trigger_name,
|
|
57
|
+
EVENT_MANIPULATION as event,
|
|
58
|
+
EVENT_OBJECT_TABLE as table_name,
|
|
59
|
+
ACTION_TIMING as timing,
|
|
60
|
+
ACTION_STATEMENT as statement,
|
|
61
|
+
ACTION_ORIENTATION as orientation,
|
|
62
|
+
DEFINER as definer,
|
|
63
|
+
CREATED as created
|
|
64
|
+
FROM INFORMATION_SCHEMA.TRIGGERS
|
|
65
|
+
WHERE TRIGGER_SCHEMA = ?
|
|
66
|
+
`;
|
|
67
|
+
const queryParams = [database];
|
|
68
|
+
if (params?.table_name) {
|
|
69
|
+
const identifierValidation = this.security.validateIdentifier(params.table_name);
|
|
70
|
+
if (!identifierValidation.valid) {
|
|
71
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid table name' };
|
|
72
|
+
}
|
|
73
|
+
query += ` AND EVENT_OBJECT_TABLE = ?`;
|
|
74
|
+
queryParams.push(params.table_name);
|
|
75
|
+
}
|
|
76
|
+
query += ` ORDER BY EVENT_OBJECT_TABLE, ACTION_TIMING, EVENT_MANIPULATION`;
|
|
77
|
+
const results = await this.db.query(query, queryParams);
|
|
78
|
+
return {
|
|
79
|
+
status: 'success',
|
|
80
|
+
data: results,
|
|
81
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return {
|
|
86
|
+
status: 'error',
|
|
87
|
+
error: error.message,
|
|
88
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get detailed information about a specific trigger
|
|
94
|
+
*/
|
|
95
|
+
async getTriggerInfo(params) {
|
|
96
|
+
try {
|
|
97
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
98
|
+
if (!dbValidation.valid) {
|
|
99
|
+
return { status: 'error', error: dbValidation.error };
|
|
100
|
+
}
|
|
101
|
+
const { trigger_name } = params;
|
|
102
|
+
const database = dbValidation.database;
|
|
103
|
+
// Validate trigger name
|
|
104
|
+
const identifierValidation = this.security.validateIdentifier(trigger_name);
|
|
105
|
+
if (!identifierValidation.valid) {
|
|
106
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid trigger name' };
|
|
107
|
+
}
|
|
108
|
+
const query = `
|
|
109
|
+
SELECT
|
|
110
|
+
TRIGGER_NAME as trigger_name,
|
|
111
|
+
EVENT_MANIPULATION as event,
|
|
112
|
+
EVENT_OBJECT_SCHEMA as schema_name,
|
|
113
|
+
EVENT_OBJECT_TABLE as table_name,
|
|
114
|
+
ACTION_ORDER as action_order,
|
|
115
|
+
ACTION_CONDITION as condition_value,
|
|
116
|
+
ACTION_STATEMENT as statement,
|
|
117
|
+
ACTION_ORIENTATION as orientation,
|
|
118
|
+
ACTION_TIMING as timing,
|
|
119
|
+
ACTION_REFERENCE_OLD_TABLE as old_table,
|
|
120
|
+
ACTION_REFERENCE_NEW_TABLE as new_table,
|
|
121
|
+
ACTION_REFERENCE_OLD_ROW as old_row,
|
|
122
|
+
ACTION_REFERENCE_NEW_ROW as new_row,
|
|
123
|
+
CREATED as created,
|
|
124
|
+
SQL_MODE as sql_mode,
|
|
125
|
+
DEFINER as definer,
|
|
126
|
+
CHARACTER_SET_CLIENT as charset,
|
|
127
|
+
COLLATION_CONNECTION as collation,
|
|
128
|
+
DATABASE_COLLATION as db_collation
|
|
129
|
+
FROM INFORMATION_SCHEMA.TRIGGERS
|
|
130
|
+
WHERE TRIGGER_SCHEMA = ? AND TRIGGER_NAME = ?
|
|
131
|
+
`;
|
|
132
|
+
const results = await this.db.query(query, [database, trigger_name]);
|
|
133
|
+
if (results.length === 0) {
|
|
134
|
+
return {
|
|
135
|
+
status: 'error',
|
|
136
|
+
error: `Trigger '${trigger_name}' not found in database '${database}'`,
|
|
137
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
status: 'success',
|
|
142
|
+
data: results[0],
|
|
143
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return {
|
|
148
|
+
status: 'error',
|
|
149
|
+
error: error.message,
|
|
150
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Create a new trigger
|
|
156
|
+
*/
|
|
157
|
+
async createTrigger(params) {
|
|
158
|
+
try {
|
|
159
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
160
|
+
if (!dbValidation.valid) {
|
|
161
|
+
return { status: 'error', error: dbValidation.error };
|
|
162
|
+
}
|
|
163
|
+
const { trigger_name, table_name, timing, event, body, definer } = params;
|
|
164
|
+
const database = dbValidation.database;
|
|
165
|
+
// Validate trigger name
|
|
166
|
+
const triggerValidation = this.security.validateIdentifier(trigger_name);
|
|
167
|
+
if (!triggerValidation.valid) {
|
|
168
|
+
return { status: 'error', error: triggerValidation.error || 'Invalid trigger name' };
|
|
169
|
+
}
|
|
170
|
+
// Validate table name
|
|
171
|
+
const tableValidation = this.security.validateIdentifier(table_name);
|
|
172
|
+
if (!tableValidation.valid) {
|
|
173
|
+
return { status: 'error', error: tableValidation.error || 'Invalid table name' };
|
|
174
|
+
}
|
|
175
|
+
// Validate timing
|
|
176
|
+
if (!['BEFORE', 'AFTER'].includes(timing)) {
|
|
177
|
+
return { status: 'error', error: 'Timing must be BEFORE or AFTER' };
|
|
178
|
+
}
|
|
179
|
+
// Validate event
|
|
180
|
+
if (!['INSERT', 'UPDATE', 'DELETE'].includes(event)) {
|
|
181
|
+
return { status: 'error', error: 'Event must be INSERT, UPDATE, or DELETE' };
|
|
182
|
+
}
|
|
183
|
+
// Build CREATE TRIGGER statement
|
|
184
|
+
let createQuery = 'CREATE';
|
|
185
|
+
if (definer) {
|
|
186
|
+
createQuery += ` DEFINER = ${definer}`;
|
|
187
|
+
}
|
|
188
|
+
createQuery += ` TRIGGER \`${database}\`.\`${trigger_name}\``;
|
|
189
|
+
createQuery += ` ${timing} ${event}`;
|
|
190
|
+
createQuery += ` ON \`${database}\`.\`${table_name}\``;
|
|
191
|
+
createQuery += ` FOR EACH ROW`;
|
|
192
|
+
// Check if body already contains BEGIN/END
|
|
193
|
+
const trimmedBody = body.trim();
|
|
194
|
+
if (trimmedBody.toUpperCase().startsWith('BEGIN') && trimmedBody.toUpperCase().endsWith('END')) {
|
|
195
|
+
createQuery += `\n${body}`;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
createQuery += `\nBEGIN\n${body}\nEND`;
|
|
199
|
+
}
|
|
200
|
+
await this.db.query(createQuery);
|
|
201
|
+
return {
|
|
202
|
+
status: 'success',
|
|
203
|
+
data: {
|
|
204
|
+
message: `Trigger '${trigger_name}' created successfully`,
|
|
205
|
+
trigger_name,
|
|
206
|
+
table_name,
|
|
207
|
+
timing,
|
|
208
|
+
event,
|
|
209
|
+
database
|
|
210
|
+
},
|
|
211
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return {
|
|
216
|
+
status: 'error',
|
|
217
|
+
error: error.message,
|
|
218
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Drop a trigger
|
|
224
|
+
*/
|
|
225
|
+
async dropTrigger(params) {
|
|
226
|
+
try {
|
|
227
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
228
|
+
if (!dbValidation.valid) {
|
|
229
|
+
return { status: 'error', error: dbValidation.error };
|
|
230
|
+
}
|
|
231
|
+
const { trigger_name, if_exists = false } = params;
|
|
232
|
+
const database = dbValidation.database;
|
|
233
|
+
// Validate trigger name
|
|
234
|
+
const identifierValidation = this.security.validateIdentifier(trigger_name);
|
|
235
|
+
if (!identifierValidation.valid) {
|
|
236
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid trigger name' };
|
|
237
|
+
}
|
|
238
|
+
const dropQuery = `DROP TRIGGER ${if_exists ? 'IF EXISTS' : ''} \`${database}\`.\`${trigger_name}\``;
|
|
239
|
+
await this.db.query(dropQuery);
|
|
240
|
+
return {
|
|
241
|
+
status: 'success',
|
|
242
|
+
message: `Trigger '${trigger_name}' dropped successfully`,
|
|
243
|
+
queryLog: this.db.getFormattedQueryLogs(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 the CREATE statement for a trigger
|
|
256
|
+
*/
|
|
257
|
+
async showCreateTrigger(params) {
|
|
258
|
+
try {
|
|
259
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
260
|
+
if (!dbValidation.valid) {
|
|
261
|
+
return { status: 'error', error: dbValidation.error };
|
|
262
|
+
}
|
|
263
|
+
const { trigger_name } = params;
|
|
264
|
+
const database = dbValidation.database;
|
|
265
|
+
// Validate trigger name
|
|
266
|
+
const identifierValidation = this.security.validateIdentifier(trigger_name);
|
|
267
|
+
if (!identifierValidation.valid) {
|
|
268
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid trigger name' };
|
|
269
|
+
}
|
|
270
|
+
const query = `SHOW CREATE TRIGGER \`${database}\`.\`${trigger_name}\``;
|
|
271
|
+
const results = await this.db.query(query);
|
|
272
|
+
if (results.length === 0) {
|
|
273
|
+
return {
|
|
274
|
+
status: 'error',
|
|
275
|
+
error: `Trigger '${trigger_name}' not found`,
|
|
276
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
status: 'success',
|
|
281
|
+
data: results[0],
|
|
282
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
return {
|
|
287
|
+
status: 'error',
|
|
288
|
+
error: error.message,
|
|
289
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
exports.TriggerTools = TriggerTools;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { SecurityLayer } from '../security/securityLayer';
|
|
2
|
+
export declare class ViewTools {
|
|
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 views in the current database
|
|
12
|
+
*/
|
|
13
|
+
listViews(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 view
|
|
23
|
+
*/
|
|
24
|
+
getViewInfo(params: {
|
|
25
|
+
view_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 view
|
|
35
|
+
*/
|
|
36
|
+
createView(params: {
|
|
37
|
+
view_name: string;
|
|
38
|
+
definition: string;
|
|
39
|
+
or_replace?: boolean;
|
|
40
|
+
algorithm?: 'UNDEFINED' | 'MERGE' | 'TEMPTABLE';
|
|
41
|
+
security?: 'DEFINER' | 'INVOKER';
|
|
42
|
+
check_option?: 'CASCADED' | 'LOCAL';
|
|
43
|
+
database?: string;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
status: string;
|
|
46
|
+
data?: any;
|
|
47
|
+
error?: string;
|
|
48
|
+
queryLog?: string;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Alter an existing view
|
|
52
|
+
*/
|
|
53
|
+
alterView(params: {
|
|
54
|
+
view_name: string;
|
|
55
|
+
definition: string;
|
|
56
|
+
algorithm?: 'UNDEFINED' | 'MERGE' | 'TEMPTABLE';
|
|
57
|
+
security?: 'DEFINER' | 'INVOKER';
|
|
58
|
+
check_option?: 'CASCADED' | 'LOCAL';
|
|
59
|
+
database?: string;
|
|
60
|
+
}): Promise<{
|
|
61
|
+
status: string;
|
|
62
|
+
data?: any;
|
|
63
|
+
error?: string;
|
|
64
|
+
queryLog?: string;
|
|
65
|
+
}>;
|
|
66
|
+
/**
|
|
67
|
+
* Drop a view
|
|
68
|
+
*/
|
|
69
|
+
dropView(params: {
|
|
70
|
+
view_name: string;
|
|
71
|
+
if_exists?: boolean;
|
|
72
|
+
database?: string;
|
|
73
|
+
}): Promise<{
|
|
74
|
+
status: string;
|
|
75
|
+
message?: string;
|
|
76
|
+
error?: string;
|
|
77
|
+
queryLog?: string;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Show the CREATE statement for a view
|
|
81
|
+
*/
|
|
82
|
+
showCreateView(params: {
|
|
83
|
+
view_name: string;
|
|
84
|
+
database?: string;
|
|
85
|
+
}): Promise<{
|
|
86
|
+
status: string;
|
|
87
|
+
data?: any;
|
|
88
|
+
error?: string;
|
|
89
|
+
queryLog?: string;
|
|
90
|
+
}>;
|
|
91
|
+
}
|