@berthojoris/mcp-mysql-server 1.9.3 → 1.10.1
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/CHANGELOG.md +47 -0
- package/DOCUMENTATIONS.md +3847 -3145
- package/README.md +151 -13
- package/bin/mcp-mysql.js +110 -53
- package/dist/config/featureConfig.d.ts +72 -9
- package/dist/config/featureConfig.js +415 -87
- package/dist/db/connection.d.ts +2 -0
- package/dist/db/connection.js +7 -1
- package/dist/index.d.ts +83 -1
- package/dist/index.js +75 -2
- package/dist/mcp-server.js +179 -7
- package/dist/security/securityLayer.d.ts +5 -1
- package/dist/security/securityLayer.js +18 -1
- package/dist/tools/performanceTools.d.ts +111 -0
- package/dist/tools/performanceTools.js +623 -0
- package/dist/tools/utilityTools.js +115 -24
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -21,9 +21,10 @@ export declare class MySQLMCP {
|
|
|
21
21
|
private backupRestoreTools;
|
|
22
22
|
private migrationTools;
|
|
23
23
|
private schemaVersioningTools;
|
|
24
|
+
private performanceTools;
|
|
24
25
|
private security;
|
|
25
26
|
private featureConfig;
|
|
26
|
-
constructor(permissionsConfig?: string);
|
|
27
|
+
constructor(permissionsConfig?: string, categoriesConfig?: string);
|
|
27
28
|
private checkToolEnabled;
|
|
28
29
|
listDatabases(): Promise<{
|
|
29
30
|
status: string;
|
|
@@ -587,6 +588,12 @@ export declare class MySQLMCP {
|
|
|
587
588
|
categoryStatus: Record<import("./config/featureConfig").ToolCategory, boolean>;
|
|
588
589
|
};
|
|
589
590
|
};
|
|
591
|
+
/**
|
|
592
|
+
* Check if a specific tool is enabled based on current permissions and categories
|
|
593
|
+
* @param toolName - The tool name in camelCase (e.g., 'listDatabases')
|
|
594
|
+
* @returns boolean indicating if the tool is enabled
|
|
595
|
+
*/
|
|
596
|
+
isToolEnabled(toolName: string): boolean;
|
|
590
597
|
/**
|
|
591
598
|
* Bulk insert multiple records into the specified table
|
|
592
599
|
*/
|
|
@@ -1093,5 +1100,80 @@ export declare class MySQLMCP {
|
|
|
1093
1100
|
error?: string;
|
|
1094
1101
|
queryLog?: string;
|
|
1095
1102
|
}>;
|
|
1103
|
+
getPerformanceMetrics(): Promise<{
|
|
1104
|
+
status: string;
|
|
1105
|
+
data?: any;
|
|
1106
|
+
error?: string;
|
|
1107
|
+
queryLog?: string;
|
|
1108
|
+
}>;
|
|
1109
|
+
getTopQueriesByTime(params?: {
|
|
1110
|
+
limit?: number;
|
|
1111
|
+
}): Promise<{
|
|
1112
|
+
status: string;
|
|
1113
|
+
data?: any[];
|
|
1114
|
+
error?: string;
|
|
1115
|
+
queryLog?: string;
|
|
1116
|
+
}>;
|
|
1117
|
+
getTopQueriesByCount(params?: {
|
|
1118
|
+
limit?: number;
|
|
1119
|
+
}): Promise<{
|
|
1120
|
+
status: string;
|
|
1121
|
+
data?: any[];
|
|
1122
|
+
error?: string;
|
|
1123
|
+
queryLog?: string;
|
|
1124
|
+
}>;
|
|
1125
|
+
getSlowQueries(params?: {
|
|
1126
|
+
limit?: number;
|
|
1127
|
+
threshold_seconds?: number;
|
|
1128
|
+
}): Promise<{
|
|
1129
|
+
status: string;
|
|
1130
|
+
data?: any[];
|
|
1131
|
+
error?: string;
|
|
1132
|
+
queryLog?: string;
|
|
1133
|
+
}>;
|
|
1134
|
+
getTableIOStats(params?: {
|
|
1135
|
+
limit?: number;
|
|
1136
|
+
table_schema?: string;
|
|
1137
|
+
}): Promise<{
|
|
1138
|
+
status: string;
|
|
1139
|
+
data?: any[];
|
|
1140
|
+
error?: string;
|
|
1141
|
+
queryLog?: string;
|
|
1142
|
+
}>;
|
|
1143
|
+
getIndexUsageStats(params?: {
|
|
1144
|
+
limit?: number;
|
|
1145
|
+
table_schema?: string;
|
|
1146
|
+
}): Promise<{
|
|
1147
|
+
status: string;
|
|
1148
|
+
data?: any[];
|
|
1149
|
+
error?: string;
|
|
1150
|
+
queryLog?: string;
|
|
1151
|
+
}>;
|
|
1152
|
+
getUnusedIndexes(params?: {
|
|
1153
|
+
table_schema?: string;
|
|
1154
|
+
}): Promise<{
|
|
1155
|
+
status: string;
|
|
1156
|
+
data?: any[];
|
|
1157
|
+
error?: string;
|
|
1158
|
+
queryLog?: string;
|
|
1159
|
+
}>;
|
|
1160
|
+
getConnectionPoolStats(): Promise<{
|
|
1161
|
+
status: string;
|
|
1162
|
+
data?: any;
|
|
1163
|
+
error?: string;
|
|
1164
|
+
queryLog?: string;
|
|
1165
|
+
}>;
|
|
1166
|
+
getDatabaseHealthCheck(): Promise<{
|
|
1167
|
+
status: string;
|
|
1168
|
+
data?: any;
|
|
1169
|
+
error?: string;
|
|
1170
|
+
queryLog?: string;
|
|
1171
|
+
}>;
|
|
1172
|
+
resetPerformanceStats(): Promise<{
|
|
1173
|
+
status: string;
|
|
1174
|
+
message?: string;
|
|
1175
|
+
error?: string;
|
|
1176
|
+
queryLog?: string;
|
|
1177
|
+
}>;
|
|
1096
1178
|
}
|
|
1097
1179
|
export default MySQLMCP;
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ const processTools_1 = require("./tools/processTools");
|
|
|
22
22
|
const backupRestoreTools_1 = require("./tools/backupRestoreTools");
|
|
23
23
|
const migrationTools_1 = require("./tools/migrationTools");
|
|
24
24
|
const schemaVersioningTools_1 = require("./tools/schemaVersioningTools");
|
|
25
|
+
const performanceTools_1 = require("./tools/performanceTools");
|
|
25
26
|
const securityLayer_1 = __importDefault(require("./security/securityLayer"));
|
|
26
27
|
const connection_1 = __importDefault(require("./db/connection"));
|
|
27
28
|
const featureConfig_1 = require("./config/featureConfig");
|
|
@@ -30,8 +31,8 @@ const featureConfig_1 = require("./config/featureConfig");
|
|
|
30
31
|
* A secure interface for AI models to interact with MySQL databases
|
|
31
32
|
*/
|
|
32
33
|
class MySQLMCP {
|
|
33
|
-
constructor(permissionsConfig) {
|
|
34
|
-
this.featureConfig = new featureConfig_1.FeatureConfig(permissionsConfig);
|
|
34
|
+
constructor(permissionsConfig, categoriesConfig) {
|
|
35
|
+
this.featureConfig = new featureConfig_1.FeatureConfig(permissionsConfig, categoriesConfig);
|
|
35
36
|
this.security = new securityLayer_1.default(this.featureConfig);
|
|
36
37
|
this.dbTools = new databaseTools_1.DatabaseTools();
|
|
37
38
|
this.crudTools = new crudTools_1.CrudTools(this.security);
|
|
@@ -51,6 +52,7 @@ class MySQLMCP {
|
|
|
51
52
|
this.backupRestoreTools = new backupRestoreTools_1.BackupRestoreTools(this.security);
|
|
52
53
|
this.migrationTools = new migrationTools_1.MigrationTools(this.security);
|
|
53
54
|
this.schemaVersioningTools = new schemaVersioningTools_1.SchemaVersioningTools(this.security);
|
|
55
|
+
this.performanceTools = new performanceTools_1.PerformanceTools(this.security);
|
|
54
56
|
}
|
|
55
57
|
// Helper method to check if tool is enabled
|
|
56
58
|
checkToolEnabled(toolName) {
|
|
@@ -502,6 +504,14 @@ class MySQLMCP {
|
|
|
502
504
|
},
|
|
503
505
|
};
|
|
504
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Check if a specific tool is enabled based on current permissions and categories
|
|
509
|
+
* @param toolName - The tool name in camelCase (e.g., 'listDatabases')
|
|
510
|
+
* @returns boolean indicating if the tool is enabled
|
|
511
|
+
*/
|
|
512
|
+
isToolEnabled(toolName) {
|
|
513
|
+
return this.featureConfig.isToolEnabled(toolName);
|
|
514
|
+
}
|
|
505
515
|
/**
|
|
506
516
|
* Bulk insert multiple records into the specified table
|
|
507
517
|
*/
|
|
@@ -925,6 +935,69 @@ class MySQLMCP {
|
|
|
925
935
|
return { status: "error", error: check.error };
|
|
926
936
|
return await this.processTools.showReplicationStatus(params);
|
|
927
937
|
}
|
|
938
|
+
// ==========================================
|
|
939
|
+
// Performance Monitoring Tools
|
|
940
|
+
// ==========================================
|
|
941
|
+
async getPerformanceMetrics() {
|
|
942
|
+
const check = this.checkToolEnabled("getPerformanceMetrics");
|
|
943
|
+
if (!check.enabled)
|
|
944
|
+
return { status: "error", error: check.error };
|
|
945
|
+
return await this.performanceTools.getPerformanceMetrics();
|
|
946
|
+
}
|
|
947
|
+
async getTopQueriesByTime(params) {
|
|
948
|
+
const check = this.checkToolEnabled("getTopQueriesByTime");
|
|
949
|
+
if (!check.enabled)
|
|
950
|
+
return { status: "error", error: check.error };
|
|
951
|
+
return await this.performanceTools.getTopQueriesByTime(params);
|
|
952
|
+
}
|
|
953
|
+
async getTopQueriesByCount(params) {
|
|
954
|
+
const check = this.checkToolEnabled("getTopQueriesByCount");
|
|
955
|
+
if (!check.enabled)
|
|
956
|
+
return { status: "error", error: check.error };
|
|
957
|
+
return await this.performanceTools.getTopQueriesByCount(params);
|
|
958
|
+
}
|
|
959
|
+
async getSlowQueries(params) {
|
|
960
|
+
const check = this.checkToolEnabled("getSlowQueries");
|
|
961
|
+
if (!check.enabled)
|
|
962
|
+
return { status: "error", error: check.error };
|
|
963
|
+
return await this.performanceTools.getSlowQueries(params);
|
|
964
|
+
}
|
|
965
|
+
async getTableIOStats(params) {
|
|
966
|
+
const check = this.checkToolEnabled("getTableIOStats");
|
|
967
|
+
if (!check.enabled)
|
|
968
|
+
return { status: "error", error: check.error };
|
|
969
|
+
return await this.performanceTools.getTableIOStats(params);
|
|
970
|
+
}
|
|
971
|
+
async getIndexUsageStats(params) {
|
|
972
|
+
const check = this.checkToolEnabled("getIndexUsageStats");
|
|
973
|
+
if (!check.enabled)
|
|
974
|
+
return { status: "error", error: check.error };
|
|
975
|
+
return await this.performanceTools.getIndexUsageStats(params);
|
|
976
|
+
}
|
|
977
|
+
async getUnusedIndexes(params) {
|
|
978
|
+
const check = this.checkToolEnabled("getUnusedIndexes");
|
|
979
|
+
if (!check.enabled)
|
|
980
|
+
return { status: "error", error: check.error };
|
|
981
|
+
return await this.performanceTools.getUnusedIndexes(params);
|
|
982
|
+
}
|
|
983
|
+
async getConnectionPoolStats() {
|
|
984
|
+
const check = this.checkToolEnabled("getConnectionPoolStats");
|
|
985
|
+
if (!check.enabled)
|
|
986
|
+
return { status: "error", error: check.error };
|
|
987
|
+
return await this.performanceTools.getConnectionPoolStats();
|
|
988
|
+
}
|
|
989
|
+
async getDatabaseHealthCheck() {
|
|
990
|
+
const check = this.checkToolEnabled("getDatabaseHealthCheck");
|
|
991
|
+
if (!check.enabled)
|
|
992
|
+
return { status: "error", error: check.error };
|
|
993
|
+
return await this.performanceTools.getDatabaseHealthCheck();
|
|
994
|
+
}
|
|
995
|
+
async resetPerformanceStats() {
|
|
996
|
+
const check = this.checkToolEnabled("resetPerformanceStats");
|
|
997
|
+
if (!check.enabled)
|
|
998
|
+
return { status: "error", error: check.error };
|
|
999
|
+
return await this.performanceTools.resetPerformanceStats();
|
|
1000
|
+
}
|
|
928
1001
|
}
|
|
929
1002
|
exports.MySQLMCP = MySQLMCP;
|
|
930
1003
|
exports.default = MySQLMCP;
|
package/dist/mcp-server.js
CHANGED
|
@@ -5,9 +5,11 @@ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
|
5
5
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
6
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
7
|
const index_js_2 = require("./index.js");
|
|
8
|
-
// Get permissions from environment
|
|
9
|
-
//
|
|
8
|
+
// Get permissions and categories from environment variables (set by bin/mcp-mysql.js)
|
|
9
|
+
// Layer 1 (Permissions): MCP_PERMISSIONS or MCP_CONFIG (backward compatible)
|
|
10
|
+
// Layer 2 (Categories): MCP_CATEGORIES (optional, for fine-grained control)
|
|
10
11
|
const permissions = process.env.MCP_PERMISSIONS || process.env.MCP_CONFIG || "";
|
|
12
|
+
const categories = process.env.MCP_CATEGORIES || "";
|
|
11
13
|
// Declare the MySQL MCP instance (will be initialized in main())
|
|
12
14
|
let mysqlMCP;
|
|
13
15
|
// Define all available tools with their schemas
|
|
@@ -2558,6 +2560,129 @@ const TOOLS = [
|
|
|
2558
2560
|
required: ["table1", "table2", "migration_name"],
|
|
2559
2561
|
},
|
|
2560
2562
|
},
|
|
2563
|
+
// Performance Monitoring Tools
|
|
2564
|
+
{
|
|
2565
|
+
name: "get_performance_metrics",
|
|
2566
|
+
description: "Get comprehensive performance metrics including query performance, connection stats, buffer pool metrics, and InnoDB statistics.",
|
|
2567
|
+
inputSchema: {
|
|
2568
|
+
type: "object",
|
|
2569
|
+
properties: {},
|
|
2570
|
+
},
|
|
2571
|
+
},
|
|
2572
|
+
{
|
|
2573
|
+
name: "get_top_queries_by_time",
|
|
2574
|
+
description: "Get the top queries ordered by total execution time. Useful for identifying slow queries.",
|
|
2575
|
+
inputSchema: {
|
|
2576
|
+
type: "object",
|
|
2577
|
+
properties: {
|
|
2578
|
+
limit: {
|
|
2579
|
+
type: "number",
|
|
2580
|
+
description: "Maximum number of queries to return (default: 10, max: 100)",
|
|
2581
|
+
},
|
|
2582
|
+
},
|
|
2583
|
+
},
|
|
2584
|
+
},
|
|
2585
|
+
{
|
|
2586
|
+
name: "get_top_queries_by_count",
|
|
2587
|
+
description: "Get the top queries ordered by execution count. Useful for identifying frequently executed queries.",
|
|
2588
|
+
inputSchema: {
|
|
2589
|
+
type: "object",
|
|
2590
|
+
properties: {
|
|
2591
|
+
limit: {
|
|
2592
|
+
type: "number",
|
|
2593
|
+
description: "Maximum number of queries to return (default: 10, max: 100)",
|
|
2594
|
+
},
|
|
2595
|
+
},
|
|
2596
|
+
},
|
|
2597
|
+
},
|
|
2598
|
+
{
|
|
2599
|
+
name: "get_slow_queries",
|
|
2600
|
+
description: "Get queries that exceed a specified execution time threshold.",
|
|
2601
|
+
inputSchema: {
|
|
2602
|
+
type: "object",
|
|
2603
|
+
properties: {
|
|
2604
|
+
limit: {
|
|
2605
|
+
type: "number",
|
|
2606
|
+
description: "Maximum number of queries to return (default: 10, max: 100)",
|
|
2607
|
+
},
|
|
2608
|
+
threshold_seconds: {
|
|
2609
|
+
type: "number",
|
|
2610
|
+
description: "Execution time threshold in seconds (default: 1)",
|
|
2611
|
+
},
|
|
2612
|
+
},
|
|
2613
|
+
},
|
|
2614
|
+
},
|
|
2615
|
+
{
|
|
2616
|
+
name: "get_table_io_stats",
|
|
2617
|
+
description: "Get I/O statistics for tables including read/write operations and timings.",
|
|
2618
|
+
inputSchema: {
|
|
2619
|
+
type: "object",
|
|
2620
|
+
properties: {
|
|
2621
|
+
limit: {
|
|
2622
|
+
type: "number",
|
|
2623
|
+
description: "Maximum number of tables to return (default: 20, max: 100)",
|
|
2624
|
+
},
|
|
2625
|
+
table_schema: {
|
|
2626
|
+
type: "string",
|
|
2627
|
+
description: "Filter by specific database schema",
|
|
2628
|
+
},
|
|
2629
|
+
},
|
|
2630
|
+
},
|
|
2631
|
+
},
|
|
2632
|
+
{
|
|
2633
|
+
name: "get_index_usage_stats",
|
|
2634
|
+
description: "Get index usage statistics showing how often each index is used.",
|
|
2635
|
+
inputSchema: {
|
|
2636
|
+
type: "object",
|
|
2637
|
+
properties: {
|
|
2638
|
+
limit: {
|
|
2639
|
+
type: "number",
|
|
2640
|
+
description: "Maximum number of indexes to return (default: 20, max: 100)",
|
|
2641
|
+
},
|
|
2642
|
+
table_schema: {
|
|
2643
|
+
type: "string",
|
|
2644
|
+
description: "Filter by specific database schema",
|
|
2645
|
+
},
|
|
2646
|
+
},
|
|
2647
|
+
},
|
|
2648
|
+
},
|
|
2649
|
+
{
|
|
2650
|
+
name: "get_unused_indexes",
|
|
2651
|
+
description: "Identify indexes that are not being used by queries. These may be candidates for removal to improve write performance.",
|
|
2652
|
+
inputSchema: {
|
|
2653
|
+
type: "object",
|
|
2654
|
+
properties: {
|
|
2655
|
+
table_schema: {
|
|
2656
|
+
type: "string",
|
|
2657
|
+
description: "Filter by specific database schema",
|
|
2658
|
+
},
|
|
2659
|
+
},
|
|
2660
|
+
},
|
|
2661
|
+
},
|
|
2662
|
+
{
|
|
2663
|
+
name: "get_connection_pool_stats",
|
|
2664
|
+
description: "Get connection pool statistics including current connections, max usage, configuration, and health indicators.",
|
|
2665
|
+
inputSchema: {
|
|
2666
|
+
type: "object",
|
|
2667
|
+
properties: {},
|
|
2668
|
+
},
|
|
2669
|
+
},
|
|
2670
|
+
{
|
|
2671
|
+
name: "get_database_health_check",
|
|
2672
|
+
description: "Perform a comprehensive health check of the database including connection usage, buffer pool efficiency, aborted connections, and slow queries.",
|
|
2673
|
+
inputSchema: {
|
|
2674
|
+
type: "object",
|
|
2675
|
+
properties: {},
|
|
2676
|
+
},
|
|
2677
|
+
},
|
|
2678
|
+
{
|
|
2679
|
+
name: "reset_performance_stats",
|
|
2680
|
+
description: "Reset performance schema statistics. This clears query digest statistics, table I/O stats, and index usage stats. Requires 'utility' permission.",
|
|
2681
|
+
inputSchema: {
|
|
2682
|
+
type: "object",
|
|
2683
|
+
properties: {},
|
|
2684
|
+
},
|
|
2685
|
+
},
|
|
2561
2686
|
];
|
|
2562
2687
|
// Create the MCP server
|
|
2563
2688
|
const server = new index_js_1.Server({
|
|
@@ -2568,10 +2693,20 @@ const server = new index_js_1.Server({
|
|
|
2568
2693
|
tools: {},
|
|
2569
2694
|
},
|
|
2570
2695
|
});
|
|
2571
|
-
// Handle list tools request
|
|
2696
|
+
// Handle list tools request - filter tools based on permissions and categories
|
|
2572
2697
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
2698
|
+
// Filter tools to only return those that are enabled based on current config
|
|
2699
|
+
const enabledTools = TOOLS.filter((tool) => {
|
|
2700
|
+
// Convert tool name from snake_case to camelCase for checking
|
|
2701
|
+
// e.g., "list_databases" -> "listDatabases"
|
|
2702
|
+
const toolNameCamelCase = tool.name.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
2703
|
+
// Check if tool is enabled based on permissions and categories
|
|
2704
|
+
return mysqlMCP.isToolEnabled(toolNameCamelCase);
|
|
2705
|
+
});
|
|
2706
|
+
// Log the filtering results
|
|
2707
|
+
console.error(`Tools available: ${enabledTools.length} of ${TOOLS.length} total tools`);
|
|
2573
2708
|
return {
|
|
2574
|
-
tools:
|
|
2709
|
+
tools: enabledTools,
|
|
2575
2710
|
};
|
|
2576
2711
|
});
|
|
2577
2712
|
// Handle tool call requests
|
|
@@ -2924,6 +3059,37 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
2924
3059
|
case "generate_migration_from_diff":
|
|
2925
3060
|
result = await mysqlMCP.generateMigrationFromDiff((args || {}));
|
|
2926
3061
|
break;
|
|
3062
|
+
// Performance Monitoring Tools
|
|
3063
|
+
case "get_performance_metrics":
|
|
3064
|
+
result = await mysqlMCP.getPerformanceMetrics();
|
|
3065
|
+
break;
|
|
3066
|
+
case "get_top_queries_by_time":
|
|
3067
|
+
result = await mysqlMCP.getTopQueriesByTime((args || {}));
|
|
3068
|
+
break;
|
|
3069
|
+
case "get_top_queries_by_count":
|
|
3070
|
+
result = await mysqlMCP.getTopQueriesByCount((args || {}));
|
|
3071
|
+
break;
|
|
3072
|
+
case "get_slow_queries":
|
|
3073
|
+
result = await mysqlMCP.getSlowQueries((args || {}));
|
|
3074
|
+
break;
|
|
3075
|
+
case "get_table_io_stats":
|
|
3076
|
+
result = await mysqlMCP.getTableIOStats((args || {}));
|
|
3077
|
+
break;
|
|
3078
|
+
case "get_index_usage_stats":
|
|
3079
|
+
result = await mysqlMCP.getIndexUsageStats((args || {}));
|
|
3080
|
+
break;
|
|
3081
|
+
case "get_unused_indexes":
|
|
3082
|
+
result = await mysqlMCP.getUnusedIndexes((args || {}));
|
|
3083
|
+
break;
|
|
3084
|
+
case "get_connection_pool_stats":
|
|
3085
|
+
result = await mysqlMCP.getConnectionPoolStats();
|
|
3086
|
+
break;
|
|
3087
|
+
case "get_database_health_check":
|
|
3088
|
+
result = await mysqlMCP.getDatabaseHealthCheck();
|
|
3089
|
+
break;
|
|
3090
|
+
case "reset_performance_stats":
|
|
3091
|
+
result = await mysqlMCP.resetPerformanceStats();
|
|
3092
|
+
break;
|
|
2927
3093
|
default:
|
|
2928
3094
|
throw new Error(`Unknown tool: ${name}`);
|
|
2929
3095
|
}
|
|
@@ -3028,10 +3194,16 @@ async function main() {
|
|
|
3028
3194
|
await server.connect(transport);
|
|
3029
3195
|
// Initialize the MySQL MCP instance AFTER transport is connected
|
|
3030
3196
|
// This ensures the database connection pool is created when the server is ready
|
|
3031
|
-
mysqlMCP = new index_js_2.MySQLMCP(permissions);
|
|
3032
|
-
// Log the effective
|
|
3033
|
-
if (permissions) {
|
|
3197
|
+
mysqlMCP = new index_js_2.MySQLMCP(permissions, categories);
|
|
3198
|
+
// Log the effective filtering configuration to stderr
|
|
3199
|
+
if (permissions && categories) {
|
|
3200
|
+
console.error(`Active permissions (Layer 1): ${permissions}`);
|
|
3201
|
+
console.error(`Active categories (Layer 2): ${categories}`);
|
|
3202
|
+
console.error("Filtering mode: Dual-layer");
|
|
3203
|
+
}
|
|
3204
|
+
else if (permissions) {
|
|
3034
3205
|
console.error(`Active permissions: ${permissions}`);
|
|
3206
|
+
console.error("Filtering mode: Single-layer");
|
|
3035
3207
|
}
|
|
3036
3208
|
else {
|
|
3037
3209
|
console.error("Active permissions: all (default)");
|
|
@@ -6,6 +6,10 @@ export declare class SecurityLayer {
|
|
|
6
6
|
private readonly ddlOperations;
|
|
7
7
|
private featureConfig;
|
|
8
8
|
constructor(featureConfig?: FeatureConfig);
|
|
9
|
+
/**
|
|
10
|
+
* Check if a query is a read-only information query (SHOW, DESCRIBE, EXPLAIN, etc.)
|
|
11
|
+
*/
|
|
12
|
+
private isInformationQuery;
|
|
9
13
|
/**
|
|
10
14
|
* Validate input against a JSON schema
|
|
11
15
|
*/
|
|
@@ -39,7 +43,7 @@ export declare class SecurityLayer {
|
|
|
39
43
|
sanitizedParams?: any[];
|
|
40
44
|
};
|
|
41
45
|
/**
|
|
42
|
-
* Check if a query is a read-only SELECT query
|
|
46
|
+
* Check if a query is a read-only SELECT query or information query (SHOW, DESCRIBE, etc.)
|
|
43
47
|
*/
|
|
44
48
|
isReadOnlyQuery(query: string): boolean;
|
|
45
49
|
/**
|
|
@@ -34,6 +34,14 @@ class SecurityLayer {
|
|
|
34
34
|
// Define DDL operations that require special permission
|
|
35
35
|
this.ddlOperations = ["CREATE", "ALTER", "DROP", "TRUNCATE", "RENAME"];
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Check if a query is a read-only information query (SHOW, DESCRIBE, EXPLAIN, etc.)
|
|
39
|
+
*/
|
|
40
|
+
isInformationQuery(query) {
|
|
41
|
+
const trimmedQuery = query.trim().toUpperCase();
|
|
42
|
+
const readOnlyCommands = ["SHOW", "DESCRIBE", "DESC", "EXPLAIN", "HELP"];
|
|
43
|
+
return readOnlyCommands.some((cmd) => trimmedQuery.startsWith(cmd));
|
|
44
|
+
}
|
|
37
45
|
/**
|
|
38
46
|
* Validate input against a JSON schema
|
|
39
47
|
*/
|
|
@@ -132,6 +140,10 @@ class SecurityLayer {
|
|
|
132
140
|
}
|
|
133
141
|
// Remove trailing semicolon for analysis
|
|
134
142
|
const cleanQuery = trimmedQuery.replace(/;$/, "");
|
|
143
|
+
// Check if it's an information query (SHOW, DESCRIBE, EXPLAIN, etc.) - these are always allowed
|
|
144
|
+
if (this.isInformationQuery(trimmedQuery)) {
|
|
145
|
+
return { valid: true, queryType: "INFORMATION" };
|
|
146
|
+
}
|
|
135
147
|
// Determine query type - check basic operations first
|
|
136
148
|
let queryType = "";
|
|
137
149
|
for (const operation of this.allowedOperations) {
|
|
@@ -257,9 +269,14 @@ class SecurityLayer {
|
|
|
257
269
|
return { valid: true, sanitizedParams };
|
|
258
270
|
}
|
|
259
271
|
/**
|
|
260
|
-
* Check if a query is a read-only SELECT query
|
|
272
|
+
* Check if a query is a read-only SELECT query or information query (SHOW, DESCRIBE, etc.)
|
|
261
273
|
*/
|
|
262
274
|
isReadOnlyQuery(query) {
|
|
275
|
+
// Check if it's an information query first (SHOW, DESCRIBE, EXPLAIN, etc.)
|
|
276
|
+
if (this.isInformationQuery(query)) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
// Check if it's a SELECT query
|
|
263
280
|
const validation = this.validateQuery(query);
|
|
264
281
|
return validation.valid && validation.queryType === "SELECT";
|
|
265
282
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { SecurityLayer } from '../security/securityLayer';
|
|
2
|
+
export declare class PerformanceTools {
|
|
3
|
+
private db;
|
|
4
|
+
private security;
|
|
5
|
+
constructor(security: SecurityLayer);
|
|
6
|
+
/**
|
|
7
|
+
* Get comprehensive performance metrics
|
|
8
|
+
*/
|
|
9
|
+
getPerformanceMetrics(): Promise<{
|
|
10
|
+
status: string;
|
|
11
|
+
data?: any;
|
|
12
|
+
error?: string;
|
|
13
|
+
queryLog?: string;
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Get top queries by execution time
|
|
17
|
+
*/
|
|
18
|
+
getTopQueriesByTime(params?: {
|
|
19
|
+
limit?: number;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
status: string;
|
|
22
|
+
data?: any[];
|
|
23
|
+
error?: string;
|
|
24
|
+
queryLog?: string;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Get top queries by execution count
|
|
28
|
+
*/
|
|
29
|
+
getTopQueriesByCount(params?: {
|
|
30
|
+
limit?: number;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
status: string;
|
|
33
|
+
data?: any[];
|
|
34
|
+
error?: string;
|
|
35
|
+
queryLog?: string;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Get slow queries
|
|
39
|
+
*/
|
|
40
|
+
getSlowQueries(params?: {
|
|
41
|
+
limit?: number;
|
|
42
|
+
threshold_seconds?: number;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
status: string;
|
|
45
|
+
data?: any[];
|
|
46
|
+
error?: string;
|
|
47
|
+
queryLog?: string;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Get table I/O statistics
|
|
51
|
+
*/
|
|
52
|
+
getTableIOStats(params?: {
|
|
53
|
+
limit?: number;
|
|
54
|
+
table_schema?: string;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
status: string;
|
|
57
|
+
data?: any[];
|
|
58
|
+
error?: string;
|
|
59
|
+
queryLog?: string;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Get index usage statistics
|
|
63
|
+
*/
|
|
64
|
+
getIndexUsageStats(params?: {
|
|
65
|
+
limit?: number;
|
|
66
|
+
table_schema?: string;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
status: string;
|
|
69
|
+
data?: any[];
|
|
70
|
+
error?: string;
|
|
71
|
+
queryLog?: string;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Get unused indexes
|
|
75
|
+
*/
|
|
76
|
+
getUnusedIndexes(params?: {
|
|
77
|
+
table_schema?: string;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
status: string;
|
|
80
|
+
data?: any[];
|
|
81
|
+
error?: string;
|
|
82
|
+
queryLog?: string;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Get connection pool statistics
|
|
86
|
+
*/
|
|
87
|
+
getConnectionPoolStats(): Promise<{
|
|
88
|
+
status: string;
|
|
89
|
+
data?: any;
|
|
90
|
+
error?: string;
|
|
91
|
+
queryLog?: string;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Get database health check
|
|
95
|
+
*/
|
|
96
|
+
getDatabaseHealthCheck(): Promise<{
|
|
97
|
+
status: string;
|
|
98
|
+
data?: any;
|
|
99
|
+
error?: string;
|
|
100
|
+
queryLog?: string;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* Reset performance schema statistics
|
|
104
|
+
*/
|
|
105
|
+
resetPerformanceStats(): Promise<{
|
|
106
|
+
status: string;
|
|
107
|
+
message?: string;
|
|
108
|
+
error?: string;
|
|
109
|
+
queryLog?: string;
|
|
110
|
+
}>;
|
|
111
|
+
}
|