@berthojoris/mcp-mysql-server 1.4.2 → 1.4.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 +77 -56
- package/README.md +131 -1
- package/dist/mcp-server.js +429 -379
- package/dist/security/securityLayer.d.ts +8 -2
- package/dist/security/securityLayer.js +120 -48
- package/dist/tools/queryTools.d.ts +1 -1
- package/dist/tools/queryTools.js +33 -29
- package/package.json +86 -86
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FeatureConfig } from
|
|
1
|
+
import { FeatureConfig } from "../config/featureConfig.js";
|
|
2
2
|
export declare class SecurityLayer {
|
|
3
3
|
private ajv;
|
|
4
4
|
private readonly dangerousKeywords;
|
|
@@ -22,8 +22,10 @@ export declare class SecurityLayer {
|
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
24
|
* Validate SQL query for security issues
|
|
25
|
+
* @param query - The SQL query to validate
|
|
26
|
+
* @param bypassDangerousCheck - If true, skips dangerous keyword check (for users with 'execute' permission)
|
|
25
27
|
*/
|
|
26
|
-
validateQuery(query: string): {
|
|
28
|
+
validateQuery(query: string, bypassDangerousCheck?: boolean): {
|
|
27
29
|
valid: boolean;
|
|
28
30
|
error?: string;
|
|
29
31
|
queryType?: string;
|
|
@@ -44,6 +46,10 @@ export declare class SecurityLayer {
|
|
|
44
46
|
* Check if a query contains dangerous operations
|
|
45
47
|
*/
|
|
46
48
|
hasDangerousOperations(query: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Check if execute permission is enabled
|
|
51
|
+
*/
|
|
52
|
+
hasExecutePermission(): boolean;
|
|
47
53
|
/**
|
|
48
54
|
* Escape identifier for safe use in SQL queries
|
|
49
55
|
*/
|
|
@@ -10,18 +10,24 @@ class SecurityLayer {
|
|
|
10
10
|
constructor(featureConfig) {
|
|
11
11
|
this.ajv = new ajv_1.default();
|
|
12
12
|
this.featureConfig = featureConfig || new featureConfig_js_1.FeatureConfig();
|
|
13
|
-
// Define dangerous SQL keywords that should
|
|
13
|
+
// Define dangerous SQL keywords that should ALWAYS be blocked (critical security threats)
|
|
14
|
+
// These are blocked even with 'execute' permission
|
|
14
15
|
this.dangerousKeywords = [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
"GRANT",
|
|
17
|
+
"REVOKE",
|
|
18
|
+
"INTO OUTFILE",
|
|
19
|
+
"INTO DUMPFILE",
|
|
20
|
+
"LOAD DATA",
|
|
21
|
+
"MYSQL",
|
|
22
|
+
"PERFORMANCE_SCHEMA",
|
|
23
|
+
"SYS",
|
|
24
|
+
"USER",
|
|
25
|
+
"PASSWORD",
|
|
20
26
|
];
|
|
21
27
|
// Define basic allowed SQL operations
|
|
22
|
-
this.allowedOperations = [
|
|
28
|
+
this.allowedOperations = ["SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
23
29
|
// Define DDL operations that require special permission
|
|
24
|
-
this.ddlOperations = [
|
|
30
|
+
this.ddlOperations = ["CREATE", "ALTER", "DROP", "TRUNCATE", "RENAME"];
|
|
25
31
|
}
|
|
26
32
|
/**
|
|
27
33
|
* Validate input against a JSON schema
|
|
@@ -32,7 +38,7 @@ class SecurityLayer {
|
|
|
32
38
|
if (!valid) {
|
|
33
39
|
return {
|
|
34
40
|
valid: false,
|
|
35
|
-
errors: validate.errors
|
|
41
|
+
errors: validate.errors,
|
|
36
42
|
};
|
|
37
43
|
}
|
|
38
44
|
return { valid: true };
|
|
@@ -41,52 +47,88 @@ class SecurityLayer {
|
|
|
41
47
|
* Validate and sanitize table/column names to prevent SQL injection
|
|
42
48
|
*/
|
|
43
49
|
validateIdentifier(identifier) {
|
|
44
|
-
if (!identifier || typeof identifier !==
|
|
45
|
-
return { valid: false, error:
|
|
50
|
+
if (!identifier || typeof identifier !== "string") {
|
|
51
|
+
return { valid: false, error: "Identifier must be a non-empty string" };
|
|
46
52
|
}
|
|
47
53
|
// Check length
|
|
48
54
|
if (identifier.length > 64) {
|
|
49
|
-
return { valid: false, error:
|
|
55
|
+
return { valid: false, error: "Identifier too long (max 64 characters)" };
|
|
50
56
|
}
|
|
51
57
|
// MySQL identifier rules: alphanumeric, underscore, dollar sign
|
|
52
58
|
// Must start with letter, underscore, or dollar sign
|
|
53
59
|
const identifierRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
54
60
|
if (!identifierRegex.test(identifier)) {
|
|
55
|
-
return { valid: false, error:
|
|
61
|
+
return { valid: false, error: "Invalid identifier format" };
|
|
56
62
|
}
|
|
57
63
|
// Check against MySQL reserved words (basic list)
|
|
58
64
|
const reservedWords = [
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
"SELECT",
|
|
66
|
+
"INSERT",
|
|
67
|
+
"UPDATE",
|
|
68
|
+
"DELETE",
|
|
69
|
+
"FROM",
|
|
70
|
+
"WHERE",
|
|
71
|
+
"JOIN",
|
|
72
|
+
"INNER",
|
|
73
|
+
"LEFT",
|
|
74
|
+
"RIGHT",
|
|
75
|
+
"OUTER",
|
|
76
|
+
"ON",
|
|
77
|
+
"AS",
|
|
78
|
+
"AND",
|
|
79
|
+
"OR",
|
|
80
|
+
"NOT",
|
|
81
|
+
"NULL",
|
|
82
|
+
"TRUE",
|
|
83
|
+
"FALSE",
|
|
84
|
+
"ORDER",
|
|
85
|
+
"BY",
|
|
86
|
+
"GROUP",
|
|
87
|
+
"HAVING",
|
|
88
|
+
"LIMIT",
|
|
89
|
+
"OFFSET",
|
|
90
|
+
"DISTINCT",
|
|
91
|
+
"ALL",
|
|
92
|
+
"EXISTS",
|
|
93
|
+
"IN",
|
|
94
|
+
"BETWEEN",
|
|
95
|
+
"LIKE",
|
|
96
|
+
"REGEXP",
|
|
97
|
+
"CASE",
|
|
98
|
+
"WHEN",
|
|
99
|
+
"THEN",
|
|
100
|
+
"ELSE",
|
|
101
|
+
"END",
|
|
102
|
+
"IF",
|
|
103
|
+
"IFNULL",
|
|
64
104
|
];
|
|
65
105
|
if (reservedWords.includes(identifier.toUpperCase())) {
|
|
66
|
-
return { valid: false, error:
|
|
106
|
+
return { valid: false, error: "Identifier cannot be a reserved word" };
|
|
67
107
|
}
|
|
68
108
|
return { valid: true };
|
|
69
109
|
}
|
|
70
110
|
/**
|
|
71
111
|
* Validate SQL query for security issues
|
|
112
|
+
* @param query - The SQL query to validate
|
|
113
|
+
* @param bypassDangerousCheck - If true, skips dangerous keyword check (for users with 'execute' permission)
|
|
72
114
|
*/
|
|
73
|
-
validateQuery(query) {
|
|
74
|
-
if (!query || typeof query !==
|
|
75
|
-
return { valid: false, error:
|
|
115
|
+
validateQuery(query, bypassDangerousCheck = false) {
|
|
116
|
+
if (!query || typeof query !== "string") {
|
|
117
|
+
return { valid: false, error: "Query must be a non-empty string" };
|
|
76
118
|
}
|
|
77
119
|
const trimmedQuery = query.trim().toUpperCase();
|
|
78
120
|
// Check for empty query
|
|
79
121
|
if (trimmedQuery.length === 0) {
|
|
80
|
-
return { valid: false, error:
|
|
122
|
+
return { valid: false, error: "Query cannot be empty" };
|
|
81
123
|
}
|
|
82
124
|
// Check for multiple statements (basic check)
|
|
83
|
-
if (query.includes(
|
|
84
|
-
return { valid: false, error:
|
|
125
|
+
if (query.includes(";") && !query.trim().endsWith(";")) {
|
|
126
|
+
return { valid: false, error: "Multiple statements not allowed" };
|
|
85
127
|
}
|
|
86
128
|
// Remove trailing semicolon for analysis
|
|
87
|
-
const cleanQuery = trimmedQuery.replace(/;$/,
|
|
129
|
+
const cleanQuery = trimmedQuery.replace(/;$/, "");
|
|
88
130
|
// Determine query type - check basic operations first
|
|
89
|
-
let queryType =
|
|
131
|
+
let queryType = "";
|
|
90
132
|
for (const operation of this.allowedOperations) {
|
|
91
133
|
if (cleanQuery.startsWith(operation)) {
|
|
92
134
|
queryType = operation;
|
|
@@ -105,35 +147,50 @@ class SecurityLayer {
|
|
|
105
147
|
else {
|
|
106
148
|
return {
|
|
107
149
|
valid: false,
|
|
108
|
-
error: `DDL operation '${ddlOp}' requires 'ddl' permission. Add 'ddl' to your permissions configuration
|
|
150
|
+
error: `DDL operation '${ddlOp}' requires 'ddl' permission. Add 'ddl' to your permissions configuration.`,
|
|
109
151
|
};
|
|
110
152
|
}
|
|
111
153
|
}
|
|
112
154
|
}
|
|
113
155
|
}
|
|
114
156
|
if (!queryType) {
|
|
115
|
-
return { valid: false, error:
|
|
157
|
+
return { valid: false, error: "Query type not allowed" };
|
|
116
158
|
}
|
|
117
|
-
// Check for dangerous keywords (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
159
|
+
// Check for dangerous keywords (blocked unless user has 'execute' permission)
|
|
160
|
+
// When bypassDangerousCheck is true (user has 'execute' permission), skip this check
|
|
161
|
+
if (!bypassDangerousCheck) {
|
|
162
|
+
for (const keyword of this.dangerousKeywords) {
|
|
163
|
+
if (cleanQuery.includes(keyword)) {
|
|
164
|
+
return {
|
|
165
|
+
valid: false,
|
|
166
|
+
error: `Dangerous keyword detected: ${keyword}. This requires 'execute' permission.`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
121
169
|
}
|
|
122
170
|
}
|
|
123
171
|
// Additional checks for specific query types
|
|
124
|
-
|
|
172
|
+
// Only enforce these restrictions when user doesn't have 'execute' permission
|
|
173
|
+
if (queryType === "SELECT" && !bypassDangerousCheck) {
|
|
125
174
|
// Check for UNION attacks
|
|
126
|
-
if (cleanQuery.includes(
|
|
127
|
-
return {
|
|
175
|
+
if (cleanQuery.includes("UNION")) {
|
|
176
|
+
return {
|
|
177
|
+
valid: false,
|
|
178
|
+
error: "UNION operations not allowed without 'execute' permission",
|
|
179
|
+
};
|
|
128
180
|
}
|
|
129
181
|
// Check for subqueries in FROM clause (basic check)
|
|
130
|
-
if (cleanQuery.includes(
|
|
131
|
-
return {
|
|
182
|
+
if (cleanQuery.includes("FROM (")) {
|
|
183
|
+
return {
|
|
184
|
+
valid: false,
|
|
185
|
+
error: "Subqueries in FROM clause not allowed without 'execute' permission",
|
|
186
|
+
};
|
|
132
187
|
}
|
|
133
188
|
}
|
|
134
189
|
// Check for comment-based injection attempts
|
|
135
|
-
if (cleanQuery.includes(
|
|
136
|
-
|
|
190
|
+
if (cleanQuery.includes("/*") ||
|
|
191
|
+
cleanQuery.includes("--") ||
|
|
192
|
+
cleanQuery.includes("#")) {
|
|
193
|
+
return { valid: false, error: "Comments not allowed in queries" };
|
|
137
194
|
}
|
|
138
195
|
return { valid: true, queryType };
|
|
139
196
|
}
|
|
@@ -145,7 +202,7 @@ class SecurityLayer {
|
|
|
145
202
|
return { valid: true, sanitizedParams: [] };
|
|
146
203
|
}
|
|
147
204
|
if (!Array.isArray(params)) {
|
|
148
|
-
return { valid: false, error:
|
|
205
|
+
return { valid: false, error: "Parameters must be an array" };
|
|
149
206
|
}
|
|
150
207
|
const sanitizedParams = [];
|
|
151
208
|
for (let i = 0; i < params.length; i++) {
|
|
@@ -156,29 +213,38 @@ class SecurityLayer {
|
|
|
156
213
|
continue;
|
|
157
214
|
}
|
|
158
215
|
// Validate based on type
|
|
159
|
-
if (typeof param ===
|
|
216
|
+
if (typeof param === "string") {
|
|
160
217
|
// Check string length
|
|
161
218
|
if (param.length > 65535) {
|
|
162
|
-
return {
|
|
219
|
+
return {
|
|
220
|
+
valid: false,
|
|
221
|
+
error: `Parameter ${i} too long (max 65535 characters)`,
|
|
222
|
+
};
|
|
163
223
|
}
|
|
164
224
|
// Don't modify strings - let MySQL handle escaping through prepared statements
|
|
165
225
|
sanitizedParams.push(param);
|
|
166
226
|
}
|
|
167
|
-
else if (typeof param ===
|
|
227
|
+
else if (typeof param === "number") {
|
|
168
228
|
// Validate number
|
|
169
229
|
if (!Number.isFinite(param)) {
|
|
170
|
-
return {
|
|
230
|
+
return {
|
|
231
|
+
valid: false,
|
|
232
|
+
error: `Parameter ${i} must be a finite number`,
|
|
233
|
+
};
|
|
171
234
|
}
|
|
172
235
|
sanitizedParams.push(param);
|
|
173
236
|
}
|
|
174
|
-
else if (typeof param ===
|
|
237
|
+
else if (typeof param === "boolean") {
|
|
175
238
|
sanitizedParams.push(param);
|
|
176
239
|
}
|
|
177
240
|
else if (param instanceof Date) {
|
|
178
241
|
sanitizedParams.push(param);
|
|
179
242
|
}
|
|
180
243
|
else {
|
|
181
|
-
return {
|
|
244
|
+
return {
|
|
245
|
+
valid: false,
|
|
246
|
+
error: `Parameter ${i} has unsupported type: ${typeof param}`,
|
|
247
|
+
};
|
|
182
248
|
}
|
|
183
249
|
}
|
|
184
250
|
return { valid: true, sanitizedParams };
|
|
@@ -188,7 +254,7 @@ class SecurityLayer {
|
|
|
188
254
|
*/
|
|
189
255
|
isReadOnlyQuery(query) {
|
|
190
256
|
const validation = this.validateQuery(query);
|
|
191
|
-
return validation.valid && validation.queryType ===
|
|
257
|
+
return validation.valid && validation.queryType === "SELECT";
|
|
192
258
|
}
|
|
193
259
|
/**
|
|
194
260
|
* Check if a query contains dangerous operations
|
|
@@ -197,6 +263,12 @@ class SecurityLayer {
|
|
|
197
263
|
const validation = this.validateQuery(query);
|
|
198
264
|
return !validation.valid;
|
|
199
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Check if execute permission is enabled
|
|
268
|
+
*/
|
|
269
|
+
hasExecutePermission() {
|
|
270
|
+
return this.featureConfig.isCategoryEnabled(featureConfig_js_1.ToolCategory.EXECUTE);
|
|
271
|
+
}
|
|
200
272
|
/**
|
|
201
273
|
* Escape identifier for safe use in SQL queries
|
|
202
274
|
*/
|
package/dist/tools/queryTools.js
CHANGED
|
@@ -18,46 +18,49 @@ class QueryTools {
|
|
|
18
18
|
// Validate input schema
|
|
19
19
|
if (!(0, schemas_1.validateRunQuery)(queryParams)) {
|
|
20
20
|
return {
|
|
21
|
-
status:
|
|
22
|
-
error:
|
|
21
|
+
status: "error",
|
|
22
|
+
error: "Invalid parameters: " + JSON.stringify(schemas_1.validateRunQuery.errors),
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
26
|
const { query, params = [] } = queryParams;
|
|
27
|
+
// Check if user has execute permission to bypass dangerous keyword checks
|
|
28
|
+
const hasExecutePermission = this.security.hasExecutePermission();
|
|
27
29
|
// Validate query using security layer
|
|
28
|
-
|
|
30
|
+
// If user has execute permission, allow advanced SQL features
|
|
31
|
+
const queryValidation = this.security.validateQuery(query, hasExecutePermission);
|
|
29
32
|
if (!queryValidation.valid) {
|
|
30
33
|
return {
|
|
31
|
-
status:
|
|
32
|
-
error: `Query validation failed: ${queryValidation.error}
|
|
34
|
+
status: "error",
|
|
35
|
+
error: `Query validation failed: ${queryValidation.error}`,
|
|
33
36
|
};
|
|
34
37
|
}
|
|
35
38
|
// Ensure it's a SELECT query
|
|
36
|
-
if (queryValidation.queryType !==
|
|
39
|
+
if (queryValidation.queryType !== "SELECT") {
|
|
37
40
|
return {
|
|
38
|
-
status:
|
|
39
|
-
error:
|
|
41
|
+
status: "error",
|
|
42
|
+
error: "Only SELECT queries are allowed with runQuery. Use executeSql for other operations.",
|
|
40
43
|
};
|
|
41
44
|
}
|
|
42
45
|
// Validate parameters
|
|
43
46
|
const paramValidation = this.security.validateParameters(params);
|
|
44
47
|
if (!paramValidation.valid) {
|
|
45
48
|
return {
|
|
46
|
-
status:
|
|
47
|
-
error: `Parameter validation failed: ${paramValidation.error}
|
|
49
|
+
status: "error",
|
|
50
|
+
error: `Parameter validation failed: ${paramValidation.error}`,
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
53
|
// Execute the query with sanitized parameters
|
|
51
54
|
const results = await this.db.query(query, paramValidation.sanitizedParams);
|
|
52
55
|
return {
|
|
53
|
-
status:
|
|
54
|
-
data: results
|
|
56
|
+
status: "success",
|
|
57
|
+
data: results,
|
|
55
58
|
};
|
|
56
59
|
}
|
|
57
60
|
catch (error) {
|
|
58
61
|
return {
|
|
59
|
-
status:
|
|
60
|
-
error: error.message
|
|
62
|
+
status: "error",
|
|
63
|
+
error: error.message,
|
|
61
64
|
};
|
|
62
65
|
}
|
|
63
66
|
}
|
|
@@ -69,49 +72,50 @@ class QueryTools {
|
|
|
69
72
|
// Validate input schema
|
|
70
73
|
if (!(0, schemas_1.validateRunQuery)(queryParams)) {
|
|
71
74
|
return {
|
|
72
|
-
status:
|
|
73
|
-
error:
|
|
75
|
+
status: "error",
|
|
76
|
+
error: "Invalid parameters: " + JSON.stringify(schemas_1.validateRunQuery.errors),
|
|
74
77
|
};
|
|
75
78
|
}
|
|
76
79
|
try {
|
|
77
80
|
const { query, params = [] } = queryParams;
|
|
78
81
|
// Validate query using security layer
|
|
79
|
-
|
|
82
|
+
// Pass true for bypassDangerousCheck since this is executeSql (requires 'execute' permission)
|
|
83
|
+
const queryValidation = this.security.validateQuery(query, true);
|
|
80
84
|
if (!queryValidation.valid) {
|
|
81
85
|
return {
|
|
82
|
-
status:
|
|
83
|
-
error: `Query validation failed: ${queryValidation.error}
|
|
86
|
+
status: "error",
|
|
87
|
+
error: `Query validation failed: ${queryValidation.error}`,
|
|
84
88
|
};
|
|
85
89
|
}
|
|
86
90
|
// Ensure it's not a SELECT query (use runQuery for that)
|
|
87
|
-
if (queryValidation.queryType ===
|
|
91
|
+
if (queryValidation.queryType === "SELECT") {
|
|
88
92
|
return {
|
|
89
|
-
status:
|
|
90
|
-
error:
|
|
93
|
+
status: "error",
|
|
94
|
+
error: "SELECT queries should use runQuery method instead of executeSql.",
|
|
91
95
|
};
|
|
92
96
|
}
|
|
93
97
|
// Validate parameters
|
|
94
98
|
const paramValidation = this.security.validateParameters(params);
|
|
95
99
|
if (!paramValidation.valid) {
|
|
96
100
|
return {
|
|
97
|
-
status:
|
|
98
|
-
error: `Parameter validation failed: ${paramValidation.error}
|
|
101
|
+
status: "error",
|
|
102
|
+
error: `Parameter validation failed: ${paramValidation.error}`,
|
|
99
103
|
};
|
|
100
104
|
}
|
|
101
105
|
// Execute the query with sanitized parameters
|
|
102
106
|
const result = await this.db.query(query, paramValidation.sanitizedParams);
|
|
103
107
|
return {
|
|
104
|
-
status:
|
|
108
|
+
status: "success",
|
|
105
109
|
data: {
|
|
106
110
|
affectedRows: result.affectedRows || 0,
|
|
107
|
-
insertId: result.insertId || null
|
|
108
|
-
}
|
|
111
|
+
insertId: result.insertId || null,
|
|
112
|
+
},
|
|
109
113
|
};
|
|
110
114
|
}
|
|
111
115
|
catch (error) {
|
|
112
116
|
return {
|
|
113
|
-
status:
|
|
114
|
-
error: error.message
|
|
117
|
+
status: "error",
|
|
118
|
+
error: error.message,
|
|
115
119
|
};
|
|
116
120
|
}
|
|
117
121
|
}
|
package/package.json
CHANGED
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@berthojoris/mcp-mysql-server",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"description": "Model Context Protocol server for MySQL database integration with dynamic per-project permissions and data export capabilities",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"mcp-mysql": "./bin/mcp-mysql.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"start": "node dist/server.js",
|
|
13
|
-
"start:mcp": "node dist/mcp-server.js",
|
|
14
|
-
"start:api": "node dist/server.js",
|
|
15
|
-
"dev": "ts-node src/index.ts",
|
|
16
|
-
"dev:mcp": "ts-node src/mcp-server.ts",
|
|
17
|
-
"dev:api": "ts-node src/server.ts",
|
|
18
|
-
"test": "jest",
|
|
19
|
-
"prepare": "npm run build",
|
|
20
|
-
"prepublishOnly": "npm run build"
|
|
21
|
-
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
"mcp",
|
|
24
|
-
"mysql",
|
|
25
|
-
"database",
|
|
26
|
-
"llm",
|
|
27
|
-
"ai",
|
|
28
|
-
"model-context-protocol",
|
|
29
|
-
"claude",
|
|
30
|
-
"cline",
|
|
31
|
-
"windsurf",
|
|
32
|
-
"agent",
|
|
33
|
-
"database-tools",
|
|
34
|
-
"sql",
|
|
35
|
-
"mysql-client",
|
|
36
|
-
"ai-tools"
|
|
37
|
-
],
|
|
38
|
-
"author": "Bertho Joris <berthojoris@gmail.com>",
|
|
39
|
-
"license": "MIT",
|
|
40
|
-
"repository": {
|
|
41
|
-
"type": "git",
|
|
42
|
-
"url": "https://github.com/berthojoris/mysql-mcp.git"
|
|
43
|
-
},
|
|
44
|
-
"bugs": {
|
|
45
|
-
"url": "https://github.com/berthojoris/mysql-mcp/issues"
|
|
46
|
-
},
|
|
47
|
-
"homepage": "https://github.com/berthojoris/mysql-mcp#readme",
|
|
48
|
-
"files": [
|
|
49
|
-
"dist",
|
|
50
|
-
"bin",
|
|
51
|
-
"README.md",
|
|
52
|
-
"CHANGELOG.md",
|
|
53
|
-
"LICENSE",
|
|
54
|
-
"manifest.json"
|
|
55
|
-
],
|
|
56
|
-
"engines": {
|
|
57
|
-
"node": ">=18.0.0"
|
|
58
|
-
},
|
|
59
|
-
"dependencies": {
|
|
60
|
-
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
61
|
-
"ajv": "^8.12.0",
|
|
62
|
-
"cors": "^2.8.5",
|
|
63
|
-
"dotenv": "^16.3.1",
|
|
64
|
-
"express": "^4.18.2",
|
|
65
|
-
"express-rate-limit": "^7.1.5",
|
|
66
|
-
"helmet": "^7.1.0",
|
|
67
|
-
"jsonwebtoken": "^9.0.2",
|
|
68
|
-
"morgan": "^1.10.0",
|
|
69
|
-
"mysql2": "^3.6.1",
|
|
70
|
-
"winston": "^3.11.0"
|
|
71
|
-
},
|
|
72
|
-
"devDependencies": {
|
|
73
|
-
"@types/cors": "^2.8.17",
|
|
74
|
-
"@types/express": "^4.17.21",
|
|
75
|
-
"@types/helmet": "^0.0.48",
|
|
76
|
-
"@types/jest": "^29.5.4",
|
|
77
|
-
"@types/jsonwebtoken": "^9.0.5",
|
|
78
|
-
"@types/morgan": "^1.9.9",
|
|
79
|
-
"@types/node": "^20.6.0",
|
|
80
|
-
"@types/winston": "^2.4.4",
|
|
81
|
-
"jest": "^29.6.4",
|
|
82
|
-
"ts-jest": "^29.1.1",
|
|
83
|
-
"ts-node": "^10.9.1",
|
|
84
|
-
"typescript": "^5.2.2"
|
|
85
|
-
}
|
|
86
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@berthojoris/mcp-mysql-server",
|
|
3
|
+
"version": "1.4.4",
|
|
4
|
+
"description": "Model Context Protocol server for MySQL database integration with dynamic per-project permissions and data export capabilities",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-mysql": "./bin/mcp-mysql.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/server.js",
|
|
13
|
+
"start:mcp": "node dist/mcp-server.js",
|
|
14
|
+
"start:api": "node dist/server.js",
|
|
15
|
+
"dev": "ts-node src/index.ts",
|
|
16
|
+
"dev:mcp": "ts-node src/mcp-server.ts",
|
|
17
|
+
"dev:api": "ts-node src/server.ts",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"mysql",
|
|
25
|
+
"database",
|
|
26
|
+
"llm",
|
|
27
|
+
"ai",
|
|
28
|
+
"model-context-protocol",
|
|
29
|
+
"claude",
|
|
30
|
+
"cline",
|
|
31
|
+
"windsurf",
|
|
32
|
+
"agent",
|
|
33
|
+
"database-tools",
|
|
34
|
+
"sql",
|
|
35
|
+
"mysql-client",
|
|
36
|
+
"ai-tools"
|
|
37
|
+
],
|
|
38
|
+
"author": "Bertho Joris <berthojoris@gmail.com>",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/berthojoris/mysql-mcp.git"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/berthojoris/mysql-mcp/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/berthojoris/mysql-mcp#readme",
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"bin",
|
|
51
|
+
"README.md",
|
|
52
|
+
"CHANGELOG.md",
|
|
53
|
+
"LICENSE",
|
|
54
|
+
"manifest.json"
|
|
55
|
+
],
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18.0.0"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
61
|
+
"ajv": "^8.12.0",
|
|
62
|
+
"cors": "^2.8.5",
|
|
63
|
+
"dotenv": "^16.3.1",
|
|
64
|
+
"express": "^4.18.2",
|
|
65
|
+
"express-rate-limit": "^7.1.5",
|
|
66
|
+
"helmet": "^7.1.0",
|
|
67
|
+
"jsonwebtoken": "^9.0.2",
|
|
68
|
+
"morgan": "^1.10.0",
|
|
69
|
+
"mysql2": "^3.6.1",
|
|
70
|
+
"winston": "^3.11.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/cors": "^2.8.17",
|
|
74
|
+
"@types/express": "^4.17.21",
|
|
75
|
+
"@types/helmet": "^0.0.48",
|
|
76
|
+
"@types/jest": "^29.5.4",
|
|
77
|
+
"@types/jsonwebtoken": "^9.0.5",
|
|
78
|
+
"@types/morgan": "^1.9.9",
|
|
79
|
+
"@types/node": "^20.6.0",
|
|
80
|
+
"@types/winston": "^2.4.4",
|
|
81
|
+
"jest": "^29.6.4",
|
|
82
|
+
"ts-jest": "^29.1.1",
|
|
83
|
+
"ts-node": "^10.9.1",
|
|
84
|
+
"typescript": "^5.2.2"
|
|
85
|
+
}
|
|
86
|
+
}
|