@berthojoris/mcp-mysql-server 1.40.3 → 1.40.4
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 +16 -0
- package/README.md +1 -1
- package/dist/mcp-server.js +1 -1
- package/manifest.json +1 -1
- package/package.json +4 -6
- package/dist/server.d.ts +0 -2
- package/dist/server.js +0 -345
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ 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.40.4] - 2026-03-07
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
- **Unused Dependencies**: Removed `winston` and `@types/winston` packages (26 packages total)
|
|
12
|
+
- `winston` logging library was not used - project uses custom in-memory `QueryLogger` instead
|
|
13
|
+
- **Dead Code**: Removed unused tool modules left from previous refactoring
|
|
14
|
+
- `src/tools/processTools.ts` - Remnant from removed `server_management` category
|
|
15
|
+
- `src/tools/performanceTools.ts` - Remnant from removed `performance_monitoring` category
|
|
16
|
+
- `src/auth/` folder - Empty unused directory
|
|
17
|
+
- `dist/server.js` and `dist/server.d.ts` - Obsolete build artifacts
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Updated dependencies: 6 → 5 production dependencies (removed winston)
|
|
21
|
+
- Updated devDependencies: 10 → 9 (removed @types/winston)
|
|
22
|
+
- Synchronized version to `1.40.4` in `package.json`
|
|
23
|
+
|
|
8
24
|
## [1.40.3] - 2026-03-07
|
|
9
25
|
|
|
10
26
|
### Fixed
|
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:** 2026-03-07
|
|
7
|
+
**Last Updated:** 2026-03-07 12:45:00
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@berthojoris/mcp-mysql-server)
|
|
10
10
|
[](https://www.npmjs.com/package/@berthojoris/mcp-mysql-server)
|
package/dist/mcp-server.js
CHANGED
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@berthojoris/mcp-mysql-server",
|
|
3
|
-
"version": "1.40.
|
|
4
|
-
"description": "Model Context Protocol server for MySQL database integration with dynamic per-project permissions",
|
|
3
|
+
"version": "1.40.4",
|
|
4
|
+
"description": "Model Context Protocol server for MySQL database integration with dynamic per-project permissions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"type": "commonjs",
|
|
@@ -62,13 +62,11 @@
|
|
|
62
62
|
"ajv": "^8.12.0",
|
|
63
63
|
"ajv-formats": "^3.0.1",
|
|
64
64
|
"dotenv": "^16.3.1",
|
|
65
|
-
"mysql2": "^3.6.1"
|
|
66
|
-
"winston": "^3.11.0"
|
|
65
|
+
"mysql2": "^3.6.1"
|
|
67
66
|
},
|
|
68
67
|
"devDependencies": {
|
|
69
68
|
"@types/jest": "^29.5.4",
|
|
70
69
|
"@types/node": "^20.6.0",
|
|
71
|
-
"@types/winston": "^2.4.4",
|
|
72
70
|
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
73
71
|
"@typescript-eslint/parser": "^8.50.1",
|
|
74
72
|
"eslint": "^9.39.2",
|
|
@@ -77,4 +75,4 @@
|
|
|
77
75
|
"ts-node": "^10.9.1",
|
|
78
76
|
"typescript": "^5.2.2"
|
|
79
77
|
}
|
|
80
|
-
}
|
|
78
|
+
}
|
package/dist/server.d.ts
DELETED
package/dist/server.js
DELETED
|
@@ -1,345 +0,0 @@
|
|
|
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
|
-
const express_1 = __importDefault(require("express"));
|
|
7
|
-
const cors_1 = __importDefault(require("cors"));
|
|
8
|
-
const helmet_1 = __importDefault(require("helmet"));
|
|
9
|
-
const morgan_1 = __importDefault(require("morgan"));
|
|
10
|
-
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
|
11
|
-
const index_1 = require("./index");
|
|
12
|
-
const winston_1 = require("winston");
|
|
13
|
-
const inputValidation_1 = require("./validation/inputValidation");
|
|
14
|
-
// Initialize the MCP instance
|
|
15
|
-
const mcp = new index_1.MySQLMCP();
|
|
16
|
-
// Create Winston logger
|
|
17
|
-
const logger = (0, winston_1.createLogger)({
|
|
18
|
-
level: 'info',
|
|
19
|
-
format: winston_1.format.combine(winston_1.format.timestamp(), winston_1.format.json()),
|
|
20
|
-
transports: [
|
|
21
|
-
new winston_1.transports.Console(),
|
|
22
|
-
new winston_1.transports.File({ filename: 'logs/error.log', level: 'error' }),
|
|
23
|
-
new winston_1.transports.File({ filename: 'logs/combined.log' })
|
|
24
|
-
]
|
|
25
|
-
});
|
|
26
|
-
// Initialize Express app
|
|
27
|
-
const app = (0, express_1.default)();
|
|
28
|
-
const PORT = process.env.PORT || 3000;
|
|
29
|
-
// Middleware
|
|
30
|
-
app.use((0, helmet_1.default)()); // Security headers
|
|
31
|
-
app.use((0, cors_1.default)()); // Enable CORS
|
|
32
|
-
app.use(express_1.default.json()); // Parse JSON bodies
|
|
33
|
-
app.use((0, morgan_1.default)('combined')); // HTTP request logging
|
|
34
|
-
// Rate limiting
|
|
35
|
-
const apiLimiter = (0, express_rate_limit_1.default)({
|
|
36
|
-
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
37
|
-
max: 100, // Limit each IP to 100 requests per windowMs
|
|
38
|
-
standardHeaders: true,
|
|
39
|
-
legacyHeaders: false,
|
|
40
|
-
});
|
|
41
|
-
app.use(apiLimiter);
|
|
42
|
-
// No authentication middleware needed for MCP server
|
|
43
|
-
// Input validation middleware
|
|
44
|
-
const validateInput = (validator) => {
|
|
45
|
-
return (req, res, next) => {
|
|
46
|
-
const validation = validator(req.body);
|
|
47
|
-
if (!validation.valid) {
|
|
48
|
-
return res.status(400).json({
|
|
49
|
-
error: {
|
|
50
|
-
code: 'VALIDATION_ERROR',
|
|
51
|
-
message: 'Input validation failed',
|
|
52
|
-
details: validation.errors
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
next();
|
|
57
|
-
};
|
|
58
|
-
};
|
|
59
|
-
// Parameter sanitization middleware
|
|
60
|
-
const sanitizeParams = (req, res, next) => {
|
|
61
|
-
// Sanitize route parameters
|
|
62
|
-
if (req.params.tableName) {
|
|
63
|
-
req.params.tableName = (0, inputValidation_1.sanitizeTableName)(req.params.tableName);
|
|
64
|
-
}
|
|
65
|
-
if (req.params.id) {
|
|
66
|
-
req.params.id = (0, inputValidation_1.sanitizeFieldName)(req.params.id);
|
|
67
|
-
}
|
|
68
|
-
// Sanitize query parameters
|
|
69
|
-
if (req.query.id_field) {
|
|
70
|
-
req.query.id_field = (0, inputValidation_1.sanitizeFieldName)(req.query.id_field);
|
|
71
|
-
}
|
|
72
|
-
if (req.query.sort_by) {
|
|
73
|
-
req.query.sort_by = (0, inputValidation_1.sanitizeFieldName)(req.query.sort_by);
|
|
74
|
-
}
|
|
75
|
-
// Sanitize request body
|
|
76
|
-
if (req.body.query) {
|
|
77
|
-
req.body.query = (0, inputValidation_1.sanitizeQuery)(req.body.query);
|
|
78
|
-
}
|
|
79
|
-
next();
|
|
80
|
-
};
|
|
81
|
-
// Request size limiting middleware
|
|
82
|
-
const requestSizeLimit = (maxSize) => {
|
|
83
|
-
return (req, res, next) => {
|
|
84
|
-
const contentLength = parseInt(req.headers['content-length'] || '0');
|
|
85
|
-
if (contentLength > maxSize) {
|
|
86
|
-
return res.status(413).json({
|
|
87
|
-
error: {
|
|
88
|
-
code: 'REQUEST_TOO_LARGE',
|
|
89
|
-
message: `Request body too large. Maximum size is ${maxSize} bytes`
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
next();
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
// Error handling middleware
|
|
97
|
-
const errorHandler = (err, req, res, next) => {
|
|
98
|
-
logger.error(`${err.name}: ${err.message}`, {
|
|
99
|
-
path: req.path,
|
|
100
|
-
method: req.method,
|
|
101
|
-
body: req.body,
|
|
102
|
-
stack: err.stack
|
|
103
|
-
});
|
|
104
|
-
res.status(500).json({
|
|
105
|
-
error: {
|
|
106
|
-
code: 'SERVER_ERROR',
|
|
107
|
-
message: 'An unexpected error occurred',
|
|
108
|
-
details: process.env.NODE_ENV === 'production' ? 'See server logs for details' : err.message
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
};
|
|
112
|
-
// Health check endpoint (no auth required)
|
|
113
|
-
app.get('/health', (req, res) => {
|
|
114
|
-
res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
115
|
-
});
|
|
116
|
-
// Feature configuration status endpoint
|
|
117
|
-
app.get('/features', (req, res) => {
|
|
118
|
-
try {
|
|
119
|
-
const featureStatus = mcp.getFeatureStatus();
|
|
120
|
-
res.status(200).json(featureStatus);
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
logger.error('Error getting feature status', { error });
|
|
124
|
-
res.status(500).json({
|
|
125
|
-
status: 'error',
|
|
126
|
-
error: 'Failed to retrieve feature configuration status'
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
// API routes - no authentication required for MCP server
|
|
131
|
-
const apiRouter = express_1.default.Router();
|
|
132
|
-
app.use('/api', sanitizeParams, apiRouter);
|
|
133
|
-
// Database Tools Routes
|
|
134
|
-
apiRouter.get('/databases', async (req, res, next) => {
|
|
135
|
-
try {
|
|
136
|
-
const result = await mcp.listDatabases();
|
|
137
|
-
res.json(result);
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
next(error);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
apiRouter.get('/tables', async (req, res, next) => {
|
|
144
|
-
try {
|
|
145
|
-
const result = await mcp.listTables({ database: undefined });
|
|
146
|
-
res.json(result);
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
next(error);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
apiRouter.get('/tables/:tableName/schema', async (req, res, next) => {
|
|
153
|
-
try {
|
|
154
|
-
const { tableName } = req.params;
|
|
155
|
-
const result = await mcp.readTableSchema({ table_name: tableName });
|
|
156
|
-
res.json(result);
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
next(error);
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
// CRUD Operations Routes
|
|
163
|
-
apiRouter.post('/tables/:tableName/records', requestSizeLimit(inputValidation_1.INPUT_LIMITS.MAX_QUERY_LENGTH), validateInput((req) => (0, inputValidation_1.validateCreateRecord)({ table_name: req.params.tableName, data: req.body.data })), async (req, res, next) => {
|
|
164
|
-
try {
|
|
165
|
-
const { tableName } = req.params;
|
|
166
|
-
const { data } = req.body;
|
|
167
|
-
const result = await mcp.createRecord({
|
|
168
|
-
table_name: tableName,
|
|
169
|
-
data
|
|
170
|
-
});
|
|
171
|
-
res.status(201).json(result);
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
next(error);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
apiRouter.get('/tables/:tableName/records', async (req, res, next) => {
|
|
178
|
-
try {
|
|
179
|
-
const { tableName } = req.params;
|
|
180
|
-
const { filters, limit, offset, sort_by, sort_direction } = req.query;
|
|
181
|
-
// Validate and parse filters
|
|
182
|
-
let parsedFilters;
|
|
183
|
-
if (filters) {
|
|
184
|
-
try {
|
|
185
|
-
parsedFilters = JSON.parse(filters);
|
|
186
|
-
}
|
|
187
|
-
catch (e) {
|
|
188
|
-
return res.status(400).json({
|
|
189
|
-
error: {
|
|
190
|
-
code: 'INVALID_FILTERS',
|
|
191
|
-
message: 'Invalid JSON in filters parameter'
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
// Validate the complete request
|
|
197
|
-
const validation = (0, inputValidation_1.validateReadRecords)({
|
|
198
|
-
table_name: tableName,
|
|
199
|
-
filters: parsedFilters,
|
|
200
|
-
pagination: {
|
|
201
|
-
page: offset ? Math.floor(parseInt(offset) / (limit ? parseInt(limit) : 10)) + 1 : 1,
|
|
202
|
-
limit: limit ? parseInt(limit) : 10
|
|
203
|
-
},
|
|
204
|
-
sorting: sort_by ? {
|
|
205
|
-
field: sort_by,
|
|
206
|
-
direction: sort_direction?.toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
207
|
-
} : undefined
|
|
208
|
-
});
|
|
209
|
-
if (!validation.valid) {
|
|
210
|
-
return res.status(400).json({
|
|
211
|
-
error: {
|
|
212
|
-
code: 'VALIDATION_ERROR',
|
|
213
|
-
message: 'Input validation failed',
|
|
214
|
-
details: validation.errors
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
const result = await mcp.readRecords({
|
|
219
|
-
table_name: tableName,
|
|
220
|
-
filters: parsedFilters,
|
|
221
|
-
pagination: {
|
|
222
|
-
page: offset ? Math.floor(parseInt(offset) / (limit ? parseInt(limit) : 10)) + 1 : 1,
|
|
223
|
-
limit: limit ? parseInt(limit) : 10
|
|
224
|
-
},
|
|
225
|
-
sorting: sort_by ? {
|
|
226
|
-
field: sort_by,
|
|
227
|
-
direction: sort_direction?.toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
228
|
-
} : undefined
|
|
229
|
-
});
|
|
230
|
-
res.json(result);
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
next(error);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
apiRouter.put('/tables/:tableName/records/:id', requestSizeLimit(inputValidation_1.INPUT_LIMITS.MAX_QUERY_LENGTH), validateInput((req) => (0, inputValidation_1.validateUpdateRecord)({
|
|
237
|
-
table_name: req.params.tableName,
|
|
238
|
-
data: req.body.data,
|
|
239
|
-
conditions: [{ field: req.body.id_field || 'id', operator: '=', value: req.params.id }]
|
|
240
|
-
})), async (req, res, next) => {
|
|
241
|
-
try {
|
|
242
|
-
const { tableName, id } = req.params;
|
|
243
|
-
const { data, id_field } = req.body;
|
|
244
|
-
const result = await mcp.updateRecord({
|
|
245
|
-
table_name: tableName,
|
|
246
|
-
data,
|
|
247
|
-
conditions: [{ field: id_field || 'id', operator: '=', value: id }]
|
|
248
|
-
});
|
|
249
|
-
res.json(result);
|
|
250
|
-
}
|
|
251
|
-
catch (error) {
|
|
252
|
-
next(error);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
apiRouter.delete('/tables/:tableName/records/:id', validateInput((req) => (0, inputValidation_1.validateDeleteRecord)({
|
|
256
|
-
table_name: req.params.tableName,
|
|
257
|
-
conditions: [{ field: req.query.id_field || 'id', operator: '=', value: req.params.id }]
|
|
258
|
-
})), async (req, res, next) => {
|
|
259
|
-
try {
|
|
260
|
-
const { tableName, id } = req.params;
|
|
261
|
-
const { id_field } = req.query;
|
|
262
|
-
const result = await mcp.deleteRecord({
|
|
263
|
-
table_name: tableName,
|
|
264
|
-
conditions: [{ field: id_field || 'id', operator: '=', value: id }]
|
|
265
|
-
});
|
|
266
|
-
res.json(result);
|
|
267
|
-
}
|
|
268
|
-
catch (error) {
|
|
269
|
-
next(error);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
// Query Tools Routes
|
|
273
|
-
apiRouter.post('/query', requestSizeLimit(inputValidation_1.INPUT_LIMITS.MAX_QUERY_LENGTH), validateInput(inputValidation_1.validateQuery), async (req, res, next) => {
|
|
274
|
-
try {
|
|
275
|
-
const { query, params } = req.body;
|
|
276
|
-
const result = await mcp.runSelectQuery({
|
|
277
|
-
query,
|
|
278
|
-
params: params || []
|
|
279
|
-
});
|
|
280
|
-
res.json(result);
|
|
281
|
-
}
|
|
282
|
-
catch (error) {
|
|
283
|
-
next(error);
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
apiRouter.post('/execute', requestSizeLimit(inputValidation_1.INPUT_LIMITS.MAX_QUERY_LENGTH), validateInput(inputValidation_1.validateQuery), async (req, res, next) => {
|
|
287
|
-
try {
|
|
288
|
-
const { query, params } = req.body;
|
|
289
|
-
const result = await mcp.executeWriteQuery({
|
|
290
|
-
query,
|
|
291
|
-
params: params || []
|
|
292
|
-
});
|
|
293
|
-
res.json(result);
|
|
294
|
-
}
|
|
295
|
-
catch (error) {
|
|
296
|
-
next(error);
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
// Utility Tools Routes
|
|
300
|
-
apiRouter.get('/connection', async (req, res, next) => {
|
|
301
|
-
try {
|
|
302
|
-
const result = await mcp.describeConnection();
|
|
303
|
-
res.json(result);
|
|
304
|
-
}
|
|
305
|
-
catch (error) {
|
|
306
|
-
next(error);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
apiRouter.get('/connection/test', async (req, res, next) => {
|
|
310
|
-
try {
|
|
311
|
-
const result = await mcp.testConnection();
|
|
312
|
-
res.json(result);
|
|
313
|
-
}
|
|
314
|
-
catch (error) {
|
|
315
|
-
next(error);
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
apiRouter.get('/tables/:tableName/relationships', async (req, res, next) => {
|
|
319
|
-
try {
|
|
320
|
-
const { tableName } = req.params;
|
|
321
|
-
const result = await mcp.getTableRelationships({ table_name: tableName });
|
|
322
|
-
res.json(result);
|
|
323
|
-
}
|
|
324
|
-
catch (error) {
|
|
325
|
-
next(error);
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
// Apply error handler
|
|
329
|
-
app.use(errorHandler);
|
|
330
|
-
// Start the server
|
|
331
|
-
const server = app.listen(PORT, () => {
|
|
332
|
-
logger.info(`MCP MySQL Server running on port ${PORT}`);
|
|
333
|
-
console.log(`MCP MySQL Server running on port ${PORT}`);
|
|
334
|
-
});
|
|
335
|
-
// Graceful shutdown
|
|
336
|
-
process.on('SIGTERM', () => {
|
|
337
|
-
logger.info('SIGTERM signal received: closing HTTP server');
|
|
338
|
-
server.close(async () => {
|
|
339
|
-
logger.info('HTTP server closed');
|
|
340
|
-
await mcp.close();
|
|
341
|
-
logger.info('Database connections closed');
|
|
342
|
-
process.exit(0);
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
exports.default = app;
|