@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,330 @@
|
|
|
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.ViewTools = void 0;
|
|
7
|
+
const connection_1 = __importDefault(require("../db/connection"));
|
|
8
|
+
const config_1 = require("../config/config");
|
|
9
|
+
class ViewTools {
|
|
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 views in the current database
|
|
46
|
+
*/
|
|
47
|
+
async listViews(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
|
+
TABLE_NAME as view_name,
|
|
57
|
+
VIEW_DEFINITION as definition,
|
|
58
|
+
CHECK_OPTION as check_option,
|
|
59
|
+
IS_UPDATABLE as is_updatable,
|
|
60
|
+
DEFINER as definer,
|
|
61
|
+
SECURITY_TYPE as security_type,
|
|
62
|
+
CHARACTER_SET_CLIENT as charset,
|
|
63
|
+
COLLATION_CONNECTION as collation
|
|
64
|
+
FROM INFORMATION_SCHEMA.VIEWS
|
|
65
|
+
WHERE TABLE_SCHEMA = ?
|
|
66
|
+
ORDER BY TABLE_NAME
|
|
67
|
+
`;
|
|
68
|
+
const results = await this.db.query(query, [database]);
|
|
69
|
+
return {
|
|
70
|
+
status: 'success',
|
|
71
|
+
data: results,
|
|
72
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
status: 'error',
|
|
78
|
+
error: error.message,
|
|
79
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get detailed information about a specific view
|
|
85
|
+
*/
|
|
86
|
+
async getViewInfo(params) {
|
|
87
|
+
try {
|
|
88
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
89
|
+
if (!dbValidation.valid) {
|
|
90
|
+
return { status: 'error', error: dbValidation.error };
|
|
91
|
+
}
|
|
92
|
+
const { view_name } = params;
|
|
93
|
+
const database = dbValidation.database;
|
|
94
|
+
// Validate view name
|
|
95
|
+
const identifierValidation = this.security.validateIdentifier(view_name);
|
|
96
|
+
if (!identifierValidation.valid) {
|
|
97
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
|
|
98
|
+
}
|
|
99
|
+
// Get view information
|
|
100
|
+
const viewQuery = `
|
|
101
|
+
SELECT
|
|
102
|
+
TABLE_NAME as view_name,
|
|
103
|
+
VIEW_DEFINITION as definition,
|
|
104
|
+
CHECK_OPTION as check_option,
|
|
105
|
+
IS_UPDATABLE as is_updatable,
|
|
106
|
+
DEFINER as definer,
|
|
107
|
+
SECURITY_TYPE as security_type,
|
|
108
|
+
CHARACTER_SET_CLIENT as charset,
|
|
109
|
+
COLLATION_CONNECTION as collation
|
|
110
|
+
FROM INFORMATION_SCHEMA.VIEWS
|
|
111
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
112
|
+
`;
|
|
113
|
+
// Get view columns
|
|
114
|
+
const columnsQuery = `
|
|
115
|
+
SELECT
|
|
116
|
+
COLUMN_NAME as column_name,
|
|
117
|
+
DATA_TYPE as data_type,
|
|
118
|
+
COLUMN_TYPE as column_type,
|
|
119
|
+
IS_NULLABLE as is_nullable,
|
|
120
|
+
COLUMN_DEFAULT as column_default,
|
|
121
|
+
ORDINAL_POSITION as position
|
|
122
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
123
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
124
|
+
ORDER BY ORDINAL_POSITION
|
|
125
|
+
`;
|
|
126
|
+
const [viewInfo, columns] = await Promise.all([
|
|
127
|
+
this.db.query(viewQuery, [database, view_name]),
|
|
128
|
+
this.db.query(columnsQuery, [database, view_name])
|
|
129
|
+
]);
|
|
130
|
+
if (viewInfo.length === 0) {
|
|
131
|
+
return {
|
|
132
|
+
status: 'error',
|
|
133
|
+
error: `View '${view_name}' not found in database '${database}'`,
|
|
134
|
+
queryLog: this.db.getFormattedQueryLogs(2)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
status: 'success',
|
|
139
|
+
data: {
|
|
140
|
+
...viewInfo[0],
|
|
141
|
+
columns: columns
|
|
142
|
+
},
|
|
143
|
+
queryLog: this.db.getFormattedQueryLogs(2)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return {
|
|
148
|
+
status: 'error',
|
|
149
|
+
error: error.message,
|
|
150
|
+
queryLog: this.db.getFormattedQueryLogs(2)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Create a new view
|
|
156
|
+
*/
|
|
157
|
+
async createView(params) {
|
|
158
|
+
try {
|
|
159
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
160
|
+
if (!dbValidation.valid) {
|
|
161
|
+
return { status: 'error', error: dbValidation.error };
|
|
162
|
+
}
|
|
163
|
+
const { view_name, definition, or_replace = false, algorithm, security, check_option } = params;
|
|
164
|
+
const database = dbValidation.database;
|
|
165
|
+
// Validate view name
|
|
166
|
+
const identifierValidation = this.security.validateIdentifier(view_name);
|
|
167
|
+
if (!identifierValidation.valid) {
|
|
168
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
|
|
169
|
+
}
|
|
170
|
+
// Validate that definition is a SELECT statement
|
|
171
|
+
const trimmedDef = definition.trim().toUpperCase();
|
|
172
|
+
if (!trimmedDef.startsWith('SELECT')) {
|
|
173
|
+
return { status: 'error', error: 'View definition must be a SELECT statement' };
|
|
174
|
+
}
|
|
175
|
+
// Build CREATE VIEW statement
|
|
176
|
+
let createQuery = or_replace ? 'CREATE OR REPLACE ' : 'CREATE ';
|
|
177
|
+
if (algorithm) {
|
|
178
|
+
createQuery += `ALGORITHM = ${algorithm} `;
|
|
179
|
+
}
|
|
180
|
+
if (security) {
|
|
181
|
+
createQuery += `SQL SECURITY ${security} `;
|
|
182
|
+
}
|
|
183
|
+
createQuery += `VIEW \`${database}\`.\`${view_name}\` AS ${definition}`;
|
|
184
|
+
if (check_option) {
|
|
185
|
+
createQuery += ` WITH ${check_option} CHECK OPTION`;
|
|
186
|
+
}
|
|
187
|
+
await this.db.query(createQuery);
|
|
188
|
+
return {
|
|
189
|
+
status: 'success',
|
|
190
|
+
data: {
|
|
191
|
+
message: `View '${view_name}' created successfully`,
|
|
192
|
+
view_name,
|
|
193
|
+
database
|
|
194
|
+
},
|
|
195
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
return {
|
|
200
|
+
status: 'error',
|
|
201
|
+
error: error.message,
|
|
202
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Alter an existing view
|
|
208
|
+
*/
|
|
209
|
+
async alterView(params) {
|
|
210
|
+
try {
|
|
211
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
212
|
+
if (!dbValidation.valid) {
|
|
213
|
+
return { status: 'error', error: dbValidation.error };
|
|
214
|
+
}
|
|
215
|
+
const { view_name, definition, algorithm, security, check_option } = params;
|
|
216
|
+
const database = dbValidation.database;
|
|
217
|
+
// Validate view name
|
|
218
|
+
const identifierValidation = this.security.validateIdentifier(view_name);
|
|
219
|
+
if (!identifierValidation.valid) {
|
|
220
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
|
|
221
|
+
}
|
|
222
|
+
// Validate that definition is a SELECT statement
|
|
223
|
+
const trimmedDef = definition.trim().toUpperCase();
|
|
224
|
+
if (!trimmedDef.startsWith('SELECT')) {
|
|
225
|
+
return { status: 'error', error: 'View definition must be a SELECT statement' };
|
|
226
|
+
}
|
|
227
|
+
// Build ALTER VIEW statement
|
|
228
|
+
let alterQuery = 'ALTER ';
|
|
229
|
+
if (algorithm) {
|
|
230
|
+
alterQuery += `ALGORITHM = ${algorithm} `;
|
|
231
|
+
}
|
|
232
|
+
if (security) {
|
|
233
|
+
alterQuery += `SQL SECURITY ${security} `;
|
|
234
|
+
}
|
|
235
|
+
alterQuery += `VIEW \`${database}\`.\`${view_name}\` AS ${definition}`;
|
|
236
|
+
if (check_option) {
|
|
237
|
+
alterQuery += ` WITH ${check_option} CHECK OPTION`;
|
|
238
|
+
}
|
|
239
|
+
await this.db.query(alterQuery);
|
|
240
|
+
return {
|
|
241
|
+
status: 'success',
|
|
242
|
+
data: {
|
|
243
|
+
message: `View '${view_name}' altered successfully`,
|
|
244
|
+
view_name,
|
|
245
|
+
database
|
|
246
|
+
},
|
|
247
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
return {
|
|
252
|
+
status: 'error',
|
|
253
|
+
error: error.message,
|
|
254
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Drop a view
|
|
260
|
+
*/
|
|
261
|
+
async dropView(params) {
|
|
262
|
+
try {
|
|
263
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
264
|
+
if (!dbValidation.valid) {
|
|
265
|
+
return { status: 'error', error: dbValidation.error };
|
|
266
|
+
}
|
|
267
|
+
const { view_name, if_exists = false } = params;
|
|
268
|
+
const database = dbValidation.database;
|
|
269
|
+
// Validate view name
|
|
270
|
+
const identifierValidation = this.security.validateIdentifier(view_name);
|
|
271
|
+
if (!identifierValidation.valid) {
|
|
272
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
|
|
273
|
+
}
|
|
274
|
+
const dropQuery = `DROP VIEW ${if_exists ? 'IF EXISTS' : ''} \`${database}\`.\`${view_name}\``;
|
|
275
|
+
await this.db.query(dropQuery);
|
|
276
|
+
return {
|
|
277
|
+
status: 'success',
|
|
278
|
+
message: `View '${view_name}' dropped successfully`,
|
|
279
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
return {
|
|
284
|
+
status: 'error',
|
|
285
|
+
error: error.message,
|
|
286
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Show the CREATE statement for a view
|
|
292
|
+
*/
|
|
293
|
+
async showCreateView(params) {
|
|
294
|
+
try {
|
|
295
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
296
|
+
if (!dbValidation.valid) {
|
|
297
|
+
return { status: 'error', error: dbValidation.error };
|
|
298
|
+
}
|
|
299
|
+
const { view_name } = params;
|
|
300
|
+
const database = dbValidation.database;
|
|
301
|
+
// Validate view name
|
|
302
|
+
const identifierValidation = this.security.validateIdentifier(view_name);
|
|
303
|
+
if (!identifierValidation.valid) {
|
|
304
|
+
return { status: 'error', error: identifierValidation.error || 'Invalid view name' };
|
|
305
|
+
}
|
|
306
|
+
const query = `SHOW CREATE VIEW \`${database}\`.\`${view_name}\``;
|
|
307
|
+
const results = await this.db.query(query);
|
|
308
|
+
if (results.length === 0) {
|
|
309
|
+
return {
|
|
310
|
+
status: 'error',
|
|
311
|
+
error: `View '${view_name}' not found`,
|
|
312
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
status: 'success',
|
|
317
|
+
data: results[0],
|
|
318
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
return {
|
|
323
|
+
status: 'error',
|
|
324
|
+
error: error.message,
|
|
325
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
exports.ViewTools = ViewTools;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@berthojoris/mcp-mysql-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Model Context Protocol server for MySQL database integration with dynamic per-project permissions and data export capabilities",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|