@berthojoris/mcp-mysql-server 1.4.15 → 1.5.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 +200 -18
- 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 +76 -3
- package/dist/index.js +161 -70
- package/dist/mcp-server.js +166 -5
- package/dist/optimization/queryOptimizer.d.ts +125 -0
- package/dist/optimization/queryOptimizer.js +509 -0
- package/dist/tools/queryTools.d.ts +14 -1
- package/dist/tools/queryTools.js +27 -3
- package/package.json +1 -1
|
@@ -0,0 +1,337 @@
|
|
|
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.QueryCache = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
/**
|
|
9
|
+
* Default cache configuration
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
enabled: true,
|
|
13
|
+
ttlMs: 60000, // 1 minute default TTL
|
|
14
|
+
maxSize: 100, // Maximum 100 cached queries
|
|
15
|
+
maxMemoryMB: 50, // Maximum 50MB memory usage
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* LRU Query Cache with TTL support
|
|
19
|
+
* Implements an in-memory cache for query results to improve performance
|
|
20
|
+
*/
|
|
21
|
+
class QueryCache {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.cache = new Map();
|
|
24
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
25
|
+
this.stats = { totalHits: 0, totalMisses: 0 };
|
|
26
|
+
this.accessOrder = [];
|
|
27
|
+
// Load config from environment if available
|
|
28
|
+
this.loadConfigFromEnv();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get singleton instance
|
|
32
|
+
*/
|
|
33
|
+
static getInstance() {
|
|
34
|
+
if (!QueryCache.instance) {
|
|
35
|
+
QueryCache.instance = new QueryCache();
|
|
36
|
+
}
|
|
37
|
+
return QueryCache.instance;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Load cache configuration from environment variables
|
|
41
|
+
*/
|
|
42
|
+
loadConfigFromEnv() {
|
|
43
|
+
if (process.env.CACHE_ENABLED !== undefined) {
|
|
44
|
+
this.config.enabled = process.env.CACHE_ENABLED === "true";
|
|
45
|
+
}
|
|
46
|
+
if (process.env.CACHE_TTL_MS) {
|
|
47
|
+
const ttl = parseInt(process.env.CACHE_TTL_MS, 10);
|
|
48
|
+
if (!isNaN(ttl) && ttl > 0) {
|
|
49
|
+
this.config.ttlMs = ttl;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (process.env.CACHE_MAX_SIZE) {
|
|
53
|
+
const maxSize = parseInt(process.env.CACHE_MAX_SIZE, 10);
|
|
54
|
+
if (!isNaN(maxSize) && maxSize > 0) {
|
|
55
|
+
this.config.maxSize = maxSize;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (process.env.CACHE_MAX_MEMORY_MB) {
|
|
59
|
+
const maxMemory = parseInt(process.env.CACHE_MAX_MEMORY_MB, 10);
|
|
60
|
+
if (!isNaN(maxMemory) && maxMemory > 0) {
|
|
61
|
+
this.config.maxMemoryMB = maxMemory;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate a unique hash for a query and its parameters
|
|
67
|
+
*/
|
|
68
|
+
generateHash(sql, params) {
|
|
69
|
+
const normalized = sql.trim().toLowerCase();
|
|
70
|
+
let paramsStr = "";
|
|
71
|
+
if (params) {
|
|
72
|
+
try {
|
|
73
|
+
paramsStr = JSON.stringify(params);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Handle circular references or non-serializable values
|
|
77
|
+
paramsStr = params
|
|
78
|
+
.map((p) => {
|
|
79
|
+
if (p === null)
|
|
80
|
+
return "null";
|
|
81
|
+
if (p === undefined)
|
|
82
|
+
return "undefined";
|
|
83
|
+
if (typeof p === "object") {
|
|
84
|
+
try {
|
|
85
|
+
return JSON.stringify(p);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return String(p);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return String(p);
|
|
92
|
+
})
|
|
93
|
+
.join(",");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const combined = `${normalized}:${paramsStr}`;
|
|
97
|
+
return crypto_1.default.createHash("md5").update(combined).digest("hex");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a query result is cached and valid
|
|
101
|
+
*/
|
|
102
|
+
get(sql, params) {
|
|
103
|
+
if (!this.config.enabled) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const hash = this.generateHash(sql, params);
|
|
107
|
+
const entry = this.cache.get(hash);
|
|
108
|
+
if (!entry) {
|
|
109
|
+
this.stats.totalMisses++;
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
// Check if entry has expired
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
if (now - entry.timestamp > this.config.ttlMs) {
|
|
115
|
+
this.cache.delete(hash);
|
|
116
|
+
this.removeFromAccessOrder(hash);
|
|
117
|
+
this.stats.totalMisses++;
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
// Update hit count and access order
|
|
121
|
+
entry.hitCount++;
|
|
122
|
+
this.updateAccessOrder(hash);
|
|
123
|
+
this.stats.totalHits++;
|
|
124
|
+
// Return a deep copy to prevent mutation of cached data
|
|
125
|
+
return {
|
|
126
|
+
...entry,
|
|
127
|
+
data: JSON.parse(JSON.stringify(entry.data)),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Cache a query result
|
|
132
|
+
*/
|
|
133
|
+
set(sql, params, data) {
|
|
134
|
+
if (!this.config.enabled) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Only cache SELECT queries
|
|
138
|
+
const normalizedSql = sql.trim().toUpperCase();
|
|
139
|
+
if (!normalizedSql.startsWith("SELECT")) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const hash = this.generateHash(sql, params);
|
|
143
|
+
// Check if we need to evict entries
|
|
144
|
+
this.evictIfNeeded();
|
|
145
|
+
const entry = {
|
|
146
|
+
data,
|
|
147
|
+
timestamp: Date.now(),
|
|
148
|
+
hitCount: 0,
|
|
149
|
+
queryHash: hash,
|
|
150
|
+
sql,
|
|
151
|
+
params,
|
|
152
|
+
};
|
|
153
|
+
this.cache.set(hash, entry);
|
|
154
|
+
this.updateAccessOrder(hash);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Invalidate cache entries matching a pattern
|
|
158
|
+
* Used when data is modified (INSERT, UPDATE, DELETE)
|
|
159
|
+
*/
|
|
160
|
+
invalidate(pattern) {
|
|
161
|
+
let invalidatedCount = 0;
|
|
162
|
+
if (!pattern) {
|
|
163
|
+
// Clear all cache
|
|
164
|
+
invalidatedCount = this.cache.size;
|
|
165
|
+
this.cache.clear();
|
|
166
|
+
this.accessOrder = [];
|
|
167
|
+
return invalidatedCount;
|
|
168
|
+
}
|
|
169
|
+
const regex = typeof pattern === "string" ? new RegExp(pattern, "i") : pattern;
|
|
170
|
+
for (const [hash, entry] of this.cache.entries()) {
|
|
171
|
+
if (regex.test(entry.sql)) {
|
|
172
|
+
this.cache.delete(hash);
|
|
173
|
+
this.removeFromAccessOrder(hash);
|
|
174
|
+
invalidatedCount++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return invalidatedCount;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Escape special regex characters in a string
|
|
181
|
+
*/
|
|
182
|
+
escapeRegex(str) {
|
|
183
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Invalidate cache entries related to a specific table
|
|
187
|
+
*/
|
|
188
|
+
invalidateTable(tableName) {
|
|
189
|
+
// Escape special regex characters in table name to prevent regex injection
|
|
190
|
+
const escapedTableName = this.escapeRegex(tableName);
|
|
191
|
+
// Match table name in various SQL patterns
|
|
192
|
+
const patterns = [
|
|
193
|
+
new RegExp(`\\bFROM\\s+[\`"']?${escapedTableName}[\`"']?\\b`, "i"),
|
|
194
|
+
new RegExp(`\\bJOIN\\s+[\`"']?${escapedTableName}[\`"']?\\b`, "i"),
|
|
195
|
+
new RegExp(`\\b${escapedTableName}\\b`, "i"),
|
|
196
|
+
];
|
|
197
|
+
let invalidatedCount = 0;
|
|
198
|
+
for (const [hash, entry] of this.cache.entries()) {
|
|
199
|
+
for (const pattern of patterns) {
|
|
200
|
+
if (pattern.test(entry.sql)) {
|
|
201
|
+
this.cache.delete(hash);
|
|
202
|
+
this.removeFromAccessOrder(hash);
|
|
203
|
+
invalidatedCount++;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return invalidatedCount;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Evict least recently used entries if cache is full
|
|
212
|
+
*/
|
|
213
|
+
evictIfNeeded() {
|
|
214
|
+
// Check size limit
|
|
215
|
+
while (this.cache.size >= this.config.maxSize &&
|
|
216
|
+
this.accessOrder.length > 0) {
|
|
217
|
+
const lruHash = this.accessOrder.shift();
|
|
218
|
+
if (lruHash) {
|
|
219
|
+
this.cache.delete(lruHash);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Check memory limit (approximate) - recalculate each iteration
|
|
223
|
+
const maxMemoryBytes = this.config.maxMemoryMB * 1024 * 1024;
|
|
224
|
+
while (this.estimateMemoryUsage() > maxMemoryBytes &&
|
|
225
|
+
this.accessOrder.length > 0) {
|
|
226
|
+
const lruHash = this.accessOrder.shift();
|
|
227
|
+
if (lruHash) {
|
|
228
|
+
this.cache.delete(lruHash);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Also evict expired entries
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
const expiredHashes = [];
|
|
234
|
+
for (const [hash, entry] of this.cache.entries()) {
|
|
235
|
+
if (now - entry.timestamp > this.config.ttlMs) {
|
|
236
|
+
expiredHashes.push(hash);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Delete after iteration to avoid modifying map during iteration
|
|
240
|
+
for (const hash of expiredHashes) {
|
|
241
|
+
this.cache.delete(hash);
|
|
242
|
+
this.removeFromAccessOrder(hash);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Update access order for LRU tracking
|
|
247
|
+
*/
|
|
248
|
+
updateAccessOrder(hash) {
|
|
249
|
+
this.removeFromAccessOrder(hash);
|
|
250
|
+
this.accessOrder.push(hash);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Remove hash from access order
|
|
254
|
+
*/
|
|
255
|
+
removeFromAccessOrder(hash) {
|
|
256
|
+
const index = this.accessOrder.indexOf(hash);
|
|
257
|
+
if (index > -1) {
|
|
258
|
+
this.accessOrder.splice(index, 1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Estimate memory usage of the cache
|
|
263
|
+
*/
|
|
264
|
+
estimateMemoryUsage() {
|
|
265
|
+
let totalSize = 0;
|
|
266
|
+
for (const [, entry] of this.cache.entries()) {
|
|
267
|
+
totalSize += JSON.stringify(entry).length * 2; // UTF-16 encoding
|
|
268
|
+
}
|
|
269
|
+
return totalSize;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get cache statistics
|
|
273
|
+
*/
|
|
274
|
+
getStats() {
|
|
275
|
+
const totalRequests = this.stats.totalHits + this.stats.totalMisses;
|
|
276
|
+
return {
|
|
277
|
+
totalHits: this.stats.totalHits,
|
|
278
|
+
totalMisses: this.stats.totalMisses,
|
|
279
|
+
hitRate: totalRequests > 0 ? this.stats.totalHits / totalRequests : 0,
|
|
280
|
+
currentSize: this.cache.size,
|
|
281
|
+
maxSize: this.config.maxSize,
|
|
282
|
+
ttlMs: this.config.ttlMs,
|
|
283
|
+
enabled: this.config.enabled,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get current cache configuration
|
|
288
|
+
*/
|
|
289
|
+
getConfig() {
|
|
290
|
+
return { ...this.config };
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Update cache configuration
|
|
294
|
+
*/
|
|
295
|
+
setConfig(config) {
|
|
296
|
+
this.config = { ...this.config, ...config };
|
|
297
|
+
// If cache is disabled, clear it
|
|
298
|
+
if (!this.config.enabled) {
|
|
299
|
+
this.clear();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Enable caching
|
|
304
|
+
*/
|
|
305
|
+
enable() {
|
|
306
|
+
this.config.enabled = true;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Disable caching
|
|
310
|
+
*/
|
|
311
|
+
disable() {
|
|
312
|
+
this.config.enabled = false;
|
|
313
|
+
this.clear();
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Clear all cache entries
|
|
317
|
+
*/
|
|
318
|
+
clear() {
|
|
319
|
+
this.cache.clear();
|
|
320
|
+
this.accessOrder = [];
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Reset statistics
|
|
324
|
+
*/
|
|
325
|
+
resetStats() {
|
|
326
|
+
this.stats.totalHits = 0;
|
|
327
|
+
this.stats.totalMisses = 0;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get all cached entries (for debugging)
|
|
331
|
+
*/
|
|
332
|
+
getAllEntries() {
|
|
333
|
+
return Array.from(this.cache.values());
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
exports.QueryCache = QueryCache;
|
|
337
|
+
exports.default = QueryCache;
|
|
@@ -21,59 +21,68 @@ var ToolCategory;
|
|
|
21
21
|
ToolCategory["DDL"] = "ddl";
|
|
22
22
|
ToolCategory["UTILITY"] = "utility";
|
|
23
23
|
ToolCategory["TRANSACTION"] = "transaction";
|
|
24
|
-
ToolCategory["PROCEDURE"] = "procedure";
|
|
24
|
+
ToolCategory["PROCEDURE"] = "procedure";
|
|
25
25
|
})(ToolCategory || (exports.ToolCategory = ToolCategory = {}));
|
|
26
26
|
/**
|
|
27
27
|
* Map of tool names to their categories
|
|
28
28
|
*/
|
|
29
29
|
exports.toolCategoryMap = {
|
|
30
30
|
// Database tools
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
listDatabases: ToolCategory.LIST,
|
|
32
|
+
listTables: ToolCategory.LIST,
|
|
33
|
+
readTableSchema: ToolCategory.LIST,
|
|
34
34
|
// CRUD tools
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
createRecord: ToolCategory.CREATE,
|
|
36
|
+
readRecords: ToolCategory.READ,
|
|
37
|
+
updateRecord: ToolCategory.UPDATE,
|
|
38
|
+
deleteRecord: ToolCategory.DELETE,
|
|
39
39
|
// Bulk operations
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
bulkInsert: ToolCategory.CREATE,
|
|
41
|
+
bulkUpdate: ToolCategory.UPDATE,
|
|
42
|
+
bulkDelete: ToolCategory.DELETE,
|
|
43
43
|
// Query tools
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
runQuery: ToolCategory.READ,
|
|
45
|
+
executeSql: ToolCategory.EXECUTE,
|
|
46
46
|
// DDL tools
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
createTable: ToolCategory.DDL,
|
|
48
|
+
alterTable: ToolCategory.DDL,
|
|
49
|
+
dropTable: ToolCategory.DDL,
|
|
50
|
+
executeDdl: ToolCategory.DDL,
|
|
51
51
|
// Utility tools
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
describeConnection: ToolCategory.UTILITY,
|
|
53
|
+
testConnection: ToolCategory.UTILITY,
|
|
54
|
+
getTableRelationships: ToolCategory.UTILITY,
|
|
55
|
+
exportTableToCSV: ToolCategory.UTILITY,
|
|
56
|
+
exportQueryToCSV: ToolCategory.UTILITY,
|
|
57
57
|
// Transaction tools
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
beginTransaction: ToolCategory.TRANSACTION,
|
|
59
|
+
commitTransaction: ToolCategory.TRANSACTION,
|
|
60
|
+
rollbackTransaction: ToolCategory.TRANSACTION,
|
|
61
|
+
getTransactionStatus: ToolCategory.TRANSACTION,
|
|
62
|
+
executeInTransaction: ToolCategory.TRANSACTION,
|
|
63
63
|
// Stored procedure tools
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
listStoredProcedures: ToolCategory.LIST,
|
|
65
|
+
getStoredProcedureInfo: ToolCategory.LIST,
|
|
66
|
+
executeStoredProcedure: ToolCategory.PROCEDURE,
|
|
67
|
+
createStoredProcedure: ToolCategory.PROCEDURE,
|
|
68
|
+
dropStoredProcedure: ToolCategory.PROCEDURE,
|
|
69
|
+
showCreateProcedure: ToolCategory.LIST,
|
|
70
|
+
// Cache management tools
|
|
71
|
+
getCacheStats: ToolCategory.UTILITY,
|
|
72
|
+
getCacheConfig: ToolCategory.UTILITY,
|
|
73
|
+
configureCacheSettings: ToolCategory.UTILITY,
|
|
74
|
+
clearCache: ToolCategory.UTILITY,
|
|
75
|
+
invalidateCacheForTable: ToolCategory.UTILITY,
|
|
76
|
+
// Query optimization tools
|
|
77
|
+
analyzeQuery: ToolCategory.UTILITY,
|
|
78
|
+
getOptimizationHints: ToolCategory.UTILITY,
|
|
70
79
|
};
|
|
71
80
|
/**
|
|
72
81
|
* Class to manage feature configuration based on runtime or environment variables
|
|
73
82
|
*/
|
|
74
83
|
class FeatureConfig {
|
|
75
84
|
constructor(configStr) {
|
|
76
|
-
this.originalConfigString = configStr || process.env.MCP_CONFIG ||
|
|
85
|
+
this.originalConfigString = configStr || process.env.MCP_CONFIG || "";
|
|
77
86
|
this.enabledCategories = this.parseConfig(configStr);
|
|
78
87
|
}
|
|
79
88
|
/**
|
|
@@ -81,14 +90,14 @@ class FeatureConfig {
|
|
|
81
90
|
*/
|
|
82
91
|
parseConfig(configStr) {
|
|
83
92
|
// Priority: 1. Provided config, 2. Environment variable, 3. Enable all
|
|
84
|
-
const config = configStr || process.env.MCP_CONFIG ||
|
|
93
|
+
const config = configStr || process.env.MCP_CONFIG || "";
|
|
85
94
|
// If config is empty, enable all features
|
|
86
95
|
if (!config.trim()) {
|
|
87
96
|
return new Set(Object.values(ToolCategory));
|
|
88
97
|
}
|
|
89
98
|
// Parse comma-separated list
|
|
90
|
-
const categories = config.split(
|
|
91
|
-
const validCategories = categories.filter(c => Object.values(ToolCategory).includes(c));
|
|
99
|
+
const categories = config.split(",").map((c) => c.trim().toLowerCase());
|
|
100
|
+
const validCategories = categories.filter((c) => Object.values(ToolCategory).includes(c));
|
|
92
101
|
return new Set(validCategories);
|
|
93
102
|
}
|
|
94
103
|
/**
|
|
@@ -119,47 +128,49 @@ class FeatureConfig {
|
|
|
119
128
|
return `Unknown tool '${toolName}'. This tool is not recognized by the MCP server.`;
|
|
120
129
|
}
|
|
121
130
|
const isAllPermissions = !this.originalConfigString.trim();
|
|
122
|
-
const currentPermissions = isAllPermissions
|
|
131
|
+
const currentPermissions = isAllPermissions
|
|
132
|
+
? "all"
|
|
133
|
+
: this.originalConfigString;
|
|
123
134
|
const actionDescriptions = {
|
|
124
|
-
[ToolCategory.LIST]:
|
|
125
|
-
[ToolCategory.READ]:
|
|
126
|
-
[ToolCategory.CREATE]:
|
|
127
|
-
[ToolCategory.UPDATE]:
|
|
128
|
-
[ToolCategory.DELETE]:
|
|
129
|
-
[ToolCategory.EXECUTE]:
|
|
130
|
-
[ToolCategory.DDL]:
|
|
131
|
-
[ToolCategory.UTILITY]:
|
|
132
|
-
[ToolCategory.TRANSACTION]:
|
|
133
|
-
[ToolCategory.PROCEDURE]:
|
|
135
|
+
[ToolCategory.LIST]: "list databases and tables",
|
|
136
|
+
[ToolCategory.READ]: "read data from tables",
|
|
137
|
+
[ToolCategory.CREATE]: "create new records",
|
|
138
|
+
[ToolCategory.UPDATE]: "update existing records",
|
|
139
|
+
[ToolCategory.DELETE]: "delete records",
|
|
140
|
+
[ToolCategory.EXECUTE]: "execute custom SQL queries",
|
|
141
|
+
[ToolCategory.DDL]: "create, alter, or drop tables (schema changes)",
|
|
142
|
+
[ToolCategory.UTILITY]: "use utility functions",
|
|
143
|
+
[ToolCategory.TRANSACTION]: "manage database transactions",
|
|
144
|
+
[ToolCategory.PROCEDURE]: "manage stored procedures",
|
|
134
145
|
};
|
|
135
146
|
const toolDescriptions = {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
147
|
+
createTable: "create new tables",
|
|
148
|
+
alterTable: "modify table structure",
|
|
149
|
+
dropTable: "delete tables",
|
|
150
|
+
executeDdl: "execute DDL statements",
|
|
151
|
+
createRecord: "insert new records",
|
|
152
|
+
updateRecord: "update existing records",
|
|
153
|
+
deleteRecord: "delete records",
|
|
154
|
+
bulkInsert: "insert multiple records in batches",
|
|
155
|
+
bulkUpdate: "update multiple records in batches",
|
|
156
|
+
bulkDelete: "delete multiple records in batches",
|
|
157
|
+
executeSql: "execute custom SQL statements",
|
|
158
|
+
runQuery: "run SELECT queries",
|
|
159
|
+
beginTransaction: "start database transactions",
|
|
160
|
+
commitTransaction: "commit database transactions",
|
|
161
|
+
rollbackTransaction: "rollback database transactions",
|
|
162
|
+
executeInTransaction: "execute queries within transactions",
|
|
163
|
+
createStoredProcedure: "create stored procedures",
|
|
164
|
+
dropStoredProcedure: "delete stored procedures",
|
|
165
|
+
executeStoredProcedure: "execute stored procedures",
|
|
166
|
+
exportTableToCSV: "export table data to CSV",
|
|
167
|
+
exportQueryToCSV: "export query results to CSV",
|
|
157
168
|
};
|
|
158
169
|
const toolDescription = toolDescriptions[toolName] || actionDescriptions[category];
|
|
159
170
|
const requiredPermission = category;
|
|
160
|
-
return `Permission denied: Cannot ${toolDescription}. ` +
|
|
171
|
+
return (`Permission denied: Cannot ${toolDescription}. ` +
|
|
161
172
|
`This action requires '${requiredPermission}' permission, but your current MCP configuration only allows: ${currentPermissions}. ` +
|
|
162
|
-
`To enable this feature, update your MCP server configuration to include '${requiredPermission}' in the permissions list
|
|
173
|
+
`To enable this feature, update your MCP server configuration to include '${requiredPermission}' in the permissions list.`);
|
|
163
174
|
}
|
|
164
175
|
/**
|
|
165
176
|
* Check if a category is enabled
|
package/dist/db/connection.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import mysql from
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
2
|
declare class DatabaseConnection {
|
|
3
3
|
private static instance;
|
|
4
4
|
private pool;
|
|
5
5
|
private activeTransactions;
|
|
6
|
+
private queryCache;
|
|
6
7
|
private constructor();
|
|
7
8
|
static getInstance(): DatabaseConnection;
|
|
8
9
|
getConnection(): Promise<mysql.PoolConnection>;
|
|
9
|
-
query<T>(sql: string, params?: any[]): Promise<T>;
|
|
10
|
+
query<T>(sql: string, params?: any[], useCache?: boolean): Promise<T>;
|
|
11
|
+
/**
|
|
12
|
+
* Invalidate cache entries when write operations occur
|
|
13
|
+
*/
|
|
14
|
+
private invalidateCacheForWriteOperation;
|
|
10
15
|
testConnection(): Promise<{
|
|
11
16
|
connected: boolean;
|
|
12
17
|
latency: number;
|
|
@@ -20,6 +25,20 @@ declare class DatabaseConnection {
|
|
|
20
25
|
getQueryLogs(): import("./queryLogger").QueryLog[];
|
|
21
26
|
getLastQueryLog(): import("./queryLogger").QueryLog | undefined;
|
|
22
27
|
getFormattedQueryLogs(count?: number): string;
|
|
28
|
+
getCacheStats(): import("../cache/queryCache").CacheStats;
|
|
29
|
+
getCacheConfig(): import("../cache/queryCache").CacheConfig;
|
|
30
|
+
setCacheConfig(config: {
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
ttlMs?: number;
|
|
33
|
+
maxSize?: number;
|
|
34
|
+
maxMemoryMB?: number;
|
|
35
|
+
}): void;
|
|
36
|
+
clearCache(): number;
|
|
37
|
+
invalidateCache(pattern?: string | RegExp): number;
|
|
38
|
+
invalidateCacheForTable(tableName: string): number;
|
|
39
|
+
enableCache(): void;
|
|
40
|
+
disableCache(): void;
|
|
41
|
+
resetCacheStats(): void;
|
|
23
42
|
executeInTransaction<T>(transactionId: string, sql: string, params?: any[]): Promise<T>;
|
|
24
43
|
}
|
|
25
44
|
export default DatabaseConnection;
|