@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/README.md CHANGED
@@ -463,7 +463,7 @@ After (DDL enabled):
463
463
 
464
464
  ## 🛠️ Available Tools
465
465
 
466
- The MCP server provides **30 powerful tools**:
466
+ The MCP server provides **72 powerful tools**:
467
467
 
468
468
  ### Database Discovery (4 tools)
469
469
 
@@ -485,9 +485,7 @@ The MCP server provides **30 powerful tools**:
485
485
 
486
486
  ### Bulk Operations (3 tools)
487
487
 
488
- | Tool | Description |
489
-
490
- | Performance |
488
+ | Tool | Description | Performance |
491
489
  |------|-------------|-------------|
492
490
  | `bulk_insert` | Insert multiple records in batches for optimal performance | Up to 10,000 records per batch |
493
491
  | `bulk_update` | Update multiple records with different conditions in batches | Up to 1,000 operations per batch |
@@ -528,7 +526,7 @@ The MCP server provides **30 powerful tools**:
528
526
  | `get_transaction_status` | Check if a transaction is active |
529
527
  | `execute_in_transaction` | Execute SQL within a transaction context |
530
528
 
531
- ### Stored Procedures (5 tools)
529
+ ### Stored Procedures (6 tools)
532
530
 
533
531
  | Tool | Description | Requires |
534
532
  |------|-------------|----------|
@@ -537,6 +535,105 @@ The MCP server provides **30 powerful tools**:
537
535
  | `get_stored_procedure_info` | Get detailed information about a stored procedure | `procedure` permission |
538
536
  | `execute_stored_procedure` | Execute stored procedures with IN/OUT/INOUT parameters | `procedure` permission |
539
537
  | `drop_stored_procedure` | Delete stored procedures | `procedure` permission |
538
+ | `show_create_procedure` | Show CREATE statement for a stored procedure | `procedure` permission |
539
+
540
+ ### Views Management (6 tools) - NEW!
541
+
542
+ | Tool | Description | Requires |
543
+ |------|-------------|----------|
544
+ | `list_views` | List all views in the database | `list` permission |
545
+ | `get_view_info` | Get detailed information about a view | `list` permission |
546
+ | `create_view` | Create a new view with SELECT definition | `ddl` permission |
547
+ | `alter_view` | Alter an existing view definition | `ddl` permission |
548
+ | `drop_view` | Drop a view | `ddl` permission |
549
+ | `show_create_view` | Show CREATE statement for a view | `list` permission |
550
+
551
+ ### Triggers Management (5 tools) - NEW!
552
+
553
+ | Tool | Description | Requires |
554
+ |------|-------------|----------|
555
+ | `list_triggers` | List all triggers in the database | `list` permission |
556
+ | `get_trigger_info` | Get detailed information about a trigger | `list` permission |
557
+ | `create_trigger` | Create a new trigger on a table | `ddl` permission |
558
+ | `drop_trigger` | Drop a trigger | `ddl` permission |
559
+ | `show_create_trigger` | Show CREATE statement for a trigger | `list` permission |
560
+
561
+ ### Functions Management (6 tools) - NEW!
562
+
563
+ | Tool | Description | Requires |
564
+ |------|-------------|----------|
565
+ | `list_functions` | List all user-defined functions | `list` permission |
566
+ | `get_function_info` | Get detailed information about a function | `list` permission |
567
+ | `create_function` | Create a new user-defined function | `ddl` permission |
568
+ | `drop_function` | Drop a function | `ddl` permission |
569
+ | `show_create_function` | Show CREATE statement for a function | `list` permission |
570
+ | `execute_function` | Execute a function and return its result | `read` permission |
571
+
572
+ ### Index Management (5 tools) - NEW!
573
+
574
+ | Tool | Description | Requires |
575
+ |------|-------------|----------|
576
+ | `list_indexes` | List all indexes for a table | `list` permission |
577
+ | `get_index_info` | Get detailed information about an index | `list` permission |
578
+ | `create_index` | Create a new index (BTREE, HASH, FULLTEXT, SPATIAL) | `ddl` permission |
579
+ | `drop_index` | Drop an index from a table | `ddl` permission |
580
+ | `analyze_index` | Analyze index statistics | `utility` permission |
581
+
582
+ ### Constraint Management (7 tools) - NEW!
583
+
584
+ | Tool | Description | Requires |
585
+ |------|-------------|----------|
586
+ | `list_foreign_keys` | List all foreign keys for a table | `list` permission |
587
+ | `list_constraints` | List all constraints (PK, FK, UNIQUE, CHECK) | `list` permission |
588
+ | `add_foreign_key` | Add a foreign key constraint | `ddl` permission |
589
+ | `drop_foreign_key` | Drop a foreign key constraint | `ddl` permission |
590
+ | `add_unique_constraint` | Add a unique constraint | `ddl` permission |
591
+ | `drop_constraint` | Drop a UNIQUE or CHECK constraint | `ddl` permission |
592
+ | `add_check_constraint` | Add a CHECK constraint (MySQL 8.0.16+) | `ddl` permission |
593
+
594
+ ### Table Maintenance (8 tools) - NEW!
595
+
596
+ | Tool | Description | Requires |
597
+ |------|-------------|----------|
598
+ | `analyze_table` | Update index statistics for optimizer | `utility` permission |
599
+ | `optimize_table` | Reclaim unused space and defragment | `utility` permission |
600
+ | `check_table` | Check table for errors | `utility` permission |
601
+ | `repair_table` | Repair corrupted table (MyISAM, ARCHIVE, CSV) | `utility` permission |
602
+ | `truncate_table` | Remove all rows quickly | `ddl` permission |
603
+ | `get_table_status` | Get detailed table status and statistics | `list` permission |
604
+ | `flush_table` | Close and reopen table(s) | `utility` permission |
605
+ | `get_table_size` | Get size information for tables | `list` permission |
606
+
607
+ ### Process & Server Management (9 tools) - NEW!
608
+
609
+ | Tool | Description | Requires |
610
+ |------|-------------|----------|
611
+ | `show_process_list` | Show all running MySQL processes | `utility` permission |
612
+ | `kill_process` | Kill a MySQL process/connection | `utility` permission |
613
+ | `show_status` | Show MySQL server status variables | `utility` permission |
614
+ | `show_variables` | Show MySQL server configuration variables | `utility` permission |
615
+ | `explain_query` | Show query execution plan (EXPLAIN) | `utility` permission |
616
+ | `show_engine_status` | Show storage engine status (InnoDB) | `utility` permission |
617
+ | `get_server_info` | Get comprehensive server information | `utility` permission |
618
+ | `show_binary_logs` | Show binary log files | `utility` permission |
619
+ | `show_replication_status` | Show replication status | `utility` permission |
620
+
621
+ ### Cache Management (5 tools)
622
+
623
+ | Tool | Description |
624
+ |------|-------------|
625
+ | `get_cache_stats` | Get query cache statistics |
626
+ | `get_cache_config` | Get current cache configuration |
627
+ | `configure_cache` | Configure cache settings (TTL, max size) |
628
+ | `clear_cache` | Clear all cached query results |
629
+ | `invalidate_table_cache` | Invalidate cache for a specific table |
630
+
631
+ ### Query Optimization (2 tools)
632
+
633
+ | Tool | Description |
634
+ |------|-------------|
635
+ | `analyze_query` | Analyze query and get optimization suggestions |
636
+ | `get_optimization_hints` | Get optimizer hints for SPEED, MEMORY, or STABILITY goals |
540
637
 
541
638
  ---
542
639
 
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Cache entry interface
3
+ */
4
+ export interface CacheEntry {
5
+ data: any;
6
+ timestamp: number;
7
+ hitCount: number;
8
+ queryHash: string;
9
+ sql: string;
10
+ params?: any[];
11
+ }
12
+ /**
13
+ * Cache statistics interface
14
+ */
15
+ export interface CacheStats {
16
+ totalHits: number;
17
+ totalMisses: number;
18
+ hitRate: number;
19
+ currentSize: number;
20
+ maxSize: number;
21
+ ttlMs: number;
22
+ enabled: boolean;
23
+ }
24
+ /**
25
+ * Cache configuration interface
26
+ */
27
+ export interface CacheConfig {
28
+ enabled: boolean;
29
+ ttlMs: number;
30
+ maxSize: number;
31
+ maxMemoryMB: number;
32
+ }
33
+ /**
34
+ * LRU Query Cache with TTL support
35
+ * Implements an in-memory cache for query results to improve performance
36
+ */
37
+ export declare class QueryCache {
38
+ private static instance;
39
+ private cache;
40
+ private config;
41
+ private stats;
42
+ private accessOrder;
43
+ private constructor();
44
+ /**
45
+ * Get singleton instance
46
+ */
47
+ static getInstance(): QueryCache;
48
+ /**
49
+ * Load cache configuration from environment variables
50
+ */
51
+ private loadConfigFromEnv;
52
+ /**
53
+ * Generate a unique hash for a query and its parameters
54
+ */
55
+ private generateHash;
56
+ /**
57
+ * Check if a query result is cached and valid
58
+ */
59
+ get(sql: string, params?: any[]): CacheEntry | null;
60
+ /**
61
+ * Cache a query result
62
+ */
63
+ set(sql: string, params: any[] | undefined, data: any): void;
64
+ /**
65
+ * Invalidate cache entries matching a pattern
66
+ * Used when data is modified (INSERT, UPDATE, DELETE)
67
+ */
68
+ invalidate(pattern?: string | RegExp): number;
69
+ /**
70
+ * Escape special regex characters in a string
71
+ */
72
+ private escapeRegex;
73
+ /**
74
+ * Invalidate cache entries related to a specific table
75
+ */
76
+ invalidateTable(tableName: string): number;
77
+ /**
78
+ * Evict least recently used entries if cache is full
79
+ */
80
+ private evictIfNeeded;
81
+ /**
82
+ * Update access order for LRU tracking
83
+ */
84
+ private updateAccessOrder;
85
+ /**
86
+ * Remove hash from access order
87
+ */
88
+ private removeFromAccessOrder;
89
+ /**
90
+ * Estimate memory usage of the cache
91
+ */
92
+ private estimateMemoryUsage;
93
+ /**
94
+ * Get cache statistics
95
+ */
96
+ getStats(): CacheStats;
97
+ /**
98
+ * Get current cache configuration
99
+ */
100
+ getConfig(): CacheConfig;
101
+ /**
102
+ * Update cache configuration
103
+ */
104
+ setConfig(config: Partial<CacheConfig>): void;
105
+ /**
106
+ * Enable caching
107
+ */
108
+ enable(): void;
109
+ /**
110
+ * Disable caching
111
+ */
112
+ disable(): void;
113
+ /**
114
+ * Clear all cache entries
115
+ */
116
+ clear(): void;
117
+ /**
118
+ * Reset statistics
119
+ */
120
+ resetStats(): void;
121
+ /**
122
+ * Get all cached entries (for debugging)
123
+ */
124
+ getAllEntries(): CacheEntry[];
125
+ }
126
+ export default QueryCache;
@@ -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;