@berthojoris/mcp-mysql-server 1.4.5 → 1.4.7
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 +36 -0
- package/README.md +146 -241
- package/dist/db/connection.d.ts +3 -0
- package/dist/db/connection.js +20 -0
- package/dist/db/queryLogger.d.ts +51 -0
- package/dist/db/queryLogger.js +139 -0
- package/dist/index.d.ts +6 -0
- package/dist/tools/crudTools.d.ts +4 -0
- package/dist/tools/crudTools.js +18 -9
- package/dist/tools/queryTools.d.ts +2 -0
- package/dist/tools/queryTools.js +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ All notable changes to the MySQL MCP Server will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.7] - 2025-11-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Query logging on output** - All query executions are now logged with detailed information including SQL, parameters, execution duration, and status
|
|
12
|
+
- `QueryLogger` utility class for tracking and formatting query logs
|
|
13
|
+
- Query logs are included in responses from query tools (runQuery, executeSql) and CRUD operations (create_record, read_records, update_record, delete_record)
|
|
14
|
+
- Query logs include: timestamp, SQL query, parameters used, execution time in milliseconds, and success/error status
|
|
15
|
+
- Production monitoring and configuration documentation for QueryLogger
|
|
16
|
+
|
|
17
|
+
### Security & Performance Improvements
|
|
18
|
+
- **Memory leak prevention** - SQL queries truncated to 500 characters max (prevents megabyte-sized log entries)
|
|
19
|
+
- **Parameter limiting** - Only first 5 parameters logged to prevent memory bloat from bulk operations
|
|
20
|
+
- **Safe serialization** - Handles circular references, BigInt, and unstringifiable objects without crashes
|
|
21
|
+
- **Deep copy protection** - Parameters are deep copied to prevent reference mutations
|
|
22
|
+
- **Bounded storage** - Maximum 100 most recent queries retained (~100 KB total memory usage)
|
|
23
|
+
- **Output truncation** - Formatted output limited to prevent massive response payloads
|
|
24
|
+
- **Error handling** - All JSON.stringify operations wrapped in try-catch with safe fallbacks
|
|
25
|
+
- **Memory impact reduction** - 99.9% memory reduction for bulk operations (from ~1 GB to ~100 KB)
|
|
26
|
+
|
|
27
|
+
### Technical Changes
|
|
28
|
+
- New `src/db/queryLogger.ts` module for query logging functionality with robust memory management
|
|
29
|
+
- Updated `src/db/connection.ts` to log all query executions with timing information
|
|
30
|
+
- Updated all query tool responses to include `queryLog` field with formatted log output
|
|
31
|
+
- Enhanced debugging capability by tracking the last 100 queries in memory
|
|
32
|
+
- Added configuration constants for tuning memory limits (MAX_LOGS, MAX_SQL_LENGTH, MAX_PARAM_LENGTH, MAX_PARAM_ITEMS)
|
|
33
|
+
- Implemented safeStringify method for type-safe value serialization
|
|
34
|
+
|
|
35
|
+
## [1.4.6] - 2025-11-21
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- Removed "Execute Permission & Advanced SQL Features" section from README.md
|
|
39
|
+
- Removed "Configuration" section from README.md
|
|
40
|
+
- Removed "REST API Mode" section from README.md
|
|
41
|
+
- Removed "Testing" section from README.md
|
|
42
|
+
- Streamlined documentation by removing outdated or less relevant sections
|
|
43
|
+
|
|
8
44
|
## [1.4.5] - 2025-01-08
|
|
9
45
|
|
|
10
46
|
### Changed
|
package/README.md
CHANGED
|
@@ -21,39 +21,6 @@ A fully-featured **Model Context Protocol (MCP)** server for MySQL database inte
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
## 🆕 Recent Updates (v1.4.4)
|
|
25
|
-
|
|
26
|
-
### Bug Fixes
|
|
27
|
-
|
|
28
|
-
#### ✅ Fixed: First Tool Call Failure Issue
|
|
29
|
-
**Problem**: The first tool call would fail with "Connection closed" error (-32000), but subsequent calls would succeed.
|
|
30
|
-
|
|
31
|
-
**Root Cause**: The MySQL MCP instance was initialized at module load time, before the MCP transport was fully connected, causing a race condition.
|
|
32
|
-
|
|
33
|
-
**Solution**: Moved the initialization to occur after the MCP transport is connected, ensuring proper startup sequence.
|
|
34
|
-
|
|
35
|
-
**Impact**: First tool calls now work reliably without needing retry.
|
|
36
|
-
|
|
37
|
-
#### ✅ Fixed: Execute Permission Not Respected
|
|
38
|
-
**Problem**: Users with `execute` permission would still get "Dangerous keyword detected" errors when using legitimate SQL functions like `LOAD_FILE()`, `UNION`, or accessing `INFORMATION_SCHEMA`.
|
|
39
|
-
|
|
40
|
-
**Root Cause**: The security layer blocked certain SQL keywords unconditionally, regardless of granted permissions.
|
|
41
|
-
|
|
42
|
-
**Solution**:
|
|
43
|
-
- Modified security validation to respect the `execute` permission
|
|
44
|
-
- Users with `execute` permission can now use:
|
|
45
|
-
- SQL functions like `LOAD_FILE()`, `BENCHMARK()`, `SLEEP()`
|
|
46
|
-
- Advanced SELECT features like `UNION` and subqueries
|
|
47
|
-
- Access to `INFORMATION_SCHEMA` for metadata queries
|
|
48
|
-
- Critical security operations remain blocked (GRANT, REVOKE, INTO OUTFILE, etc.)
|
|
49
|
-
|
|
50
|
-
**Impact**: Users with full permissions can now use advanced SQL features as intended.
|
|
51
|
-
|
|
52
|
-
### Breaking Changes
|
|
53
|
-
None - all changes are backward compatible.
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
24
|
## 📦 Installation
|
|
58
25
|
|
|
59
26
|
### Option 1: Quick Start (npx)
|
|
@@ -239,103 +206,6 @@ You can have different databases with different permissions in the same AI agent
|
|
|
239
206
|
|
|
240
207
|
---
|
|
241
208
|
|
|
242
|
-
## 🔓 Execute Permission & Advanced SQL Features
|
|
243
|
-
|
|
244
|
-
The `execute` permission unlocks advanced SQL capabilities that are restricted by default for security reasons.
|
|
245
|
-
|
|
246
|
-
### What Execute Permission Enables
|
|
247
|
-
|
|
248
|
-
When you grant the `execute` permission, users can:
|
|
249
|
-
|
|
250
|
-
#### 1. **Advanced SQL Functions**
|
|
251
|
-
- `LOAD_FILE()` - Read files from the server
|
|
252
|
-
- `BENCHMARK()` - Performance testing
|
|
253
|
-
- `SLEEP()` - Delay execution
|
|
254
|
-
- And other MySQL built-in functions
|
|
255
|
-
|
|
256
|
-
#### 2. **Advanced SELECT Features**
|
|
257
|
-
- `UNION` queries - Combine multiple SELECT results
|
|
258
|
-
- Subqueries in FROM clause - Complex nested queries
|
|
259
|
-
- Access to `INFORMATION_SCHEMA` - Database metadata queries
|
|
260
|
-
|
|
261
|
-
#### 3. **Execute Custom SQL**
|
|
262
|
-
- Use `execute_sql` tool for INSERT, UPDATE, DELETE with advanced features
|
|
263
|
-
- Use `run_query` tool for complex SELECT queries with UNION and subqueries
|
|
264
|
-
|
|
265
|
-
### Security Considerations
|
|
266
|
-
|
|
267
|
-
**Without `execute` permission:**
|
|
268
|
-
```sql
|
|
269
|
-
-- ❌ BLOCKED: UNION queries
|
|
270
|
-
SELECT * FROM users WHERE id = 1 UNION SELECT * FROM admin;
|
|
271
|
-
|
|
272
|
-
-- ❌ BLOCKED: Subqueries in FROM
|
|
273
|
-
SELECT * FROM (SELECT * FROM users WHERE active = 1) AS active_users;
|
|
274
|
-
|
|
275
|
-
-- ❌ BLOCKED: LOAD_FILE function
|
|
276
|
-
SELECT LOAD_FILE('/etc/passwd');
|
|
277
|
-
|
|
278
|
-
-- ❌ BLOCKED: INFORMATION_SCHEMA access
|
|
279
|
-
SELECT * FROM INFORMATION_SCHEMA.TABLES;
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
**With `execute` permission:**
|
|
283
|
-
```sql
|
|
284
|
-
-- ✅ ALLOWED: All advanced SQL features
|
|
285
|
-
SELECT * FROM users WHERE id = 1 UNION SELECT * FROM admin;
|
|
286
|
-
SELECT * FROM (SELECT * FROM users WHERE active = 1) AS active_users;
|
|
287
|
-
SELECT LOAD_FILE('/path/to/file.txt');
|
|
288
|
-
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'mydb';
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### When to Grant Execute Permission
|
|
292
|
-
|
|
293
|
-
**Grant `execute` when:**
|
|
294
|
-
- Users need complex analytical queries with UNION or subqueries
|
|
295
|
-
- Developers need access to INFORMATION_SCHEMA for metadata analysis
|
|
296
|
-
- Advanced SQL functions are required for specific operations
|
|
297
|
-
- You trust the user with broader database access
|
|
298
|
-
|
|
299
|
-
**Don't grant `execute` when:**
|
|
300
|
-
- Users only need basic CRUD operations
|
|
301
|
-
- Working with untrusted AI agents
|
|
302
|
-
- Production environments with strict security policies
|
|
303
|
-
- Read-only access is sufficient
|
|
304
|
-
|
|
305
|
-
### Example: Analytics with Execute Permission
|
|
306
|
-
|
|
307
|
-
```json
|
|
308
|
-
{
|
|
309
|
-
"args": [
|
|
310
|
-
"mysql://analyst:pass@localhost:3306/analytics_db",
|
|
311
|
-
"list,read,execute,utility"
|
|
312
|
-
]
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
This allows the AI agent to run complex analytical queries:
|
|
317
|
-
|
|
318
|
-
```sql
|
|
319
|
-
-- Complex query with UNION and subqueries
|
|
320
|
-
SELECT 'Q1' as quarter, SUM(revenue) as total
|
|
321
|
-
FROM (SELECT * FROM sales WHERE date BETWEEN '2024-01-01' AND '2024-03-31') AS q1_sales
|
|
322
|
-
UNION
|
|
323
|
-
SELECT 'Q2' as quarter, SUM(revenue) as total
|
|
324
|
-
FROM (SELECT * FROM sales WHERE date BETWEEN '2024-04-01' AND '2024-06-30') AS q2_sales;
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### Critical Security Keywords (Always Blocked)
|
|
328
|
-
|
|
329
|
-
Even with `execute` permission, these operations are **always blocked** for security:
|
|
330
|
-
|
|
331
|
-
- `GRANT` / `REVOKE` - User privilege management
|
|
332
|
-
- `INTO OUTFILE` / `INTO DUMPFILE` - Writing files to server
|
|
333
|
-
- `LOAD DATA` - Loading data from files
|
|
334
|
-
- Direct access to `mysql`, `performance_schema`, `sys` databases
|
|
335
|
-
- `USER` / `PASSWORD` manipulation
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
209
|
## 🚫 Permission Error Handling
|
|
340
210
|
|
|
341
211
|
The MySQL MCP Server provides clear, user-friendly error messages when operations are attempted without proper permissions. This helps users understand exactly what permissions are needed and how to enable them.
|
|
@@ -1189,49 +1059,169 @@ END IF;
|
|
|
1189
1059
|
|
|
1190
1060
|
---
|
|
1191
1061
|
|
|
1192
|
-
##
|
|
1062
|
+
## 📝 Query Logging
|
|
1193
1063
|
|
|
1194
|
-
|
|
1064
|
+
All queries executed through the MySQL MCP Server are automatically logged with detailed execution information. Query logs are included in the response output of all query and data manipulation operations.
|
|
1065
|
+
|
|
1066
|
+
### Query Log Information
|
|
1067
|
+
|
|
1068
|
+
Each logged query includes:
|
|
1069
|
+
- **Timestamp** - ISO 8601 formatted execution time
|
|
1070
|
+
- **SQL Query** - The exact SQL statement executed
|
|
1071
|
+
- **Parameters** - Values passed to the query (if any)
|
|
1072
|
+
- **Execution Duration** - Time taken to execute in milliseconds
|
|
1073
|
+
- **Status** - Success or error indication
|
|
1074
|
+
- **Error Details** - Error message if the query failed (optional)
|
|
1075
|
+
|
|
1076
|
+
### Example Query Log Output
|
|
1195
1077
|
|
|
1196
1078
|
```
|
|
1197
|
-
|
|
1079
|
+
[1] 2025-11-21T10:30:45.123Z | SELECT * FROM users WHERE id = ? | Params: [5] | Duration: 12ms | Status: success
|
|
1198
1080
|
```
|
|
1199
1081
|
|
|
1200
|
-
|
|
1082
|
+
### Viewing Query Logs in Responses
|
|
1083
|
+
|
|
1084
|
+
Query logs are automatically included in tool responses via the `queryLog` field:
|
|
1085
|
+
|
|
1086
|
+
**Query execution:**
|
|
1087
|
+
```json
|
|
1088
|
+
{
|
|
1089
|
+
"status": "success",
|
|
1090
|
+
"data": [
|
|
1091
|
+
{"id": 1, "name": "John Doe", "email": "john@example.com"}
|
|
1092
|
+
],
|
|
1093
|
+
"queryLog": "[1] 2025-11-21T10:30:45.123Z | SELECT * FROM users | Duration: 8ms | Status: success"
|
|
1094
|
+
}
|
|
1201
1095
|
```
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1096
|
+
|
|
1097
|
+
**Bulk operations with multiple queries:**
|
|
1098
|
+
```json
|
|
1099
|
+
{
|
|
1100
|
+
"status": "success",
|
|
1101
|
+
"data": {
|
|
1102
|
+
"affectedRows": 100,
|
|
1103
|
+
"totalInserted": 100
|
|
1104
|
+
},
|
|
1105
|
+
"queryLog": "[1] 2025-11-21T10:30:45.123Z | INSERT INTO users ... | Duration: 45ms | Status: success\n[2] 2025-11-21T10:30:45.168Z | INSERT INTO users ... | Duration: 23ms | Status: success"
|
|
1106
|
+
}
|
|
1205
1107
|
```
|
|
1206
1108
|
|
|
1207
|
-
|
|
1109
|
+
### Query Logs for Debugging
|
|
1110
|
+
|
|
1111
|
+
Query logs are valuable for:
|
|
1112
|
+
- **Performance Analysis** - Track which queries are slow (high duration)
|
|
1113
|
+
- **Troubleshooting** - Review exact parameters sent to queries
|
|
1114
|
+
- **Auditing** - See what operations were performed and when
|
|
1115
|
+
- **Optimization** - Identify patterns in query execution
|
|
1116
|
+
- **Error Investigation** - Review failed queries and their errors
|
|
1117
|
+
|
|
1118
|
+
### Query Log Limitations
|
|
1119
|
+
|
|
1120
|
+
- Logs are stored in memory (not persisted to disk)
|
|
1121
|
+
- Maximum of 100 most recent queries are retained
|
|
1122
|
+
- Logs are cleared when the MCP server restarts
|
|
1123
|
+
- For production audit trails, consider using MySQL's built-in query logging
|
|
1124
|
+
|
|
1125
|
+
### Tools with Query Logging
|
|
1126
|
+
|
|
1127
|
+
All tools that execute queries include logs:
|
|
1128
|
+
- `run_query` - SELECT query execution
|
|
1129
|
+
- `executeSql` - Write operations (INSERT, UPDATE, DELETE)
|
|
1130
|
+
- `create_record` - Single record insertion
|
|
1131
|
+
- `read_records` - Record querying with filters
|
|
1132
|
+
- `update_record` - Record updates
|
|
1133
|
+
- `delete_record` - Record deletion
|
|
1134
|
+
- Bulk operations (`bulk_insert`, `bulk_update`, `bulk_delete`)
|
|
1135
|
+
- Stored procedure execution
|
|
1136
|
+
- Transaction operations
|
|
1137
|
+
|
|
1138
|
+
### Query Logger Performance & Configuration
|
|
1139
|
+
|
|
1140
|
+
#### Memory Management
|
|
1141
|
+
|
|
1142
|
+
The QueryLogger is designed with robust memory safety:
|
|
1143
|
+
|
|
1144
|
+
**Built-in Protections:**
|
|
1145
|
+
- ✅ **SQL Truncation** - Queries truncated to 500 characters max
|
|
1146
|
+
- ✅ **Parameter Limiting** - Only first 5 parameters logged
|
|
1147
|
+
- ✅ **Value Truncation** - Individual parameter values limited to 50 characters
|
|
1148
|
+
- ✅ **Error Truncation** - Error messages limited to 200 characters
|
|
1149
|
+
- ✅ **Deep Copy** - Parameters are deep copied to prevent reference mutations
|
|
1150
|
+
- ✅ **Safe Serialization** - Handles circular references, BigInt, and unstringifiable objects
|
|
1151
|
+
- ✅ **Bounded Storage** - Maximum 100 most recent queries retained
|
|
1152
|
+
|
|
1153
|
+
**Memory Impact:**
|
|
1208
1154
|
```
|
|
1209
|
-
|
|
1155
|
+
Regular query: ~1 KB per log entry
|
|
1156
|
+
Bulk operations: ~1 KB per log entry (99.9% reduction vs unbounded)
|
|
1157
|
+
Total max memory: ~100 KB for all 100 log entries
|
|
1210
1158
|
```
|
|
1211
1159
|
|
|
1212
|
-
|
|
1160
|
+
#### Configuration Tuning
|
|
1213
1161
|
|
|
1214
|
-
|
|
1162
|
+
The QueryLogger limits are defined as constants and can be adjusted if needed by modifying `src/db/queryLogger.ts`:
|
|
1215
1163
|
|
|
1216
|
-
|
|
1164
|
+
```typescript
|
|
1165
|
+
private static readonly MAX_LOGS = 100; // Number of queries to retain
|
|
1166
|
+
private static readonly MAX_SQL_LENGTH = 500; // Max SQL string length
|
|
1167
|
+
private static readonly MAX_PARAM_LENGTH = 200; // Max params output length
|
|
1168
|
+
private static readonly MAX_PARAM_ITEMS = 5; // Max number of params to log
|
|
1169
|
+
```
|
|
1217
1170
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
DB_USER=root
|
|
1223
|
-
DB_PASSWORD=yourpassword
|
|
1224
|
-
DB_NAME=yourdatabase
|
|
1171
|
+
**Tuning Recommendations:**
|
|
1172
|
+
- **High-traffic production**: Reduce `MAX_LOGS` to 50 to minimize memory
|
|
1173
|
+
- **Development/debugging**: Increase `MAX_SQL_LENGTH` to 1000 for fuller visibility
|
|
1174
|
+
- **Bulk operations heavy**: Keep defaults - they're optimized for bulk workloads
|
|
1225
1175
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1176
|
+
#### Production Monitoring
|
|
1177
|
+
|
|
1178
|
+
When running in production, monitor these metrics:
|
|
1179
|
+
|
|
1180
|
+
1. **Memory Usage** - QueryLogger should use <500 KB total
|
|
1181
|
+
2. **Response Payload Size** - Query logs add minimal overhead (<1 KB per response)
|
|
1182
|
+
3. **Performance Impact** - Logging overhead is <1ms per query
|
|
1228
1183
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1184
|
+
**Health Check:**
|
|
1185
|
+
```javascript
|
|
1186
|
+
// Check log memory usage
|
|
1187
|
+
const logs = db.getQueryLogs();
|
|
1188
|
+
const estimatedMemory = logs.length * 1; // ~1 KB per log
|
|
1189
|
+
console.log(`Query log memory usage: ~${estimatedMemory} KB`);
|
|
1233
1190
|
```
|
|
1234
1191
|
|
|
1192
|
+
#### Persistent Logging for Production Auditing
|
|
1193
|
+
|
|
1194
|
+
**Important:** QueryLogger stores logs in memory only (not persisted to disk). For production audit trails and compliance, consider:
|
|
1195
|
+
|
|
1196
|
+
1. **MySQL Query Log** (Recommended)
|
|
1197
|
+
```sql
|
|
1198
|
+
-- Enable general query log
|
|
1199
|
+
SET GLOBAL general_log = 'ON';
|
|
1200
|
+
SET GLOBAL general_log_file = '/var/log/mysql/queries.log';
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
2. **MySQL Slow Query Log**
|
|
1204
|
+
```sql
|
|
1205
|
+
-- Log queries slower than 1 second
|
|
1206
|
+
SET GLOBAL slow_query_log = 'ON';
|
|
1207
|
+
SET GLOBAL long_query_time = 1;
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
3. **Application-Level Logging**
|
|
1211
|
+
- Use Winston or similar logger to persist query logs to disk
|
|
1212
|
+
- Integrate with log aggregation services (ELK, Splunk, DataDog)
|
|
1213
|
+
|
|
1214
|
+
4. **Database Audit Plugins**
|
|
1215
|
+
- MySQL Enterprise Audit
|
|
1216
|
+
- MariaDB Audit Plugin
|
|
1217
|
+
- Percona Audit Log Plugin
|
|
1218
|
+
|
|
1219
|
+
**Trade-offs:**
|
|
1220
|
+
- **In-Memory (QueryLogger)**: Fast, lightweight, for debugging & development
|
|
1221
|
+
- **MySQL Query Log**: Complete audit trail, slight performance impact
|
|
1222
|
+
- **Application Logging**: Flexible, can include business context
|
|
1223
|
+
- **Audit Plugins**: Enterprise-grade, compliance-ready, feature-rich
|
|
1224
|
+
|
|
1235
1225
|
---
|
|
1236
1226
|
|
|
1237
1227
|
## 🔒 Security Features
|
|
@@ -1281,92 +1271,6 @@ NODE_ENV=development
|
|
|
1281
1271
|
|
|
1282
1272
|
---
|
|
1283
1273
|
|
|
1284
|
-
## 🌐 REST API Mode
|
|
1285
|
-
|
|
1286
|
-
In addition to MCP protocol, this server can run as a REST API.
|
|
1287
|
-
|
|
1288
|
-
### Start API Server
|
|
1289
|
-
|
|
1290
|
-
```bash
|
|
1291
|
-
npm run start:api
|
|
1292
|
-
```
|
|
1293
|
-
|
|
1294
|
-
Server runs on `http://localhost:3000` (or configured PORT).
|
|
1295
|
-
|
|
1296
|
-
### API Endpoints
|
|
1297
|
-
|
|
1298
|
-
All endpoints require JWT authentication (except `/health`).
|
|
1299
|
-
|
|
1300
|
-
**Health Check:**
|
|
1301
|
-
```
|
|
1302
|
-
GET /health
|
|
1303
|
-
```
|
|
1304
|
-
|
|
1305
|
-
**Database Operations:**
|
|
1306
|
-
```
|
|
1307
|
-
GET /api/databases # List databases
|
|
1308
|
-
GET /api/tables # List tables
|
|
1309
|
-
GET /api/tables/:name/schema # Get table schema
|
|
1310
|
-
GET /api/tables/:name/records # Read records
|
|
1311
|
-
POST /api/tables/:name/records # Create record
|
|
1312
|
-
PUT /api/tables/:name/records/:id # Update record
|
|
1313
|
-
DELETE /api/tables/:name/records/:id # Delete record
|
|
1314
|
-
```
|
|
1315
|
-
|
|
1316
|
-
**Query Operations:**
|
|
1317
|
-
```
|
|
1318
|
-
POST /api/query # Execute SELECT query
|
|
1319
|
-
POST /api/execute # Execute write query
|
|
1320
|
-
```
|
|
1321
|
-
|
|
1322
|
-
**Utilities:**
|
|
1323
|
-
```
|
|
1324
|
-
GET /api/connection # Connection info
|
|
1325
|
-
GET /api/connection/test # Test connection
|
|
1326
|
-
GET /api/tables/:name/relationships # Get foreign keys
|
|
1327
|
-
```
|
|
1328
|
-
|
|
1329
|
-
### Authentication
|
|
1330
|
-
|
|
1331
|
-
Include JWT token in Authorization header:
|
|
1332
|
-
|
|
1333
|
-
```bash
|
|
1334
|
-
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
1335
|
-
http://localhost:3000/api/tables
|
|
1336
|
-
```
|
|
1337
|
-
|
|
1338
|
-
---
|
|
1339
|
-
|
|
1340
|
-
## 🧪 Testing
|
|
1341
|
-
|
|
1342
|
-
### Test MCP Server Locally
|
|
1343
|
-
|
|
1344
|
-
```bash
|
|
1345
|
-
# Test connection using npx (recommended)
|
|
1346
|
-
npx -y @berthojoris/mcp-mysql-server mysql://user:pass@localhost:3306/test "list,read,utility"
|
|
1347
|
-
|
|
1348
|
-
# Or if you cloned the repository locally
|
|
1349
|
-
node bin/mcp-mysql.js mysql://user:pass@localhost:3306/test "list,read,utility"
|
|
1350
|
-
```
|
|
1351
|
-
|
|
1352
|
-
### Test with Claude Desktop
|
|
1353
|
-
|
|
1354
|
-
1. Configure `claude_desktop_config.json`
|
|
1355
|
-
2. Restart Claude Desktop
|
|
1356
|
-
3. Ask: *"What databases are available?"*
|
|
1357
|
-
|
|
1358
|
-
### Test REST API
|
|
1359
|
-
|
|
1360
|
-
```bash
|
|
1361
|
-
# Start API server
|
|
1362
|
-
npm run start:api
|
|
1363
|
-
|
|
1364
|
-
# Test health endpoint
|
|
1365
|
-
curl http://localhost:3000/health
|
|
1366
|
-
```
|
|
1367
|
-
|
|
1368
|
-
---
|
|
1369
|
-
|
|
1370
1274
|
## 🚀 Bulk Operations
|
|
1371
1275
|
|
|
1372
1276
|
The MySQL MCP server includes powerful bulk operation tools designed for high-performance data processing. These tools are optimized for handling large datasets efficiently.
|
|
@@ -1542,6 +1446,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
1542
1446
|
- ✅ **Transaction support (BEGIN, COMMIT, ROLLBACK)** - **COMPLETED!**
|
|
1543
1447
|
- ✅ **Stored procedure execution** - **COMPLETED!**
|
|
1544
1448
|
- ✅ **Bulk operations (batch insert/update/delete)** - **COMPLETED!**
|
|
1449
|
+
- ✅ **Add query log on output** - **COMPLETED!**
|
|
1545
1450
|
- [ ] Query result caching
|
|
1546
1451
|
- [ ] Advanced query optimization hints
|
|
1547
1452
|
|
package/dist/db/connection.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ declare class DatabaseConnection {
|
|
|
17
17
|
rollbackTransaction(transactionId: string): Promise<void>;
|
|
18
18
|
getActiveTransactionIds(): string[];
|
|
19
19
|
hasActiveTransaction(transactionId: string): boolean;
|
|
20
|
+
getQueryLogs(): import("./queryLogger").QueryLog[];
|
|
21
|
+
getLastQueryLog(): import("./queryLogger").QueryLog | undefined;
|
|
22
|
+
getFormattedQueryLogs(count?: number): string;
|
|
20
23
|
executeInTransaction<T>(transactionId: string, sql: string, params?: any[]): Promise<T>;
|
|
21
24
|
}
|
|
22
25
|
export default DatabaseConnection;
|
package/dist/db/connection.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const promise_1 = __importDefault(require("mysql2/promise"));
|
|
7
7
|
const config_1 = require("../config/config");
|
|
8
|
+
const queryLogger_1 = require("./queryLogger");
|
|
8
9
|
class DatabaseConnection {
|
|
9
10
|
constructor() {
|
|
10
11
|
this.pool = promise_1.default.createPool({
|
|
@@ -34,11 +35,16 @@ class DatabaseConnection {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
async query(sql, params) {
|
|
38
|
+
const startTime = Date.now();
|
|
37
39
|
try {
|
|
38
40
|
const [results] = await this.pool.query(sql, params);
|
|
41
|
+
const duration = Date.now() - startTime;
|
|
42
|
+
queryLogger_1.QueryLogger.log(sql, params, duration, 'success');
|
|
39
43
|
return results;
|
|
40
44
|
}
|
|
41
45
|
catch (error) {
|
|
46
|
+
const duration = Date.now() - startTime;
|
|
47
|
+
queryLogger_1.QueryLogger.log(sql, params, duration, 'error', error.message);
|
|
42
48
|
throw new Error(`Query execution failed: ${error}`);
|
|
43
49
|
}
|
|
44
50
|
}
|
|
@@ -118,16 +124,30 @@ class DatabaseConnection {
|
|
|
118
124
|
hasActiveTransaction(transactionId) {
|
|
119
125
|
return this.activeTransactions.has(transactionId);
|
|
120
126
|
}
|
|
127
|
+
getQueryLogs() {
|
|
128
|
+
return queryLogger_1.QueryLogger.getLogs();
|
|
129
|
+
}
|
|
130
|
+
getLastQueryLog() {
|
|
131
|
+
return queryLogger_1.QueryLogger.getLastLog();
|
|
132
|
+
}
|
|
133
|
+
getFormattedQueryLogs(count = 1) {
|
|
134
|
+
return queryLogger_1.QueryLogger.formatLogs(queryLogger_1.QueryLogger.getLastLogs(count));
|
|
135
|
+
}
|
|
121
136
|
async executeInTransaction(transactionId, sql, params) {
|
|
122
137
|
const connection = this.activeTransactions.get(transactionId);
|
|
123
138
|
if (!connection) {
|
|
124
139
|
throw new Error(`No active transaction found with ID: ${transactionId}`);
|
|
125
140
|
}
|
|
141
|
+
const startTime = Date.now();
|
|
126
142
|
try {
|
|
127
143
|
const [results] = await connection.query(sql, params);
|
|
144
|
+
const duration = Date.now() - startTime;
|
|
145
|
+
queryLogger_1.QueryLogger.log(sql, params, duration, 'success');
|
|
128
146
|
return results;
|
|
129
147
|
}
|
|
130
148
|
catch (error) {
|
|
149
|
+
const duration = Date.now() - startTime;
|
|
150
|
+
queryLogger_1.QueryLogger.log(sql, params, duration, 'error', error.message);
|
|
131
151
|
throw new Error(`Query execution in transaction failed: ${error}`);
|
|
132
152
|
}
|
|
133
153
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface QueryLog {
|
|
2
|
+
sql: string;
|
|
3
|
+
params?: any[];
|
|
4
|
+
duration: number;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
status: 'success' | 'error';
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class QueryLogger {
|
|
10
|
+
private static logs;
|
|
11
|
+
private static readonly MAX_LOGS;
|
|
12
|
+
private static readonly MAX_SQL_LENGTH;
|
|
13
|
+
private static readonly MAX_PARAM_LENGTH;
|
|
14
|
+
private static readonly MAX_PARAM_ITEMS;
|
|
15
|
+
/**
|
|
16
|
+
* Safely stringify a value with truncation and error handling
|
|
17
|
+
*/
|
|
18
|
+
private static safeStringify;
|
|
19
|
+
/**
|
|
20
|
+
* Truncate SQL string to prevent memory issues
|
|
21
|
+
*/
|
|
22
|
+
private static truncateSQL;
|
|
23
|
+
/**
|
|
24
|
+
* Create a memory-safe copy of parameters
|
|
25
|
+
*/
|
|
26
|
+
private static sanitizeParams;
|
|
27
|
+
/**
|
|
28
|
+
* Log a query execution
|
|
29
|
+
*/
|
|
30
|
+
static log(sql: string, params: any[] | undefined, duration: number, status: 'success' | 'error', error?: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Get all logged queries (returns shallow copy of array)
|
|
33
|
+
*/
|
|
34
|
+
static getLogs(): QueryLog[];
|
|
35
|
+
/**
|
|
36
|
+
* Get the last N logged queries
|
|
37
|
+
*/
|
|
38
|
+
static getLastLogs(count?: number): QueryLog[];
|
|
39
|
+
/**
|
|
40
|
+
* Get logs for the current session (last query execution)
|
|
41
|
+
*/
|
|
42
|
+
static getLastLog(): QueryLog | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Clear all logs
|
|
45
|
+
*/
|
|
46
|
+
static clearLogs(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Get logs as formatted string for output
|
|
49
|
+
*/
|
|
50
|
+
static formatLogs(logs: QueryLog[]): string;
|
|
51
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueryLogger = void 0;
|
|
4
|
+
class QueryLogger {
|
|
5
|
+
/**
|
|
6
|
+
* Safely stringify a value with truncation and error handling
|
|
7
|
+
*/
|
|
8
|
+
static safeStringify(value, maxLength = 100) {
|
|
9
|
+
try {
|
|
10
|
+
if (value === null)
|
|
11
|
+
return 'null';
|
|
12
|
+
if (value === undefined)
|
|
13
|
+
return 'undefined';
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
return value.length > maxLength ? value.substring(0, maxLength) + '...' : value;
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
18
|
+
return String(value);
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === 'bigint') {
|
|
21
|
+
return value.toString() + 'n';
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
if (value.length === 0)
|
|
25
|
+
return '[]';
|
|
26
|
+
const items = value.slice(0, 3).map(v => this.safeStringify(v, 30));
|
|
27
|
+
return value.length > 3
|
|
28
|
+
? `[${items.join(', ')}, ... +${value.length - 3} more]`
|
|
29
|
+
: `[${items.join(', ')}]`;
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === 'object') {
|
|
32
|
+
const str = JSON.stringify(value);
|
|
33
|
+
return str.length > maxLength ? str.substring(0, maxLength) + '...}' : str;
|
|
34
|
+
}
|
|
35
|
+
return String(value);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return '[Unstringifiable]';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Truncate SQL string to prevent memory issues
|
|
43
|
+
*/
|
|
44
|
+
static truncateSQL(sql) {
|
|
45
|
+
if (sql.length <= this.MAX_SQL_LENGTH)
|
|
46
|
+
return sql;
|
|
47
|
+
return sql.substring(0, this.MAX_SQL_LENGTH) + `... [truncated ${sql.length - this.MAX_SQL_LENGTH} chars]`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a memory-safe copy of parameters
|
|
51
|
+
*/
|
|
52
|
+
static sanitizeParams(params) {
|
|
53
|
+
if (!params || params.length === 0)
|
|
54
|
+
return undefined;
|
|
55
|
+
// Only keep first N params to prevent memory issues
|
|
56
|
+
const limitedParams = params.slice(0, this.MAX_PARAM_ITEMS);
|
|
57
|
+
// Create deep copy to prevent reference issues
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(JSON.stringify(limitedParams));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// If JSON serialization fails, create safe string representations
|
|
63
|
+
return limitedParams.map(p => this.safeStringify(p, 50));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Log a query execution
|
|
68
|
+
*/
|
|
69
|
+
static log(sql, params, duration, status, error) {
|
|
70
|
+
const log = {
|
|
71
|
+
sql: this.truncateSQL(sql),
|
|
72
|
+
params: this.sanitizeParams(params),
|
|
73
|
+
duration,
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
status,
|
|
76
|
+
error: error ? (error.length > 200 ? error.substring(0, 200) + '...' : error) : undefined
|
|
77
|
+
};
|
|
78
|
+
this.logs.push(log);
|
|
79
|
+
// Keep only the last MAX_LOGS entries
|
|
80
|
+
if (this.logs.length > this.MAX_LOGS) {
|
|
81
|
+
this.logs.shift();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get all logged queries (returns shallow copy of array)
|
|
86
|
+
*/
|
|
87
|
+
static getLogs() {
|
|
88
|
+
return [...this.logs];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the last N logged queries
|
|
92
|
+
*/
|
|
93
|
+
static getLastLogs(count = 10) {
|
|
94
|
+
// Ensure count doesn't exceed array length to prevent issues
|
|
95
|
+
const safeCount = Math.min(Math.max(1, count), this.logs.length);
|
|
96
|
+
return this.logs.slice(-safeCount);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get logs for the current session (last query execution)
|
|
100
|
+
*/
|
|
101
|
+
static getLastLog() {
|
|
102
|
+
return this.logs[this.logs.length - 1];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Clear all logs
|
|
106
|
+
*/
|
|
107
|
+
static clearLogs() {
|
|
108
|
+
this.logs = [];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get logs as formatted string for output
|
|
112
|
+
*/
|
|
113
|
+
static formatLogs(logs) {
|
|
114
|
+
if (logs.length === 0)
|
|
115
|
+
return '';
|
|
116
|
+
return logs.map((log, index) => {
|
|
117
|
+
let paramStr = '';
|
|
118
|
+
if (log.params && log.params.length > 0) {
|
|
119
|
+
try {
|
|
120
|
+
const paramsJson = JSON.stringify(log.params);
|
|
121
|
+
paramStr = paramsJson.length > this.MAX_PARAM_LENGTH
|
|
122
|
+
? ` | Params: ${paramsJson.substring(0, this.MAX_PARAM_LENGTH)}...`
|
|
123
|
+
: ` | Params: ${paramsJson}`;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
paramStr = ' | Params: [Error serializing]';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const errorStr = log.error ? ` | Error: ${log.error}` : '';
|
|
130
|
+
return `[${index + 1}] ${log.timestamp} | ${log.sql}${paramStr} | Duration: ${log.duration}ms | Status: ${log.status}${errorStr}`;
|
|
131
|
+
}).join('\n');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.QueryLogger = QueryLogger;
|
|
135
|
+
QueryLogger.logs = [];
|
|
136
|
+
QueryLogger.MAX_LOGS = 100;
|
|
137
|
+
QueryLogger.MAX_SQL_LENGTH = 500; // Truncate SQL beyond this
|
|
138
|
+
QueryLogger.MAX_PARAM_LENGTH = 200; // Truncate params beyond this
|
|
139
|
+
QueryLogger.MAX_PARAM_ITEMS = 5; // Only log first N params
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export declare class MySQLMCP {
|
|
|
41
41
|
status: string;
|
|
42
42
|
data?: any;
|
|
43
43
|
error?: string;
|
|
44
|
+
queryLog?: string;
|
|
44
45
|
}>;
|
|
45
46
|
readRecords(params: {
|
|
46
47
|
table_name: string;
|
|
@@ -58,6 +59,7 @@ export declare class MySQLMCP {
|
|
|
58
59
|
data?: any[];
|
|
59
60
|
total?: number;
|
|
60
61
|
error?: string;
|
|
62
|
+
queryLog?: string;
|
|
61
63
|
}>;
|
|
62
64
|
updateRecord(params: {
|
|
63
65
|
table_name: string;
|
|
@@ -69,6 +71,7 @@ export declare class MySQLMCP {
|
|
|
69
71
|
affectedRows: number;
|
|
70
72
|
};
|
|
71
73
|
error?: string;
|
|
74
|
+
queryLog?: string;
|
|
72
75
|
}>;
|
|
73
76
|
deleteRecord(params: {
|
|
74
77
|
table_name: string;
|
|
@@ -79,6 +82,7 @@ export declare class MySQLMCP {
|
|
|
79
82
|
affectedRows: number;
|
|
80
83
|
};
|
|
81
84
|
error?: string;
|
|
85
|
+
queryLog?: string;
|
|
82
86
|
}>;
|
|
83
87
|
runQuery(params: {
|
|
84
88
|
query: string;
|
|
@@ -87,6 +91,7 @@ export declare class MySQLMCP {
|
|
|
87
91
|
status: string;
|
|
88
92
|
data?: any[];
|
|
89
93
|
error?: string;
|
|
94
|
+
queryLog?: string;
|
|
90
95
|
}>;
|
|
91
96
|
executeSql(params: {
|
|
92
97
|
query: string;
|
|
@@ -95,6 +100,7 @@ export declare class MySQLMCP {
|
|
|
95
100
|
status: string;
|
|
96
101
|
data?: any;
|
|
97
102
|
error?: string;
|
|
103
|
+
queryLog?: string;
|
|
98
104
|
}>;
|
|
99
105
|
createTable(params: any): Promise<{
|
|
100
106
|
status: string;
|
|
@@ -14,6 +14,7 @@ export declare class CrudTools {
|
|
|
14
14
|
status: string;
|
|
15
15
|
data?: any;
|
|
16
16
|
error?: string;
|
|
17
|
+
queryLog?: string;
|
|
17
18
|
}>;
|
|
18
19
|
/**
|
|
19
20
|
* Read records from the specified table with optional filters, pagination, and sorting
|
|
@@ -28,6 +29,7 @@ export declare class CrudTools {
|
|
|
28
29
|
data?: any[];
|
|
29
30
|
total?: number;
|
|
30
31
|
error?: string;
|
|
32
|
+
queryLog?: string;
|
|
31
33
|
}>;
|
|
32
34
|
/**
|
|
33
35
|
* Update records in the specified table based on conditions
|
|
@@ -42,6 +44,7 @@ export declare class CrudTools {
|
|
|
42
44
|
affectedRows: number;
|
|
43
45
|
};
|
|
44
46
|
error?: string;
|
|
47
|
+
queryLog?: string;
|
|
45
48
|
}>;
|
|
46
49
|
/**
|
|
47
50
|
* Delete records from the specified table based on conditions
|
|
@@ -55,6 +58,7 @@ export declare class CrudTools {
|
|
|
55
58
|
affectedRows: number;
|
|
56
59
|
};
|
|
57
60
|
error?: string;
|
|
61
|
+
queryLog?: string;
|
|
58
62
|
}>;
|
|
59
63
|
/**
|
|
60
64
|
* Bulk insert multiple records into the specified table
|
package/dist/tools/crudTools.js
CHANGED
|
@@ -64,13 +64,15 @@ class CrudTools {
|
|
|
64
64
|
data: {
|
|
65
65
|
insertId: result.insertId,
|
|
66
66
|
affectedRows: result.affectedRows
|
|
67
|
-
}
|
|
67
|
+
},
|
|
68
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
71
|
catch (error) {
|
|
71
72
|
return {
|
|
72
73
|
status: 'error',
|
|
73
|
-
error: error.message
|
|
74
|
+
error: error.message,
|
|
75
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
74
76
|
};
|
|
75
77
|
}
|
|
76
78
|
}
|
|
@@ -188,7 +190,8 @@ class CrudTools {
|
|
|
188
190
|
return {
|
|
189
191
|
status: 'success',
|
|
190
192
|
data: results,
|
|
191
|
-
total
|
|
193
|
+
total,
|
|
194
|
+
queryLog: this.db.getFormattedQueryLogs(2)
|
|
192
195
|
};
|
|
193
196
|
}
|
|
194
197
|
else {
|
|
@@ -198,14 +201,16 @@ class CrudTools {
|
|
|
198
201
|
return {
|
|
199
202
|
status: 'success',
|
|
200
203
|
data: results,
|
|
201
|
-
total: results.length
|
|
204
|
+
total: results.length,
|
|
205
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
202
206
|
};
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
catch (error) {
|
|
206
210
|
return {
|
|
207
211
|
status: 'error',
|
|
208
|
-
error: error.message
|
|
212
|
+
error: error.message,
|
|
213
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
209
214
|
};
|
|
210
215
|
}
|
|
211
216
|
}
|
|
@@ -320,13 +325,15 @@ class CrudTools {
|
|
|
320
325
|
status: 'success',
|
|
321
326
|
data: {
|
|
322
327
|
affectedRows: result.affectedRows
|
|
323
|
-
}
|
|
328
|
+
},
|
|
329
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
324
330
|
};
|
|
325
331
|
}
|
|
326
332
|
catch (error) {
|
|
327
333
|
return {
|
|
328
334
|
status: 'error',
|
|
329
|
-
error: error.message
|
|
335
|
+
error: error.message,
|
|
336
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
330
337
|
};
|
|
331
338
|
}
|
|
332
339
|
}
|
|
@@ -429,13 +436,15 @@ class CrudTools {
|
|
|
429
436
|
status: 'success',
|
|
430
437
|
data: {
|
|
431
438
|
affectedRows: result.affectedRows
|
|
432
|
-
}
|
|
439
|
+
},
|
|
440
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
433
441
|
};
|
|
434
442
|
}
|
|
435
443
|
catch (error) {
|
|
436
444
|
return {
|
|
437
445
|
status: 'error',
|
|
438
|
-
error: error.message
|
|
446
|
+
error: error.message,
|
|
447
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
439
448
|
};
|
|
440
449
|
}
|
|
441
450
|
}
|
|
@@ -13,6 +13,7 @@ export declare class QueryTools {
|
|
|
13
13
|
status: string;
|
|
14
14
|
data?: any[];
|
|
15
15
|
error?: string;
|
|
16
|
+
queryLog?: string;
|
|
16
17
|
}>;
|
|
17
18
|
/**
|
|
18
19
|
* Execute write operations (INSERT, UPDATE, DELETE) with validation
|
|
@@ -25,5 +26,6 @@ export declare class QueryTools {
|
|
|
25
26
|
status: string;
|
|
26
27
|
data?: any;
|
|
27
28
|
error?: string;
|
|
29
|
+
queryLog?: string;
|
|
28
30
|
}>;
|
|
29
31
|
}
|
package/dist/tools/queryTools.js
CHANGED
|
@@ -55,12 +55,14 @@ class QueryTools {
|
|
|
55
55
|
return {
|
|
56
56
|
status: "success",
|
|
57
57
|
data: results,
|
|
58
|
+
queryLog: this.db.getFormattedQueryLogs(1),
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
61
|
catch (error) {
|
|
61
62
|
return {
|
|
62
63
|
status: "error",
|
|
63
64
|
error: error.message,
|
|
65
|
+
queryLog: this.db.getFormattedQueryLogs(1),
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -110,12 +112,14 @@ class QueryTools {
|
|
|
110
112
|
affectedRows: result.affectedRows || 0,
|
|
111
113
|
insertId: result.insertId || null,
|
|
112
114
|
},
|
|
115
|
+
queryLog: this.db.getFormattedQueryLogs(1),
|
|
113
116
|
};
|
|
114
117
|
}
|
|
115
118
|
catch (error) {
|
|
116
119
|
return {
|
|
117
120
|
status: "error",
|
|
118
121
|
error: error.message,
|
|
122
|
+
queryLog: this.db.getFormattedQueryLogs(1),
|
|
119
123
|
};
|
|
120
124
|
}
|
|
121
125
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@berthojoris/mcp-mysql-server",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"description": "Model Context Protocol server for MySQL database integration with dynamic per-project permissions and data export capabilities",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|