@berthojoris/mcp-mysql-server 1.10.5 → 1.13.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/CHANGELOG.md +37 -0
- package/DOCUMENTATIONS.md +105 -32
- package/README.md +80 -9
- package/bin/mcp-mysql.js +107 -39
- package/dist/config/featureConfig.d.ts +41 -3
- package/dist/config/featureConfig.js +188 -13
- package/dist/index.d.ts +81 -1
- package/dist/index.js +54 -2
- package/dist/mcp-server.js +121 -40
- package/dist/optimization/explainAnalyzer.d.ts +21 -0
- package/dist/optimization/explainAnalyzer.js +147 -0
- package/dist/tools/aiTools.d.ts +22 -0
- package/dist/tools/aiTools.js +80 -0
- package/dist/tools/analysisTools.d.ts +35 -0
- package/dist/tools/analysisTools.js +327 -0
- package/dist/tools/databaseTools.d.ts +21 -0
- package/dist/tools/databaseTools.js +138 -0
- package/dist/tools/queryTools.d.ts +5 -0
- package/dist/tools/queryTools.js +27 -0
- package/manifest.json +22 -1
- package/package.json +89 -89
package/dist/mcp-server.js
CHANGED
|
@@ -10,6 +10,7 @@ const index_js_2 = require("./index.js");
|
|
|
10
10
|
// Layer 2 (Categories): MCP_CATEGORIES (optional, for fine-grained control)
|
|
11
11
|
const permissions = process.env.MCP_PERMISSIONS || process.env.MCP_CONFIG || "";
|
|
12
12
|
const categories = process.env.MCP_CATEGORIES || "";
|
|
13
|
+
const preset = process.env.MCP_PRESET || process.env.MCP_PERMISSION_PRESET || "";
|
|
13
14
|
// Declare the MySQL MCP instance (will be initialized in main())
|
|
14
15
|
let mysqlMCP;
|
|
15
16
|
// Define all available tools with their schemas
|
|
@@ -35,6 +36,79 @@ const TOOLS = [
|
|
|
35
36
|
},
|
|
36
37
|
},
|
|
37
38
|
},
|
|
39
|
+
{
|
|
40
|
+
name: "get_database_summary",
|
|
41
|
+
description: "Get a high-level summary of the database (tables, columns, row counts) optimized for AI context.",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
database: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "Optional: specific database name",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "get_schema_erd",
|
|
54
|
+
description: "Get a Mermaid.js ER diagram string representing the database schema and relationships.",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
database: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Optional: specific database name",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "get_schema_rag_context",
|
|
67
|
+
description: "Return a compact schema-aware context pack (tables, PK/FK, columns, row estimates) optimized for RAG prompts.",
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
database: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "Optional: specific database name",
|
|
74
|
+
},
|
|
75
|
+
max_tables: {
|
|
76
|
+
type: "number",
|
|
77
|
+
description: "Optional: maximum number of tables to include (default 50, max 200)",
|
|
78
|
+
},
|
|
79
|
+
max_columns: {
|
|
80
|
+
type: "number",
|
|
81
|
+
description: "Optional: maximum number of columns per table (default 12, max 200)",
|
|
82
|
+
},
|
|
83
|
+
include_relationships: {
|
|
84
|
+
type: "boolean",
|
|
85
|
+
description: "Whether to include FK relationships section (default: true)",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "get_column_statistics",
|
|
92
|
+
description: "Get detailed statistics for a specific column (min, max, avg, distinct counts, nulls).",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
table_name: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Name of the table",
|
|
99
|
+
},
|
|
100
|
+
column_name: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "Name of the column",
|
|
103
|
+
},
|
|
104
|
+
database: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Optional: specific database name",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
required: ["table_name", "column_name"],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
38
112
|
{
|
|
39
113
|
name: "read_table_schema",
|
|
40
114
|
description: "Reads the schema of a specified table, including columns, types, keys, and indexes.",
|
|
@@ -375,6 +449,10 @@ const TOOLS = [
|
|
|
375
449
|
type: "boolean",
|
|
376
450
|
description: "Whether to use query result caching (default: true)",
|
|
377
451
|
},
|
|
452
|
+
dry_run: {
|
|
453
|
+
type: "boolean",
|
|
454
|
+
description: "If true, returns query plan and estimated cost without executing (Safe Mode)",
|
|
455
|
+
},
|
|
378
456
|
},
|
|
379
457
|
required: ["query"],
|
|
380
458
|
},
|
|
@@ -398,6 +476,24 @@ const TOOLS = [
|
|
|
398
476
|
required: ["query"],
|
|
399
477
|
},
|
|
400
478
|
},
|
|
479
|
+
{
|
|
480
|
+
name: "repair_query",
|
|
481
|
+
description: "Analyzes a SQL query (and optional error) to suggest repairs or optimizations using EXPLAIN and heuristics.",
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: "object",
|
|
484
|
+
properties: {
|
|
485
|
+
query: {
|
|
486
|
+
type: "string",
|
|
487
|
+
description: "The SQL query to analyze or repair",
|
|
488
|
+
},
|
|
489
|
+
error_message: {
|
|
490
|
+
type: "string",
|
|
491
|
+
description: "Optional error message received when executing the query",
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
required: ["query"],
|
|
495
|
+
},
|
|
496
|
+
},
|
|
401
497
|
{
|
|
402
498
|
name: "create_table",
|
|
403
499
|
description: 'Creates a new table with the specified columns and indexes. Requires "ddl" permission.',
|
|
@@ -2563,7 +2659,7 @@ const TOOLS = [
|
|
|
2563
2659
|
// Performance Monitoring Tools
|
|
2564
2660
|
{
|
|
2565
2661
|
name: "get_performance_metrics",
|
|
2566
|
-
description: "Get comprehensive performance metrics including query performance, connection stats, buffer pool metrics, and InnoDB statistics.",
|
|
2662
|
+
description: "Get comprehensive MySQL performance metrics including query performance, connection stats, buffer pool metrics, and InnoDB statistics.",
|
|
2567
2663
|
inputSchema: {
|
|
2568
2664
|
type: "object",
|
|
2569
2665
|
properties: {},
|
|
@@ -2687,7 +2783,7 @@ const TOOLS = [
|
|
|
2687
2783
|
// Create the MCP server
|
|
2688
2784
|
const server = new index_js_1.Server({
|
|
2689
2785
|
name: "mysql-mcp-server",
|
|
2690
|
-
version: "1.
|
|
2786
|
+
version: "1.12.0",
|
|
2691
2787
|
}, {
|
|
2692
2788
|
capabilities: {
|
|
2693
2789
|
tools: {},
|
|
@@ -2721,38 +2817,20 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
2721
2817
|
case "list_tables":
|
|
2722
2818
|
result = await mysqlMCP.listTables((args || {}));
|
|
2723
2819
|
break;
|
|
2724
|
-
case "
|
|
2725
|
-
result = await mysqlMCP.
|
|
2726
|
-
break;
|
|
2727
|
-
case "create_record":
|
|
2728
|
-
result = await mysqlMCP.createRecord((args || {}));
|
|
2729
|
-
break;
|
|
2730
|
-
case "read_records":
|
|
2731
|
-
result = await mysqlMCP.readRecords((args || {}));
|
|
2732
|
-
break;
|
|
2733
|
-
case "update_record":
|
|
2734
|
-
result = await mysqlMCP.updateRecord((args || {}));
|
|
2735
|
-
break;
|
|
2736
|
-
case "delete_record":
|
|
2737
|
-
result = await mysqlMCP.deleteRecord((args || {}));
|
|
2820
|
+
case "get_database_summary":
|
|
2821
|
+
result = await mysqlMCP.getDatabaseSummary((args || {}));
|
|
2738
2822
|
break;
|
|
2739
|
-
case "
|
|
2740
|
-
result = await mysqlMCP.
|
|
2823
|
+
case "get_schema_erd":
|
|
2824
|
+
result = await mysqlMCP.getSchemaERD((args || {}));
|
|
2741
2825
|
break;
|
|
2742
|
-
case "
|
|
2743
|
-
result = await mysqlMCP.
|
|
2826
|
+
case "get_schema_rag_context":
|
|
2827
|
+
result = await mysqlMCP.getSchemaRagContext((args || {}));
|
|
2744
2828
|
break;
|
|
2745
|
-
case "
|
|
2746
|
-
result = await mysqlMCP.
|
|
2829
|
+
case "get_column_statistics":
|
|
2830
|
+
result = await mysqlMCP.getColumnStatistics((args || {}));
|
|
2747
2831
|
break;
|
|
2748
|
-
case "
|
|
2749
|
-
result = await mysqlMCP.
|
|
2750
|
-
break;
|
|
2751
|
-
case "execute_sql":
|
|
2752
|
-
result = await mysqlMCP.executeSql((args || {}));
|
|
2753
|
-
break;
|
|
2754
|
-
case "create_table":
|
|
2755
|
-
result = await mysqlMCP.createTable(args || {});
|
|
2832
|
+
case "read_table_schema":
|
|
2833
|
+
result = await mysqlMCP.readTableSchema((args || {}));
|
|
2756
2834
|
break;
|
|
2757
2835
|
case "alter_table":
|
|
2758
2836
|
result = await mysqlMCP.alterTable(args || {});
|
|
@@ -3090,6 +3168,9 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
3090
3168
|
case "reset_performance_stats":
|
|
3091
3169
|
result = await mysqlMCP.resetPerformanceStats();
|
|
3092
3170
|
break;
|
|
3171
|
+
case "repair_query":
|
|
3172
|
+
result = await mysqlMCP.repairQuery((args || {}));
|
|
3173
|
+
break;
|
|
3093
3174
|
default:
|
|
3094
3175
|
throw new Error(`Unknown tool: ${name}`);
|
|
3095
3176
|
}
|
|
@@ -3173,20 +3254,20 @@ async function main() {
|
|
|
3173
3254
|
await server.connect(transport);
|
|
3174
3255
|
// Initialize the MySQL MCP instance AFTER transport is connected
|
|
3175
3256
|
// This ensures the database connection pool is created when the server is ready
|
|
3176
|
-
mysqlMCP = new index_js_2.MySQLMCP(permissions, categories);
|
|
3257
|
+
mysqlMCP = new index_js_2.MySQLMCP(permissions, categories, preset);
|
|
3177
3258
|
// Log the effective filtering configuration to stderr
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
console.error(`
|
|
3181
|
-
console.error("Filtering mode: Dual-layer");
|
|
3259
|
+
const accessProfile = mysqlMCP.getAccessProfile();
|
|
3260
|
+
if (accessProfile.preset) {
|
|
3261
|
+
console.error(`Preset: ${accessProfile.preset.name} (${accessProfile.preset.description})`);
|
|
3182
3262
|
}
|
|
3183
|
-
else if (
|
|
3184
|
-
console.error(`
|
|
3185
|
-
console.error("Filtering mode: Single-layer");
|
|
3263
|
+
else if (preset) {
|
|
3264
|
+
console.error(`Preset requested but not recognized: ${preset}`);
|
|
3186
3265
|
}
|
|
3187
|
-
|
|
3188
|
-
|
|
3266
|
+
console.error(`Permissions (resolved): ${accessProfile.permissions}`);
|
|
3267
|
+
if (accessProfile.categories) {
|
|
3268
|
+
console.error(`Categories (resolved): ${accessProfile.categories}`);
|
|
3189
3269
|
}
|
|
3270
|
+
console.error(`Filtering mode: ${accessProfile.filteringMode}`);
|
|
3190
3271
|
// Log to stderr (not stdout, which is used for MCP protocol)
|
|
3191
3272
|
console.error("MySQL MCP Server running on stdio");
|
|
3192
3273
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ExplainAnalysis {
|
|
2
|
+
complexity: "LOW" | "MEDIUM" | "HIGH";
|
|
3
|
+
issues: string[];
|
|
4
|
+
suggestions: string[];
|
|
5
|
+
summary: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class ExplainAnalyzer {
|
|
8
|
+
private static instance;
|
|
9
|
+
private constructor();
|
|
10
|
+
static getInstance(): ExplainAnalyzer;
|
|
11
|
+
/**
|
|
12
|
+
* Analyze EXPLAIN output (assumed to be in JSON format or standard table format)
|
|
13
|
+
* Note: The server should prefer EXPLAIN FORMAT=JSON for better analysis,
|
|
14
|
+
* but we will handle the standard result set array as well.
|
|
15
|
+
*/
|
|
16
|
+
analyze(explainResult: any[]): ExplainAnalysis;
|
|
17
|
+
private analyzeJsonFormat;
|
|
18
|
+
private traverseJsonPlan;
|
|
19
|
+
private analyzeRow;
|
|
20
|
+
private generateSummary;
|
|
21
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExplainAnalyzer = void 0;
|
|
4
|
+
class ExplainAnalyzer {
|
|
5
|
+
constructor() { }
|
|
6
|
+
static getInstance() {
|
|
7
|
+
if (!ExplainAnalyzer.instance) {
|
|
8
|
+
ExplainAnalyzer.instance = new ExplainAnalyzer();
|
|
9
|
+
}
|
|
10
|
+
return ExplainAnalyzer.instance;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Analyze EXPLAIN output (assumed to be in JSON format or standard table format)
|
|
14
|
+
* Note: The server should prefer EXPLAIN FORMAT=JSON for better analysis,
|
|
15
|
+
* but we will handle the standard result set array as well.
|
|
16
|
+
*/
|
|
17
|
+
analyze(explainResult) {
|
|
18
|
+
const issues = [];
|
|
19
|
+
const suggestions = [];
|
|
20
|
+
let maxComplexity = 0;
|
|
21
|
+
// Check if result is empty
|
|
22
|
+
if (!explainResult || explainResult.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
complexity: "LOW",
|
|
25
|
+
issues: ["No EXPLAIN output returned"],
|
|
26
|
+
suggestions: [],
|
|
27
|
+
summary: "Query validation failed or produced no execution plan."
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Detect format (JSON string in first column or standard columns)
|
|
31
|
+
// If using EXPLAIN FORMAT=JSON, output is usually [{ EXPLAIN: '...json...' }]
|
|
32
|
+
const firstRow = explainResult[0];
|
|
33
|
+
if (firstRow && (firstRow['EXPLAIN'] || firstRow['JSON_EXPLAIN'])) {
|
|
34
|
+
return this.analyzeJsonFormat(firstRow['EXPLAIN'] || firstRow['JSON_EXPLAIN']);
|
|
35
|
+
}
|
|
36
|
+
// Analyze standard tabular format
|
|
37
|
+
for (const row of explainResult) {
|
|
38
|
+
this.analyzeRow(row, issues, suggestions);
|
|
39
|
+
// Heuristic for complexity
|
|
40
|
+
if (row.type === 'ALL')
|
|
41
|
+
maxComplexity = Math.max(maxComplexity, 3);
|
|
42
|
+
else if (row.type === 'index')
|
|
43
|
+
maxComplexity = Math.max(maxComplexity, 2);
|
|
44
|
+
else
|
|
45
|
+
maxComplexity = Math.max(maxComplexity, 1);
|
|
46
|
+
}
|
|
47
|
+
let complexity = "LOW";
|
|
48
|
+
if (maxComplexity >= 3)
|
|
49
|
+
complexity = "HIGH";
|
|
50
|
+
else if (maxComplexity === 2)
|
|
51
|
+
complexity = "MEDIUM";
|
|
52
|
+
return {
|
|
53
|
+
complexity,
|
|
54
|
+
issues: Array.from(new Set(issues)),
|
|
55
|
+
suggestions: Array.from(new Set(suggestions)),
|
|
56
|
+
summary: this.generateSummary(complexity, issues)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
analyzeJsonFormat(jsonString) {
|
|
60
|
+
try {
|
|
61
|
+
const data = JSON.parse(jsonString);
|
|
62
|
+
const queryBlock = data.query_block || data; // Root might be query_block directly in some versions
|
|
63
|
+
const issues = [];
|
|
64
|
+
const suggestions = [];
|
|
65
|
+
this.traverseJsonPlan(queryBlock, issues, suggestions);
|
|
66
|
+
const distinctIssues = Array.from(new Set(issues));
|
|
67
|
+
const complexity = distinctIssues.some(i => i.includes("Full Table Scan")) ? "HIGH" : "MEDIUM"; // Simplified
|
|
68
|
+
return {
|
|
69
|
+
complexity,
|
|
70
|
+
issues: distinctIssues,
|
|
71
|
+
suggestions: Array.from(new Set(suggestions)),
|
|
72
|
+
summary: this.generateSummary(complexity, distinctIssues)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
return {
|
|
77
|
+
complexity: "LOW",
|
|
78
|
+
issues: ["Failed to parse EXPLAIN JSON output"],
|
|
79
|
+
suggestions: [],
|
|
80
|
+
summary: "Could not analyze the execution plan details."
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
traverseJsonPlan(node, issues, suggestions) {
|
|
85
|
+
if (!node)
|
|
86
|
+
return;
|
|
87
|
+
// Check for full table scans
|
|
88
|
+
if (node.access_type === 'ALL') {
|
|
89
|
+
issues.push(`Full Table Scan on table '${node.table_name || 'unknown'}'`);
|
|
90
|
+
suggestions.push(`Consider adding an index on table '${node.table_name || 'unknown'}' for the columns used in WHERE clause.`);
|
|
91
|
+
}
|
|
92
|
+
// Check for filesort
|
|
93
|
+
if (node.filesort || (node.extra && node.extra.includes('Using filesort'))) {
|
|
94
|
+
issues.push("Using Filesort (sorting without index)");
|
|
95
|
+
suggestions.push("Consider adding an index that matches the ORDER BY clause.");
|
|
96
|
+
}
|
|
97
|
+
// Check for temporary tables
|
|
98
|
+
if (node.temporary_table || (node.extra && node.extra.includes('Using temporary'))) {
|
|
99
|
+
issues.push("Using Temporary Table");
|
|
100
|
+
suggestions.push("Optimize query to avoid temporary tables (e.g. check GROUP BY or ORDER BY columns).");
|
|
101
|
+
}
|
|
102
|
+
// Recursive traversal (simplified)
|
|
103
|
+
if (node.nested_loop) {
|
|
104
|
+
for (const child of node.nested_loop) {
|
|
105
|
+
this.traverseJsonPlan(child.table, issues, suggestions);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// TODO: Add more structural traversal if needed for specific JSON structures
|
|
109
|
+
}
|
|
110
|
+
analyzeRow(row, issues, suggestions) {
|
|
111
|
+
const table = row.table || 'unknown';
|
|
112
|
+
const type = row.type;
|
|
113
|
+
const extra = row.Extra || '';
|
|
114
|
+
const possible_keys = row.possible_keys;
|
|
115
|
+
const key = row.key;
|
|
116
|
+
if (type === 'ALL') {
|
|
117
|
+
issues.push(`Full Table Scan on table '${table}'`);
|
|
118
|
+
suggestions.push(`Add an index to '${table}' to avoid full scan.`);
|
|
119
|
+
}
|
|
120
|
+
if (type === 'index') {
|
|
121
|
+
issues.push(`Full Index Scan on table '${table}'`);
|
|
122
|
+
suggestions.push(`Query scans entire index tree for '${table}'. Consider specific range or key.`);
|
|
123
|
+
}
|
|
124
|
+
if (!key && possible_keys && type !== 'ALL') {
|
|
125
|
+
// Indexes exist but not used? Rare case if not ALL.
|
|
126
|
+
}
|
|
127
|
+
if (extra.includes('Using filesort')) {
|
|
128
|
+
issues.push(`Using Filesort on table '${table}'`);
|
|
129
|
+
suggestions.push(`Add index for ORDER BY clause on '${table}'.`);
|
|
130
|
+
}
|
|
131
|
+
if (extra.includes('Using temporary')) {
|
|
132
|
+
issues.push(`Using Temporary Table for '${table}'`);
|
|
133
|
+
suggestions.push(`Check GROUP BY / ORDER BY columns on '${table}'.`);
|
|
134
|
+
}
|
|
135
|
+
if (extra.includes('Range checked for each record')) {
|
|
136
|
+
issues.push(`Inefficient join buffer usage on '${table}'`);
|
|
137
|
+
suggestions.push(`Check join conditions and indexes on '${table}'.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
generateSummary(complexity, issues) {
|
|
141
|
+
if (issues.length === 0) {
|
|
142
|
+
return "Query plan looks good. No obvious performance issues detected.";
|
|
143
|
+
}
|
|
144
|
+
return `Query has ${complexity} complexity with ${issues.length} potential performance issue(s): ${issues.join("; ")}`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.ExplainAnalyzer = ExplainAnalyzer;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SecurityLayer } from "../security/securityLayer";
|
|
2
|
+
export declare class AiTools {
|
|
3
|
+
private db;
|
|
4
|
+
private security;
|
|
5
|
+
private analyzer;
|
|
6
|
+
private optimizer;
|
|
7
|
+
constructor(security: SecurityLayer);
|
|
8
|
+
/**
|
|
9
|
+
* guided_query_fixer
|
|
10
|
+
* Analyzes a query (and optional error) to suggest repairs or optimizations using EXPLAIN.
|
|
11
|
+
*/
|
|
12
|
+
repairQuery(params: {
|
|
13
|
+
query: string;
|
|
14
|
+
error_message?: string;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
status: string;
|
|
17
|
+
analysis?: any;
|
|
18
|
+
fixed_query?: string;
|
|
19
|
+
suggestions?: string[];
|
|
20
|
+
error?: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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.AiTools = void 0;
|
|
7
|
+
const connection_1 = __importDefault(require("../db/connection"));
|
|
8
|
+
const explainAnalyzer_1 = require("../optimization/explainAnalyzer");
|
|
9
|
+
const queryOptimizer_1 = require("../optimization/queryOptimizer");
|
|
10
|
+
class AiTools {
|
|
11
|
+
constructor(security) {
|
|
12
|
+
this.db = connection_1.default.getInstance();
|
|
13
|
+
this.security = security;
|
|
14
|
+
this.analyzer = explainAnalyzer_1.ExplainAnalyzer.getInstance();
|
|
15
|
+
this.optimizer = queryOptimizer_1.QueryOptimizer.getInstance();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* guided_query_fixer
|
|
19
|
+
* Analyzes a query (and optional error) to suggest repairs or optimizations using EXPLAIN.
|
|
20
|
+
*/
|
|
21
|
+
async repairQuery(params) {
|
|
22
|
+
const { query, error_message } = params;
|
|
23
|
+
// 1. If there is a syntax error provided, we can't run EXPLAIN.
|
|
24
|
+
// We try to provided simple heuristics or just return the analysis of the error.
|
|
25
|
+
if (error_message) {
|
|
26
|
+
return {
|
|
27
|
+
status: "success",
|
|
28
|
+
suggestions: [
|
|
29
|
+
"Check SQL syntax matching your MySQL version.",
|
|
30
|
+
"Verify table and column names using 'list_tables' or 'read_table_schema'.",
|
|
31
|
+
"Ensure string literals are quoted correctly."
|
|
32
|
+
],
|
|
33
|
+
fixed_query: query, // We can't auto-fix syntax errors reliably without an LLM
|
|
34
|
+
analysis: {
|
|
35
|
+
issue: "Syntax/Execution Error",
|
|
36
|
+
details: error_message
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// 2. Validate query generally (security check)
|
|
41
|
+
// We assume this tool is used by an agent who might have 'read' permissions at least.
|
|
42
|
+
// If the query is unsafe (e.g. injection), we return that.
|
|
43
|
+
const validation = this.security.validateQuery(query, true); // validate with execute permission simulation to check structure
|
|
44
|
+
if (!validation.valid) {
|
|
45
|
+
return {
|
|
46
|
+
status: "error",
|
|
47
|
+
error: `Query rejected by security layer: ${validation.error}`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// 3. Run EXPLAIN
|
|
51
|
+
try {
|
|
52
|
+
const explainQuery = `EXPLAIN FORMAT=JSON ${query}`;
|
|
53
|
+
// Note: We use the raw connection or executeSql equivalent.
|
|
54
|
+
// But EXPLAIN is safe-ish if the inner query is safe.
|
|
55
|
+
// validation passed, so we try EXPLAIN.
|
|
56
|
+
const explainResult = await this.db.query(explainQuery);
|
|
57
|
+
const analysis = this.analyzer.analyze(explainResult);
|
|
58
|
+
// 4. Try to apply simple fixes based on analysis (e.g. Missing Limit)
|
|
59
|
+
let fixedQuery = query;
|
|
60
|
+
if (analysis.complexity === "HIGH" && !query.toLowerCase().includes("limit")) {
|
|
61
|
+
// Suggest adding LIMIT if not present and complexity is high
|
|
62
|
+
analysis.suggestions.push("Consider adding 'LIMIT 100' to prevent massive data transfer.");
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
status: "success",
|
|
66
|
+
analysis: analysis,
|
|
67
|
+
suggestions: analysis.suggestions,
|
|
68
|
+
fixed_query: fixedQuery
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return {
|
|
73
|
+
status: "error",
|
|
74
|
+
error: `Failed to analyze query: ${e.message}`,
|
|
75
|
+
suggestions: ["Verify the query is valid SQL before analyzing."]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.AiTools = AiTools;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { SecurityLayer } from "../security/securityLayer";
|
|
2
|
+
export declare class AnalysisTools {
|
|
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
|
+
* Get statistics for a specific column
|
|
12
|
+
*/
|
|
13
|
+
getColumnStatistics(params: {
|
|
14
|
+
table_name: string;
|
|
15
|
+
column_name: string;
|
|
16
|
+
database?: string;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
status: string;
|
|
19
|
+
data?: any;
|
|
20
|
+
error?: string;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Build a compact, schema-aware context pack for RAG (tables, PK/FK, columns, row estimates)
|
|
24
|
+
*/
|
|
25
|
+
getSchemaRagContext(params?: {
|
|
26
|
+
database?: string;
|
|
27
|
+
max_tables?: number;
|
|
28
|
+
max_columns?: number;
|
|
29
|
+
include_relationships?: boolean;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
status: string;
|
|
32
|
+
data?: any;
|
|
33
|
+
error?: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|