@berthojoris/mcp-mysql-server 1.23.0 → 1.24.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 +13 -0
- package/DOCUMENTATIONS.md +3 -2
- package/README.md +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +7 -0
- package/dist/mcp-server.js +1 -1
- package/dist/tools/analysisTools.js +40 -40
- package/dist/tools/indexRecommendationTools.js +39 -39
- package/dist/tools/performanceTools.js +152 -152
- package/dist/tools/securityAuditTools.js +13 -13
- package/dist/tools/utilityTools.d.ts +8 -0
- package/dist/tools/utilityTools.js +39 -0
- package/manifest.json +37 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -27,6 +27,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
27
27
|
### Changed
|
|
28
28
|
- Version increment for implemented features and improvements
|
|
29
29
|
|
|
30
|
+
## [1.24.0] - 2025-12-19
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- New tool `list_all_tools` for listing all available tools in the MySQL MCP server with their descriptions and schemas
|
|
34
|
+
- Enhanced meta-tool capabilities for AI agents to discover available functionality
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- Updated tool count from 144 to 145 in README.md
|
|
38
|
+
- Updated tool count from 157 to 158 in DOCUMENTATIONS.md
|
|
39
|
+
- Added `list_all_tools` to the Utilities section in documentation
|
|
40
|
+
- Updated Last Updated timestamp in README.md
|
|
41
|
+
- Version increment for new tool implementation
|
|
42
|
+
|
|
30
43
|
## [Unreleased]
|
|
31
44
|
|
|
32
45
|
### Security
|
package/DOCUMENTATIONS.md
CHANGED
|
@@ -9,7 +9,7 @@ This file contains detailed documentation for all features of the MySQL MCP Serv
|
|
|
9
9
|
## Table of Contents
|
|
10
10
|
|
|
11
11
|
1. [Setup & Configuration (Extended)](#setup--configuration-extended) - Permissions + Categories
|
|
12
|
-
2. [🔧 Complete Tools Reference](#🔧-complete-tools-reference) - All
|
|
12
|
+
2. [🔧 Complete Tools Reference](#🔧-complete-tools-reference) - All 158 tools organized by category
|
|
13
13
|
3. [DDL Operations](#🏗️-ddl-operations)
|
|
14
14
|
4. [Data Export Tools](#📤-data-export-tools)
|
|
15
15
|
5. [Data Import Tools](#📥-data-import-tools)
|
|
@@ -201,7 +201,7 @@ Add 'bulk_operations' to the categories argument.
|
|
|
201
201
|
|
|
202
202
|
## 🔧 Complete Tools Reference
|
|
203
203
|
|
|
204
|
-
This section provides a comprehensive reference of all
|
|
204
|
+
This section provides a comprehensive reference of all 158 available tools organized by category.
|
|
205
205
|
|
|
206
206
|
### Database Discovery
|
|
207
207
|
|
|
@@ -255,6 +255,7 @@ This section provides a comprehensive reference of all 157 available tools organ
|
|
|
255
255
|
| `read_changelog` | Read the changelog to see new features/changes |
|
|
256
256
|
| `export_table_to_csv` | Export table data to CSV format |
|
|
257
257
|
| `export_query_to_csv` | Export query results to CSV format |
|
|
258
|
+
| `list_all_tools` | List all available tools in this MySQL MCP server |
|
|
258
259
|
|
|
259
260
|
### Transaction Management
|
|
260
261
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**A production-ready Model Context Protocol (MCP) server for MySQL database integration with AI agents**
|
|
6
6
|
|
|
7
|
-
**Last Updated:** 2025-12-
|
|
7
|
+
**Last Updated:** 2025-12-19 03:59:00
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@berthojoris/mysql-mcp)
|
|
10
10
|
[](https://www.npmjs.com/package/@berthojoris/mysql-mcp)
|
|
@@ -300,7 +300,7 @@ Common configuration examples are documented in **[DOCUMENTATIONS.md → Categor
|
|
|
300
300
|
|
|
301
301
|
## Available Tools
|
|
302
302
|
|
|
303
|
-
The server exposes **
|
|
303
|
+
The server exposes **145 tools** organized into categories (CRUD, schema, backups, migrations, perf/monitoring, and AI enhancement).
|
|
304
304
|
|
|
305
305
|
- Complete list of tools: **[DOCUMENTATIONS.md → Complete Tools Reference](DOCUMENTATIONS.md#🔧-complete-tools-reference)**
|
|
306
306
|
- AI enhancement tools overview: **[DOCUMENTATIONS.md → AI Enhancement Tools](DOCUMENTATIONS.md#🤖-ai-enhancement-tools)**
|
package/dist/index.d.ts
CHANGED
|
@@ -216,6 +216,11 @@ export declare class MySQLMCP {
|
|
|
216
216
|
data?: any;
|
|
217
217
|
error?: string;
|
|
218
218
|
}>;
|
|
219
|
+
listAllTools(): Promise<{
|
|
220
|
+
status: string;
|
|
221
|
+
data?: any;
|
|
222
|
+
error?: string;
|
|
223
|
+
}>;
|
|
219
224
|
beginTransaction(params?: {
|
|
220
225
|
transactionId?: string;
|
|
221
226
|
}): Promise<import("./tools/transactionTools").TransactionResult | {
|
package/dist/index.js
CHANGED
|
@@ -270,6 +270,13 @@ class MySQLMCP {
|
|
|
270
270
|
}
|
|
271
271
|
return await this.utilityTools.readChangelog(params);
|
|
272
272
|
}
|
|
273
|
+
async listAllTools() {
|
|
274
|
+
const check = this.checkToolEnabled("listAllTools");
|
|
275
|
+
if (!check.enabled) {
|
|
276
|
+
return { status: "error", error: check.error };
|
|
277
|
+
}
|
|
278
|
+
return await this.utilityTools.listAllTools();
|
|
279
|
+
}
|
|
273
280
|
// Transaction Tools
|
|
274
281
|
async beginTransaction(params) {
|
|
275
282
|
const check = this.checkToolEnabled("beginTransaction");
|
package/dist/mcp-server.js
CHANGED
|
@@ -699,7 +699,7 @@ const TOOLS = [
|
|
|
699
699
|
},
|
|
700
700
|
{
|
|
701
701
|
name: "get_all_tables_relationships",
|
|
702
|
-
description: "Gets foreign key relationships
|
|
702
|
+
description: "Gets all table foreign key relationships in one call with memory-efficient relationship mapping.",
|
|
703
703
|
inputSchema: {
|
|
704
704
|
type: "object",
|
|
705
705
|
properties: {
|
|
@@ -60,10 +60,10 @@ class AnalysisTools {
|
|
|
60
60
|
return { status: "error", error: "Invalid column name" };
|
|
61
61
|
}
|
|
62
62
|
// Check if column exists and get its type
|
|
63
|
-
const colCheckQuery = `
|
|
64
|
-
SELECT DATA_TYPE
|
|
65
|
-
FROM INFORMATION_SCHEMA.COLUMNS
|
|
66
|
-
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?
|
|
63
|
+
const colCheckQuery = `
|
|
64
|
+
SELECT DATA_TYPE
|
|
65
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
66
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?
|
|
67
67
|
`;
|
|
68
68
|
const colCheck = await this.db.query(colCheckQuery, [
|
|
69
69
|
database,
|
|
@@ -95,34 +95,34 @@ class AnalysisTools {
|
|
|
95
95
|
"year",
|
|
96
96
|
].includes(dataType);
|
|
97
97
|
// Build statistics query
|
|
98
|
-
let query = `
|
|
99
|
-
SELECT
|
|
100
|
-
COUNT(*) as total_rows,
|
|
101
|
-
COUNT(\`${column_name}\`) as non_null_count,
|
|
102
|
-
COUNT(DISTINCT \`${column_name}\`) as distinct_count,
|
|
103
|
-
SUM(CASE WHEN \`${column_name}\` IS NULL THEN 1 ELSE 0 END) as null_count
|
|
98
|
+
let query = `
|
|
99
|
+
SELECT
|
|
100
|
+
COUNT(*) as total_rows,
|
|
101
|
+
COUNT(\`${column_name}\`) as non_null_count,
|
|
102
|
+
COUNT(DISTINCT \`${column_name}\`) as distinct_count,
|
|
103
|
+
SUM(CASE WHEN \`${column_name}\` IS NULL THEN 1 ELSE 0 END) as null_count
|
|
104
104
|
`;
|
|
105
105
|
if (isNumeric || isDate) {
|
|
106
|
-
query += `,
|
|
107
|
-
MIN(\`${column_name}\`) as min_value,
|
|
108
|
-
MAX(\`${column_name}\`) as max_value
|
|
106
|
+
query += `,
|
|
107
|
+
MIN(\`${column_name}\`) as min_value,
|
|
108
|
+
MAX(\`${column_name}\`) as max_value
|
|
109
109
|
`;
|
|
110
110
|
}
|
|
111
111
|
if (isNumeric) {
|
|
112
|
-
query += `,
|
|
113
|
-
AVG(\`${column_name}\`) as avg_value
|
|
112
|
+
query += `,
|
|
113
|
+
AVG(\`${column_name}\`) as avg_value
|
|
114
114
|
`;
|
|
115
115
|
}
|
|
116
116
|
query += ` FROM \`${database}\`.\`${table_name}\``;
|
|
117
117
|
const statsResult = await this.db.query(query);
|
|
118
118
|
const stats = statsResult[0];
|
|
119
119
|
// Get top frequent values
|
|
120
|
-
const topValuesQuery = `
|
|
121
|
-
SELECT \`${column_name}\` as value, COUNT(*) as count
|
|
122
|
-
FROM \`${database}\`.\`${table_name}\`
|
|
123
|
-
GROUP BY \`${column_name}\`
|
|
124
|
-
ORDER BY count DESC
|
|
125
|
-
LIMIT 5
|
|
120
|
+
const topValuesQuery = `
|
|
121
|
+
SELECT \`${column_name}\` as value, COUNT(*) as count
|
|
122
|
+
FROM \`${database}\`.\`${table_name}\`
|
|
123
|
+
GROUP BY \`${column_name}\`
|
|
124
|
+
ORDER BY count DESC
|
|
125
|
+
LIMIT 5
|
|
126
126
|
`;
|
|
127
127
|
const topValues = await this.db.query(topValuesQuery);
|
|
128
128
|
return {
|
|
@@ -171,12 +171,12 @@ class AnalysisTools {
|
|
|
171
171
|
const totalTablesResult = await this.db.query(`SELECT COUNT(*) as total FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?`, [database]);
|
|
172
172
|
const totalTables = totalTablesResult[0]?.total ?? 0;
|
|
173
173
|
// Fetch tables limited for context pack
|
|
174
|
-
const tables = await this.db.query(`
|
|
175
|
-
SELECT TABLE_NAME, TABLE_ROWS
|
|
176
|
-
FROM INFORMATION_SCHEMA.TABLES
|
|
177
|
-
WHERE TABLE_SCHEMA = ?
|
|
178
|
-
ORDER BY TABLE_NAME
|
|
179
|
-
LIMIT ?
|
|
174
|
+
const tables = await this.db.query(`
|
|
175
|
+
SELECT TABLE_NAME, TABLE_ROWS
|
|
176
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
177
|
+
WHERE TABLE_SCHEMA = ?
|
|
178
|
+
ORDER BY TABLE_NAME
|
|
179
|
+
LIMIT ?
|
|
180
180
|
`, [database, maxTables]);
|
|
181
181
|
if (!tables.length) {
|
|
182
182
|
return {
|
|
@@ -193,23 +193,23 @@ class AnalysisTools {
|
|
|
193
193
|
const tableNames = tables.map((t) => t.TABLE_NAME);
|
|
194
194
|
const placeholders = tableNames.map(() => "?").join(",");
|
|
195
195
|
const columnParams = [database, ...tableNames];
|
|
196
|
-
const columns = await this.db.query(`
|
|
197
|
-
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_KEY, IS_NULLABLE
|
|
198
|
-
FROM INFORMATION_SCHEMA.COLUMNS
|
|
199
|
-
WHERE TABLE_SCHEMA = ?
|
|
200
|
-
AND TABLE_NAME IN (${placeholders})
|
|
201
|
-
ORDER BY TABLE_NAME, ORDINAL_POSITION
|
|
196
|
+
const columns = await this.db.query(`
|
|
197
|
+
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_KEY, IS_NULLABLE
|
|
198
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
199
|
+
WHERE TABLE_SCHEMA = ?
|
|
200
|
+
AND TABLE_NAME IN (${placeholders})
|
|
201
|
+
ORDER BY TABLE_NAME, ORDINAL_POSITION
|
|
202
202
|
`, columnParams);
|
|
203
203
|
let foreignKeys = [];
|
|
204
204
|
if (includeRelationships) {
|
|
205
205
|
const fkParams = [database, ...tableNames, ...tableNames];
|
|
206
|
-
foreignKeys = await this.db.query(`
|
|
207
|
-
SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
208
|
-
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
209
|
-
WHERE TABLE_SCHEMA = ?
|
|
210
|
-
AND TABLE_NAME IN (${placeholders})
|
|
211
|
-
AND REFERENCED_TABLE_NAME IN (${placeholders})
|
|
212
|
-
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
206
|
+
foreignKeys = await this.db.query(`
|
|
207
|
+
SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
208
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
209
|
+
WHERE TABLE_SCHEMA = ?
|
|
210
|
+
AND TABLE_NAME IN (${placeholders})
|
|
211
|
+
AND REFERENCED_TABLE_NAME IN (${placeholders})
|
|
212
|
+
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
213
213
|
`, fkParams);
|
|
214
214
|
}
|
|
215
215
|
const fkLookup = new Map();
|
|
@@ -250,11 +250,11 @@ class IndexRecommendationTools {
|
|
|
250
250
|
return false;
|
|
251
251
|
}
|
|
252
252
|
async getExistingIndexMap(database) {
|
|
253
|
-
const rows = await this.db.query(`
|
|
254
|
-
SELECT TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME
|
|
255
|
-
FROM INFORMATION_SCHEMA.STATISTICS
|
|
256
|
-
WHERE TABLE_SCHEMA = ?
|
|
257
|
-
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX
|
|
253
|
+
const rows = await this.db.query(`
|
|
254
|
+
SELECT TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME
|
|
255
|
+
FROM INFORMATION_SCHEMA.STATISTICS
|
|
256
|
+
WHERE TABLE_SCHEMA = ?
|
|
257
|
+
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX
|
|
258
258
|
`, [database]);
|
|
259
259
|
const byTable = new Map();
|
|
260
260
|
for (const r of rows) {
|
|
@@ -281,21 +281,21 @@ class IndexRecommendationTools {
|
|
|
281
281
|
}
|
|
282
282
|
async getTopSelectDigests(limit) {
|
|
283
283
|
// Note: performance_schema access may require extra privileges.
|
|
284
|
-
const query = `
|
|
285
|
-
SELECT
|
|
286
|
-
DIGEST_TEXT as query_pattern,
|
|
287
|
-
COUNT_STAR as execution_count,
|
|
288
|
-
ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
|
|
289
|
-
ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
|
|
290
|
-
SUM_ROWS_EXAMINED as rows_examined,
|
|
291
|
-
SUM_ROWS_SENT as rows_sent,
|
|
292
|
-
FIRST_SEEN as first_seen,
|
|
293
|
-
LAST_SEEN as last_seen
|
|
294
|
-
FROM performance_schema.events_statements_summary_by_digest
|
|
295
|
-
WHERE DIGEST_TEXT IS NOT NULL
|
|
296
|
-
AND DIGEST_TEXT LIKE 'SELECT %'
|
|
297
|
-
ORDER BY SUM_TIMER_WAIT DESC
|
|
298
|
-
LIMIT ${limit}
|
|
284
|
+
const query = `
|
|
285
|
+
SELECT
|
|
286
|
+
DIGEST_TEXT as query_pattern,
|
|
287
|
+
COUNT_STAR as execution_count,
|
|
288
|
+
ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
|
|
289
|
+
ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
|
|
290
|
+
SUM_ROWS_EXAMINED as rows_examined,
|
|
291
|
+
SUM_ROWS_SENT as rows_sent,
|
|
292
|
+
FIRST_SEEN as first_seen,
|
|
293
|
+
LAST_SEEN as last_seen
|
|
294
|
+
FROM performance_schema.events_statements_summary_by_digest
|
|
295
|
+
WHERE DIGEST_TEXT IS NOT NULL
|
|
296
|
+
AND DIGEST_TEXT LIKE 'SELECT %'
|
|
297
|
+
ORDER BY SUM_TIMER_WAIT DESC
|
|
298
|
+
LIMIT ${limit}
|
|
299
299
|
`;
|
|
300
300
|
const rows = await this.db.query(query);
|
|
301
301
|
return rows.map((r) => ({
|
|
@@ -310,25 +310,25 @@ class IndexRecommendationTools {
|
|
|
310
310
|
}));
|
|
311
311
|
}
|
|
312
312
|
async getUnusedIndexes() {
|
|
313
|
-
const query = `
|
|
314
|
-
SELECT
|
|
315
|
-
t.TABLE_SCHEMA as table_schema,
|
|
316
|
-
t.TABLE_NAME as table_name,
|
|
317
|
-
s.INDEX_NAME as index_name
|
|
318
|
-
FROM information_schema.STATISTICS s
|
|
319
|
-
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage p
|
|
320
|
-
ON s.TABLE_SCHEMA = p.OBJECT_SCHEMA
|
|
321
|
-
AND s.TABLE_NAME = p.OBJECT_NAME
|
|
322
|
-
AND s.INDEX_NAME = p.INDEX_NAME
|
|
323
|
-
JOIN information_schema.TABLES t
|
|
324
|
-
ON s.TABLE_SCHEMA = t.TABLE_SCHEMA
|
|
325
|
-
AND s.TABLE_NAME = t.TABLE_NAME
|
|
326
|
-
WHERE s.TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
|
|
327
|
-
AND s.INDEX_NAME != 'PRIMARY'
|
|
328
|
-
AND (p.INDEX_NAME IS NULL OR p.COUNT_STAR = 0)
|
|
329
|
-
AND t.TABLE_TYPE = 'BASE TABLE'
|
|
330
|
-
GROUP BY t.TABLE_SCHEMA, t.TABLE_NAME, s.INDEX_NAME
|
|
331
|
-
ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME, s.INDEX_NAME
|
|
313
|
+
const query = `
|
|
314
|
+
SELECT
|
|
315
|
+
t.TABLE_SCHEMA as table_schema,
|
|
316
|
+
t.TABLE_NAME as table_name,
|
|
317
|
+
s.INDEX_NAME as index_name
|
|
318
|
+
FROM information_schema.STATISTICS s
|
|
319
|
+
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage p
|
|
320
|
+
ON s.TABLE_SCHEMA = p.OBJECT_SCHEMA
|
|
321
|
+
AND s.TABLE_NAME = p.OBJECT_NAME
|
|
322
|
+
AND s.INDEX_NAME = p.INDEX_NAME
|
|
323
|
+
JOIN information_schema.TABLES t
|
|
324
|
+
ON s.TABLE_SCHEMA = t.TABLE_SCHEMA
|
|
325
|
+
AND s.TABLE_NAME = t.TABLE_NAME
|
|
326
|
+
WHERE s.TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
|
|
327
|
+
AND s.INDEX_NAME != 'PRIMARY'
|
|
328
|
+
AND (p.INDEX_NAME IS NULL OR p.COUNT_STAR = 0)
|
|
329
|
+
AND t.TABLE_TYPE = 'BASE TABLE'
|
|
330
|
+
GROUP BY t.TABLE_SCHEMA, t.TABLE_NAME, s.INDEX_NAME
|
|
331
|
+
ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME, s.INDEX_NAME
|
|
332
332
|
`;
|
|
333
333
|
const rows = await this.db.query(query);
|
|
334
334
|
return rows.map((r) => ({
|
|
@@ -17,28 +17,28 @@ class PerformanceTools {
|
|
|
17
17
|
try {
|
|
18
18
|
const metrics = {};
|
|
19
19
|
// Get query performance metrics
|
|
20
|
-
const perfQuery = `
|
|
21
|
-
SELECT
|
|
22
|
-
SUM(TIMER_WAIT) / 1000000000000 as total_execution_time_sec,
|
|
23
|
-
SUM(LOCK_TIME) / 1000000000000 as total_lock_time_sec,
|
|
24
|
-
SUM(ROWS_EXAMINED) as total_rows_examined,
|
|
25
|
-
SUM(ROWS_SENT) as total_rows_sent,
|
|
26
|
-
SUM(ROWS_AFFECTED) as total_rows_affected,
|
|
27
|
-
SUM(SELECT_FULL_JOIN) as full_table_scans,
|
|
28
|
-
SUM(NO_INDEX_USED) as queries_without_indexes,
|
|
29
|
-
SUM(NO_GOOD_INDEX_USED) as queries_with_bad_indexes,
|
|
30
|
-
COUNT(*) as total_queries
|
|
31
|
-
FROM performance_schema.events_statements_summary_global_by_event_name
|
|
32
|
-
WHERE EVENT_NAME LIKE 'statement/sql/%'
|
|
20
|
+
const perfQuery = `
|
|
21
|
+
SELECT
|
|
22
|
+
SUM(TIMER_WAIT) / 1000000000000 as total_execution_time_sec,
|
|
23
|
+
SUM(LOCK_TIME) / 1000000000000 as total_lock_time_sec,
|
|
24
|
+
SUM(ROWS_EXAMINED) as total_rows_examined,
|
|
25
|
+
SUM(ROWS_SENT) as total_rows_sent,
|
|
26
|
+
SUM(ROWS_AFFECTED) as total_rows_affected,
|
|
27
|
+
SUM(SELECT_FULL_JOIN) as full_table_scans,
|
|
28
|
+
SUM(NO_INDEX_USED) as queries_without_indexes,
|
|
29
|
+
SUM(NO_GOOD_INDEX_USED) as queries_with_bad_indexes,
|
|
30
|
+
COUNT(*) as total_queries
|
|
31
|
+
FROM performance_schema.events_statements_summary_global_by_event_name
|
|
32
|
+
WHERE EVENT_NAME LIKE 'statement/sql/%'
|
|
33
33
|
`;
|
|
34
34
|
const perfResult = await this.db.query(perfQuery);
|
|
35
35
|
metrics.query_performance = perfResult[0] || {};
|
|
36
36
|
// Get connection metrics
|
|
37
|
-
const connQuery = `
|
|
38
|
-
SHOW GLOBAL STATUS WHERE Variable_name IN (
|
|
39
|
-
'Threads_connected', 'Threads_running', 'Threads_created',
|
|
40
|
-
'Connections', 'Max_used_connections', 'Aborted_connects', 'Aborted_clients'
|
|
41
|
-
)
|
|
37
|
+
const connQuery = `
|
|
38
|
+
SHOW GLOBAL STATUS WHERE Variable_name IN (
|
|
39
|
+
'Threads_connected', 'Threads_running', 'Threads_created',
|
|
40
|
+
'Connections', 'Max_used_connections', 'Aborted_connects', 'Aborted_clients'
|
|
41
|
+
)
|
|
42
42
|
`;
|
|
43
43
|
const connResult = await this.db.query(connQuery);
|
|
44
44
|
metrics.connections = {};
|
|
@@ -46,11 +46,11 @@ class PerformanceTools {
|
|
|
46
46
|
metrics.connections[row.Variable_name.toLowerCase()] = row.Value;
|
|
47
47
|
}
|
|
48
48
|
// Get table cache metrics
|
|
49
|
-
const cacheQuery = `
|
|
50
|
-
SHOW GLOBAL STATUS WHERE Variable_name IN (
|
|
51
|
-
'Open_tables', 'Opened_tables', 'Table_open_cache_hits',
|
|
52
|
-
'Table_open_cache_misses', 'Table_open_cache_overflows'
|
|
53
|
-
)
|
|
49
|
+
const cacheQuery = `
|
|
50
|
+
SHOW GLOBAL STATUS WHERE Variable_name IN (
|
|
51
|
+
'Open_tables', 'Opened_tables', 'Table_open_cache_hits',
|
|
52
|
+
'Table_open_cache_misses', 'Table_open_cache_overflows'
|
|
53
|
+
)
|
|
54
54
|
`;
|
|
55
55
|
const cacheResult = await this.db.query(cacheQuery);
|
|
56
56
|
metrics.table_cache = {};
|
|
@@ -58,15 +58,15 @@ class PerformanceTools {
|
|
|
58
58
|
metrics.table_cache[row.Variable_name.toLowerCase()] = row.Value;
|
|
59
59
|
}
|
|
60
60
|
// Get InnoDB metrics
|
|
61
|
-
const innodbQuery = `
|
|
62
|
-
SHOW GLOBAL STATUS WHERE Variable_name LIKE 'Innodb_%'
|
|
63
|
-
AND Variable_name IN (
|
|
64
|
-
'Innodb_buffer_pool_reads', 'Innodb_buffer_pool_read_requests',
|
|
65
|
-
'Innodb_buffer_pool_pages_total', 'Innodb_buffer_pool_pages_free',
|
|
66
|
-
'Innodb_buffer_pool_pages_data', 'Innodb_buffer_pool_pages_dirty',
|
|
67
|
-
'Innodb_row_lock_waits', 'Innodb_row_lock_time', 'Innodb_rows_read',
|
|
68
|
-
'Innodb_rows_inserted', 'Innodb_rows_updated', 'Innodb_rows_deleted'
|
|
69
|
-
)
|
|
61
|
+
const innodbQuery = `
|
|
62
|
+
SHOW GLOBAL STATUS WHERE Variable_name LIKE 'Innodb_%'
|
|
63
|
+
AND Variable_name IN (
|
|
64
|
+
'Innodb_buffer_pool_reads', 'Innodb_buffer_pool_read_requests',
|
|
65
|
+
'Innodb_buffer_pool_pages_total', 'Innodb_buffer_pool_pages_free',
|
|
66
|
+
'Innodb_buffer_pool_pages_data', 'Innodb_buffer_pool_pages_dirty',
|
|
67
|
+
'Innodb_row_lock_waits', 'Innodb_row_lock_time', 'Innodb_rows_read',
|
|
68
|
+
'Innodb_rows_inserted', 'Innodb_rows_updated', 'Innodb_rows_deleted'
|
|
69
|
+
)
|
|
70
70
|
`;
|
|
71
71
|
const innodbResult = await this.db.query(innodbQuery);
|
|
72
72
|
metrics.innodb = {};
|
|
@@ -82,8 +82,8 @@ class PerformanceTools {
|
|
|
82
82
|
(((requests - reads) / requests) * 100).toFixed(2) + "%";
|
|
83
83
|
}
|
|
84
84
|
// Get slow query metrics
|
|
85
|
-
const slowQuery = `
|
|
86
|
-
SHOW GLOBAL STATUS WHERE Variable_name IN ('Slow_queries', 'Questions')
|
|
85
|
+
const slowQuery = `
|
|
86
|
+
SHOW GLOBAL STATUS WHERE Variable_name IN ('Slow_queries', 'Questions')
|
|
87
87
|
`;
|
|
88
88
|
const slowResult = await this.db.query(slowQuery);
|
|
89
89
|
metrics.slow_queries = {};
|
|
@@ -121,23 +121,23 @@ class PerformanceTools {
|
|
|
121
121
|
error: "Limit must be a positive integer between 1 and 100",
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
-
const query = `
|
|
125
|
-
SELECT
|
|
126
|
-
DIGEST_TEXT as query_pattern,
|
|
127
|
-
COUNT_STAR as execution_count,
|
|
128
|
-
ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
|
|
129
|
-
ROUND(MAX_TIMER_WAIT / 1000000000000, 6) as max_execution_time_sec,
|
|
130
|
-
ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
|
|
131
|
-
ROUND(SUM_LOCK_TIME / 1000000000000, 6) as total_lock_time_sec,
|
|
132
|
-
SUM_ROWS_EXAMINED as rows_examined,
|
|
133
|
-
SUM_ROWS_SENT as rows_sent,
|
|
134
|
-
SUM_ROWS_AFFECTED as rows_affected,
|
|
135
|
-
FIRST_SEEN,
|
|
136
|
-
LAST_SEEN
|
|
137
|
-
FROM performance_schema.events_statements_summary_by_digest
|
|
138
|
-
WHERE DIGEST_TEXT IS NOT NULL
|
|
139
|
-
ORDER BY SUM_TIMER_WAIT DESC
|
|
140
|
-
LIMIT ${limit}
|
|
124
|
+
const query = `
|
|
125
|
+
SELECT
|
|
126
|
+
DIGEST_TEXT as query_pattern,
|
|
127
|
+
COUNT_STAR as execution_count,
|
|
128
|
+
ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
|
|
129
|
+
ROUND(MAX_TIMER_WAIT / 1000000000000, 6) as max_execution_time_sec,
|
|
130
|
+
ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
|
|
131
|
+
ROUND(SUM_LOCK_TIME / 1000000000000, 6) as total_lock_time_sec,
|
|
132
|
+
SUM_ROWS_EXAMINED as rows_examined,
|
|
133
|
+
SUM_ROWS_SENT as rows_sent,
|
|
134
|
+
SUM_ROWS_AFFECTED as rows_affected,
|
|
135
|
+
FIRST_SEEN,
|
|
136
|
+
LAST_SEEN
|
|
137
|
+
FROM performance_schema.events_statements_summary_by_digest
|
|
138
|
+
WHERE DIGEST_TEXT IS NOT NULL
|
|
139
|
+
ORDER BY SUM_TIMER_WAIT DESC
|
|
140
|
+
LIMIT ${limit}
|
|
141
141
|
`;
|
|
142
142
|
const results = await this.db.query(query);
|
|
143
143
|
return {
|
|
@@ -164,21 +164,21 @@ class PerformanceTools {
|
|
|
164
164
|
error: "Limit must be a positive integer between 1 and 100",
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
|
-
const query = `
|
|
168
|
-
SELECT
|
|
169
|
-
DIGEST_TEXT as query_pattern,
|
|
170
|
-
COUNT_STAR as execution_count,
|
|
171
|
-
ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
|
|
172
|
-
ROUND(MAX_TIMER_WAIT / 1000000000000, 6) as max_execution_time_sec,
|
|
173
|
-
ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
|
|
174
|
-
SUM_ROWS_EXAMINED as rows_examined,
|
|
175
|
-
SUM_ROWS_SENT as rows_sent,
|
|
176
|
-
FIRST_SEEN,
|
|
177
|
-
LAST_SEEN
|
|
178
|
-
FROM performance_schema.events_statements_summary_by_digest
|
|
179
|
-
WHERE DIGEST_TEXT IS NOT NULL
|
|
180
|
-
ORDER BY COUNT_STAR DESC
|
|
181
|
-
LIMIT ${limit}
|
|
167
|
+
const query = `
|
|
168
|
+
SELECT
|
|
169
|
+
DIGEST_TEXT as query_pattern,
|
|
170
|
+
COUNT_STAR as execution_count,
|
|
171
|
+
ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
|
|
172
|
+
ROUND(MAX_TIMER_WAIT / 1000000000000, 6) as max_execution_time_sec,
|
|
173
|
+
ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
|
|
174
|
+
SUM_ROWS_EXAMINED as rows_examined,
|
|
175
|
+
SUM_ROWS_SENT as rows_sent,
|
|
176
|
+
FIRST_SEEN,
|
|
177
|
+
LAST_SEEN
|
|
178
|
+
FROM performance_schema.events_statements_summary_by_digest
|
|
179
|
+
WHERE DIGEST_TEXT IS NOT NULL
|
|
180
|
+
ORDER BY COUNT_STAR DESC
|
|
181
|
+
LIMIT ${limit}
|
|
182
182
|
`;
|
|
183
183
|
const results = await this.db.query(query);
|
|
184
184
|
return {
|
|
@@ -213,23 +213,23 @@ class PerformanceTools {
|
|
|
213
213
|
};
|
|
214
214
|
}
|
|
215
215
|
const thresholdPico = thresholdSec * 1000000000000;
|
|
216
|
-
const query = `
|
|
217
|
-
SELECT
|
|
218
|
-
DIGEST_TEXT as query_pattern,
|
|
219
|
-
COUNT_STAR as execution_count,
|
|
220
|
-
ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
|
|
221
|
-
ROUND(MAX_TIMER_WAIT / 1000000000000, 6) as max_execution_time_sec,
|
|
222
|
-
ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
|
|
223
|
-
ROUND(SUM_LOCK_TIME / 1000000000000, 6) as total_lock_time_sec,
|
|
224
|
-
SUM_ROWS_EXAMINED as rows_examined,
|
|
225
|
-
SUM_ROWS_SENT as rows_sent,
|
|
226
|
-
SUM_NO_INDEX_USED as no_index_used_count,
|
|
227
|
-
FIRST_SEEN,
|
|
228
|
-
LAST_SEEN
|
|
229
|
-
FROM performance_schema.events_statements_summary_by_digest
|
|
230
|
-
WHERE DIGEST_TEXT IS NOT NULL AND AVG_TIMER_WAIT > ${thresholdPico}
|
|
231
|
-
ORDER BY AVG_TIMER_WAIT DESC
|
|
232
|
-
LIMIT ${limit}
|
|
216
|
+
const query = `
|
|
217
|
+
SELECT
|
|
218
|
+
DIGEST_TEXT as query_pattern,
|
|
219
|
+
COUNT_STAR as execution_count,
|
|
220
|
+
ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
|
|
221
|
+
ROUND(MAX_TIMER_WAIT / 1000000000000, 6) as max_execution_time_sec,
|
|
222
|
+
ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
|
|
223
|
+
ROUND(SUM_LOCK_TIME / 1000000000000, 6) as total_lock_time_sec,
|
|
224
|
+
SUM_ROWS_EXAMINED as rows_examined,
|
|
225
|
+
SUM_ROWS_SENT as rows_sent,
|
|
226
|
+
SUM_NO_INDEX_USED as no_index_used_count,
|
|
227
|
+
FIRST_SEEN,
|
|
228
|
+
LAST_SEEN
|
|
229
|
+
FROM performance_schema.events_statements_summary_by_digest
|
|
230
|
+
WHERE DIGEST_TEXT IS NOT NULL AND AVG_TIMER_WAIT > ${thresholdPico}
|
|
231
|
+
ORDER BY AVG_TIMER_WAIT DESC
|
|
232
|
+
LIMIT ${limit}
|
|
233
233
|
`;
|
|
234
234
|
const results = await this.db.query(query);
|
|
235
235
|
return {
|
|
@@ -257,21 +257,21 @@ class PerformanceTools {
|
|
|
257
257
|
error: "Limit must be a positive integer between 1 and 100",
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
|
-
let query = `
|
|
261
|
-
SELECT
|
|
262
|
-
OBJECT_SCHEMA as table_schema,
|
|
263
|
-
OBJECT_NAME as table_name,
|
|
264
|
-
COUNT_READ as read_operations,
|
|
265
|
-
COUNT_WRITE as write_operations,
|
|
266
|
-
COUNT_FETCH as fetch_operations,
|
|
267
|
-
COUNT_INSERT as insert_operations,
|
|
268
|
-
COUNT_UPDATE as update_operations,
|
|
269
|
-
COUNT_DELETE as delete_operations,
|
|
270
|
-
ROUND(SUM_TIMER_READ / 1000000000000, 6) as total_read_time_sec,
|
|
271
|
-
ROUND(SUM_TIMER_WRITE / 1000000000000, 6) as total_write_time_sec,
|
|
272
|
-
ROUND(SUM_TIMER_FETCH / 1000000000000, 6) as total_fetch_time_sec
|
|
273
|
-
FROM performance_schema.table_io_waits_summary_by_table
|
|
274
|
-
WHERE OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
|
|
260
|
+
let query = `
|
|
261
|
+
SELECT
|
|
262
|
+
OBJECT_SCHEMA as table_schema,
|
|
263
|
+
OBJECT_NAME as table_name,
|
|
264
|
+
COUNT_READ as read_operations,
|
|
265
|
+
COUNT_WRITE as write_operations,
|
|
266
|
+
COUNT_FETCH as fetch_operations,
|
|
267
|
+
COUNT_INSERT as insert_operations,
|
|
268
|
+
COUNT_UPDATE as update_operations,
|
|
269
|
+
COUNT_DELETE as delete_operations,
|
|
270
|
+
ROUND(SUM_TIMER_READ / 1000000000000, 6) as total_read_time_sec,
|
|
271
|
+
ROUND(SUM_TIMER_WRITE / 1000000000000, 6) as total_write_time_sec,
|
|
272
|
+
ROUND(SUM_TIMER_FETCH / 1000000000000, 6) as total_fetch_time_sec
|
|
273
|
+
FROM performance_schema.table_io_waits_summary_by_table
|
|
274
|
+
WHERE OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
|
|
275
275
|
`;
|
|
276
276
|
if (schema) {
|
|
277
277
|
query += ` AND OBJECT_SCHEMA = '${schema.replace(/'/g, "''")}'`;
|
|
@@ -303,21 +303,21 @@ class PerformanceTools {
|
|
|
303
303
|
error: "Limit must be a positive integer between 1 and 100",
|
|
304
304
|
};
|
|
305
305
|
}
|
|
306
|
-
let query = `
|
|
307
|
-
SELECT
|
|
308
|
-
OBJECT_SCHEMA as table_schema,
|
|
309
|
-
OBJECT_NAME as table_name,
|
|
310
|
-
INDEX_NAME as index_name,
|
|
311
|
-
COUNT_STAR as usage_count,
|
|
312
|
-
COUNT_READ as read_count,
|
|
313
|
-
COUNT_WRITE as write_count,
|
|
314
|
-
COUNT_FETCH as fetch_count,
|
|
315
|
-
COUNT_INSERT as insert_count,
|
|
316
|
-
COUNT_UPDATE as update_count,
|
|
317
|
-
COUNT_DELETE as delete_count
|
|
318
|
-
FROM performance_schema.table_io_waits_summary_by_index_usage
|
|
319
|
-
WHERE OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
|
|
320
|
-
AND INDEX_NAME IS NOT NULL
|
|
306
|
+
let query = `
|
|
307
|
+
SELECT
|
|
308
|
+
OBJECT_SCHEMA as table_schema,
|
|
309
|
+
OBJECT_NAME as table_name,
|
|
310
|
+
INDEX_NAME as index_name,
|
|
311
|
+
COUNT_STAR as usage_count,
|
|
312
|
+
COUNT_READ as read_count,
|
|
313
|
+
COUNT_WRITE as write_count,
|
|
314
|
+
COUNT_FETCH as fetch_count,
|
|
315
|
+
COUNT_INSERT as insert_count,
|
|
316
|
+
COUNT_UPDATE as update_count,
|
|
317
|
+
COUNT_DELETE as delete_count
|
|
318
|
+
FROM performance_schema.table_io_waits_summary_by_index_usage
|
|
319
|
+
WHERE OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
|
|
320
|
+
AND INDEX_NAME IS NOT NULL
|
|
321
321
|
`;
|
|
322
322
|
if (schema) {
|
|
323
323
|
query += ` AND OBJECT_SCHEMA = '${schema.replace(/'/g, "''")}'`;
|
|
@@ -342,26 +342,26 @@ class PerformanceTools {
|
|
|
342
342
|
async getUnusedIndexes(params) {
|
|
343
343
|
try {
|
|
344
344
|
const schema = params?.table_schema;
|
|
345
|
-
let query = `
|
|
346
|
-
SELECT
|
|
347
|
-
t.TABLE_SCHEMA as table_schema,
|
|
348
|
-
t.TABLE_NAME as table_name,
|
|
349
|
-
s.INDEX_NAME as index_name,
|
|
350
|
-
s.COLUMN_NAME as column_name,
|
|
351
|
-
s.SEQ_IN_INDEX as sequence_in_index,
|
|
352
|
-
s.NON_UNIQUE as is_non_unique
|
|
353
|
-
FROM information_schema.STATISTICS s
|
|
354
|
-
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage p
|
|
355
|
-
ON s.TABLE_SCHEMA = p.OBJECT_SCHEMA
|
|
356
|
-
AND s.TABLE_NAME = p.OBJECT_NAME
|
|
357
|
-
AND s.INDEX_NAME = p.INDEX_NAME
|
|
358
|
-
JOIN information_schema.TABLES t
|
|
359
|
-
ON s.TABLE_SCHEMA = t.TABLE_SCHEMA
|
|
360
|
-
AND s.TABLE_NAME = t.TABLE_NAME
|
|
361
|
-
WHERE s.TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
|
|
362
|
-
AND s.INDEX_NAME != 'PRIMARY'
|
|
363
|
-
AND (p.INDEX_NAME IS NULL OR p.COUNT_STAR = 0)
|
|
364
|
-
AND t.TABLE_TYPE = 'BASE TABLE'
|
|
345
|
+
let query = `
|
|
346
|
+
SELECT
|
|
347
|
+
t.TABLE_SCHEMA as table_schema,
|
|
348
|
+
t.TABLE_NAME as table_name,
|
|
349
|
+
s.INDEX_NAME as index_name,
|
|
350
|
+
s.COLUMN_NAME as column_name,
|
|
351
|
+
s.SEQ_IN_INDEX as sequence_in_index,
|
|
352
|
+
s.NON_UNIQUE as is_non_unique
|
|
353
|
+
FROM information_schema.STATISTICS s
|
|
354
|
+
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage p
|
|
355
|
+
ON s.TABLE_SCHEMA = p.OBJECT_SCHEMA
|
|
356
|
+
AND s.TABLE_NAME = p.OBJECT_NAME
|
|
357
|
+
AND s.INDEX_NAME = p.INDEX_NAME
|
|
358
|
+
JOIN information_schema.TABLES t
|
|
359
|
+
ON s.TABLE_SCHEMA = t.TABLE_SCHEMA
|
|
360
|
+
AND s.TABLE_NAME = t.TABLE_NAME
|
|
361
|
+
WHERE s.TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
|
|
362
|
+
AND s.INDEX_NAME != 'PRIMARY'
|
|
363
|
+
AND (p.INDEX_NAME IS NULL OR p.COUNT_STAR = 0)
|
|
364
|
+
AND t.TABLE_TYPE = 'BASE TABLE'
|
|
365
365
|
`;
|
|
366
366
|
if (schema) {
|
|
367
367
|
query += ` AND s.TABLE_SCHEMA = '${schema.replace(/'/g, "''")}'`;
|
|
@@ -385,18 +385,18 @@ class PerformanceTools {
|
|
|
385
385
|
*/
|
|
386
386
|
async getConnectionPoolStats() {
|
|
387
387
|
try {
|
|
388
|
-
const statusQuery = `
|
|
389
|
-
SHOW GLOBAL STATUS WHERE Variable_name IN (
|
|
390
|
-
'Threads_connected', 'Threads_running', 'Threads_created', 'Threads_cached',
|
|
391
|
-
'Connections', 'Max_used_connections', 'Max_used_connections_time',
|
|
392
|
-
'Aborted_connects', 'Aborted_clients', 'Connection_errors_max_connections'
|
|
393
|
-
)
|
|
388
|
+
const statusQuery = `
|
|
389
|
+
SHOW GLOBAL STATUS WHERE Variable_name IN (
|
|
390
|
+
'Threads_connected', 'Threads_running', 'Threads_created', 'Threads_cached',
|
|
391
|
+
'Connections', 'Max_used_connections', 'Max_used_connections_time',
|
|
392
|
+
'Aborted_connects', 'Aborted_clients', 'Connection_errors_max_connections'
|
|
393
|
+
)
|
|
394
394
|
`;
|
|
395
|
-
const variablesQuery = `
|
|
396
|
-
SHOW GLOBAL VARIABLES WHERE Variable_name IN (
|
|
397
|
-
'max_connections', 'thread_cache_size', 'connect_timeout',
|
|
398
|
-
'wait_timeout', 'interactive_timeout'
|
|
399
|
-
)
|
|
395
|
+
const variablesQuery = `
|
|
396
|
+
SHOW GLOBAL VARIABLES WHERE Variable_name IN (
|
|
397
|
+
'max_connections', 'thread_cache_size', 'connect_timeout',
|
|
398
|
+
'wait_timeout', 'interactive_timeout'
|
|
399
|
+
)
|
|
400
400
|
`;
|
|
401
401
|
const statusResult = await this.db.query(statusQuery);
|
|
402
402
|
const variablesResult = await this.db.query(variablesQuery);
|
|
@@ -461,8 +461,8 @@ class PerformanceTools {
|
|
|
461
461
|
errors: [],
|
|
462
462
|
};
|
|
463
463
|
// Check 1: Connection usage
|
|
464
|
-
const connQuery = `
|
|
465
|
-
SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected', 'Max_used_connections')
|
|
464
|
+
const connQuery = `
|
|
465
|
+
SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected', 'Max_used_connections')
|
|
466
466
|
`;
|
|
467
467
|
const maxConnQuery = `SHOW GLOBAL VARIABLES WHERE Variable_name = 'max_connections'`;
|
|
468
468
|
const connResult = await this.db.query(connQuery);
|
|
@@ -498,8 +498,8 @@ class PerformanceTools {
|
|
|
498
498
|
health.overall_status = "warning";
|
|
499
499
|
}
|
|
500
500
|
// Check 2: Buffer pool hit ratio
|
|
501
|
-
const bufferQuery = `
|
|
502
|
-
SHOW GLOBAL STATUS WHERE Variable_name IN ('Innodb_buffer_pool_read_requests', 'Innodb_buffer_pool_reads')
|
|
501
|
+
const bufferQuery = `
|
|
502
|
+
SHOW GLOBAL STATUS WHERE Variable_name IN ('Innodb_buffer_pool_read_requests', 'Innodb_buffer_pool_reads')
|
|
503
503
|
`;
|
|
504
504
|
const bufferResult = await this.db.query(bufferQuery);
|
|
505
505
|
let readRequests = 0;
|
|
@@ -526,8 +526,8 @@ class PerformanceTools {
|
|
|
526
526
|
health.overall_status = "warning";
|
|
527
527
|
}
|
|
528
528
|
// Check 3: Aborted connections
|
|
529
|
-
const abortQuery = `
|
|
530
|
-
SHOW GLOBAL STATUS WHERE Variable_name IN ('Connections', 'Aborted_connects')
|
|
529
|
+
const abortQuery = `
|
|
530
|
+
SHOW GLOBAL STATUS WHERE Variable_name IN ('Connections', 'Aborted_connects')
|
|
531
531
|
`;
|
|
532
532
|
const abortResult = await this.db.query(abortQuery);
|
|
533
533
|
let totalConnections = 0;
|
|
@@ -557,8 +557,8 @@ class PerformanceTools {
|
|
|
557
557
|
health.overall_status = "warning";
|
|
558
558
|
}
|
|
559
559
|
// Check 4: Slow queries
|
|
560
|
-
const slowQuery = `
|
|
561
|
-
SHOW GLOBAL STATUS WHERE Variable_name IN ('Questions', 'Slow_queries')
|
|
560
|
+
const slowQuery = `
|
|
561
|
+
SHOW GLOBAL STATUS WHERE Variable_name IN ('Questions', 'Slow_queries')
|
|
562
562
|
`;
|
|
563
563
|
const slowResult = await this.db.query(slowQuery);
|
|
564
564
|
let questions = 0;
|
|
@@ -217,15 +217,15 @@ class SecurityAuditTools {
|
|
|
217
217
|
async tryReadUserAccounts() {
|
|
218
218
|
try {
|
|
219
219
|
// We intentionally do NOT return authentication_string or password hashes.
|
|
220
|
-
const rows = await this.db.query(`
|
|
221
|
-
SELECT
|
|
222
|
-
User as user,
|
|
223
|
-
Host as host,
|
|
224
|
-
account_locked,
|
|
225
|
-
password_expired,
|
|
226
|
-
plugin
|
|
227
|
-
FROM mysql.user
|
|
228
|
-
WHERE User NOT IN ('mysql.sys', 'mysql.session', 'mysql.infoschema')
|
|
220
|
+
const rows = await this.db.query(`
|
|
221
|
+
SELECT
|
|
222
|
+
User as user,
|
|
223
|
+
Host as host,
|
|
224
|
+
account_locked,
|
|
225
|
+
password_expired,
|
|
226
|
+
plugin
|
|
227
|
+
FROM mysql.user
|
|
228
|
+
WHERE User NOT IN ('mysql.sys', 'mysql.session', 'mysql.infoschema')
|
|
229
229
|
`);
|
|
230
230
|
const findings = [];
|
|
231
231
|
const anonymous = rows.filter((r) => !r.user);
|
|
@@ -282,10 +282,10 @@ class SecurityAuditTools {
|
|
|
282
282
|
async tryReadPrivilegeSummaries() {
|
|
283
283
|
try {
|
|
284
284
|
// These INFORMATION_SCHEMA views are generally less sensitive than mysql.user.
|
|
285
|
-
const schemaPriv = await this.db.query(`
|
|
286
|
-
SELECT GRANTEE as grantee, TABLE_SCHEMA as schema_name, PRIVILEGE_TYPE as privilege_type
|
|
287
|
-
FROM INFORMATION_SCHEMA.SCHEMA_PRIVILEGES
|
|
288
|
-
WHERE TABLE_SCHEMA NOT IN ('mysql','performance_schema','information_schema','sys')
|
|
285
|
+
const schemaPriv = await this.db.query(`
|
|
286
|
+
SELECT GRANTEE as grantee, TABLE_SCHEMA as schema_name, PRIVILEGE_TYPE as privilege_type
|
|
287
|
+
FROM INFORMATION_SCHEMA.SCHEMA_PRIVILEGES
|
|
288
|
+
WHERE TABLE_SCHEMA NOT IN ('mysql','performance_schema','information_schema','sys')
|
|
289
289
|
`);
|
|
290
290
|
const findings = [];
|
|
291
291
|
const anyAll = schemaPriv.filter((r) => (r.privilege_type || "").toUpperCase() === "ALL PRIVILEGES");
|
|
@@ -38,6 +38,14 @@ export declare class UtilityTools {
|
|
|
38
38
|
data?: any;
|
|
39
39
|
error?: string;
|
|
40
40
|
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Lists all available tools in this MySQL MCP server
|
|
43
|
+
*/
|
|
44
|
+
listAllTools(): Promise<{
|
|
45
|
+
status: string;
|
|
46
|
+
data?: any;
|
|
47
|
+
error?: string;
|
|
48
|
+
}>;
|
|
41
49
|
/**
|
|
42
50
|
* Reads the CHANGELOG.md file from the project root
|
|
43
51
|
*/
|
|
@@ -292,6 +292,45 @@ class UtilityTools {
|
|
|
292
292
|
};
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Lists all available tools in this MySQL MCP server
|
|
297
|
+
*/
|
|
298
|
+
async listAllTools() {
|
|
299
|
+
try {
|
|
300
|
+
// Read manifest.json to get tool definitions
|
|
301
|
+
const manifestPath = path_1.default.resolve(__dirname, "..", "..", "manifest.json");
|
|
302
|
+
if (!fs_1.default.existsSync(manifestPath)) {
|
|
303
|
+
return {
|
|
304
|
+
status: "error",
|
|
305
|
+
error: "manifest.json not found in project root.",
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const manifestContent = fs_1.default.readFileSync(manifestPath, "utf-8");
|
|
309
|
+
const manifest = JSON.parse(manifestContent);
|
|
310
|
+
const tools = manifest.tools.map((tool) => ({
|
|
311
|
+
name: tool.name,
|
|
312
|
+
description: tool.description,
|
|
313
|
+
input_schema: tool.input_schema,
|
|
314
|
+
output_schema: tool.output_schema
|
|
315
|
+
}));
|
|
316
|
+
return {
|
|
317
|
+
status: "success",
|
|
318
|
+
data: {
|
|
319
|
+
total_tools: tools.length,
|
|
320
|
+
server_name: manifest.name,
|
|
321
|
+
server_version: manifest.version,
|
|
322
|
+
server_description: manifest.description,
|
|
323
|
+
tools: tools
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
return {
|
|
329
|
+
status: "error",
|
|
330
|
+
error: `Failed to list tools: ${error.message}`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
295
334
|
/**
|
|
296
335
|
* Reads the CHANGELOG.md file from the project root
|
|
297
336
|
*/
|
package/manifest.json
CHANGED
|
@@ -404,7 +404,7 @@
|
|
|
404
404
|
},
|
|
405
405
|
{
|
|
406
406
|
"name": "get_all_tables_relationships",
|
|
407
|
-
"description": "Gets foreign key relationships
|
|
407
|
+
"description": "Gets all table foreign key relationships in one call with memory-efficient relationship mapping.",
|
|
408
408
|
"input_schema": {
|
|
409
409
|
"type": "object",
|
|
410
410
|
"properties": {
|
|
@@ -784,6 +784,42 @@
|
|
|
784
784
|
"error": { "type": ["string", "null"] }
|
|
785
785
|
}
|
|
786
786
|
}
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
"name": "list_all_tools",
|
|
790
|
+
"description": "Lists all available tools in this MySQL MCP server with their descriptions and schemas.",
|
|
791
|
+
"input_schema": {
|
|
792
|
+
"type": "object",
|
|
793
|
+
"properties": {}
|
|
794
|
+
},
|
|
795
|
+
"output_schema": {
|
|
796
|
+
"type": "object",
|
|
797
|
+
"properties": {
|
|
798
|
+
"status": { "type": "string" },
|
|
799
|
+
"data": {
|
|
800
|
+
"type": "object",
|
|
801
|
+
"properties": {
|
|
802
|
+
"total_tools": { "type": "number" },
|
|
803
|
+
"server_name": { "type": "string" },
|
|
804
|
+
"server_version": { "type": "string" },
|
|
805
|
+
"server_description": { "type": "string" },
|
|
806
|
+
"tools": {
|
|
807
|
+
"type": "array",
|
|
808
|
+
"items": {
|
|
809
|
+
"type": "object",
|
|
810
|
+
"properties": {
|
|
811
|
+
"name": { "type": "string" },
|
|
812
|
+
"description": { "type": "string" },
|
|
813
|
+
"input_schema": { "type": "object" },
|
|
814
|
+
"output_schema": { "type": "object" }
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
"error": { "type": ["string", "null"] }
|
|
821
|
+
}
|
|
822
|
+
}
|
|
787
823
|
}
|
|
788
824
|
]
|
|
789
825
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@berthojoris/mcp-mysql-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Model Context Protocol server for MySQL database integration with dynamic per-project permissions, backup/restore, data import/export, and data migration capabilities",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|