@praise25/mcp-server-mysql 1.0.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/LICENSE.md +21 -0
- package/README.md +730 -0
- package/assets/demo.gif +0 -0
- package/dist/evals.js +32 -0
- package/dist/index.js +405 -0
- package/dist/src/config/index.js +51 -0
- package/dist/src/db/index.js +142 -0
- package/dist/src/db/permissions.js +34 -0
- package/dist/src/db/utils.js +116 -0
- package/dist/src/types/index.js +1 -0
- package/dist/src/utils/index.js +29 -0
- package/package.json +85 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { isMultiDbMode } from "./../config/index.js";
|
|
2
|
+
import { log } from "./../utils/index.js";
|
|
3
|
+
import SqlParser from "node-sql-parser";
|
|
4
|
+
const { Parser } = SqlParser;
|
|
5
|
+
const parser = new Parser();
|
|
6
|
+
function calculateComplexity(astNode) {
|
|
7
|
+
let complexity = 0;
|
|
8
|
+
if (!astNode || typeof astNode !== 'object') {
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
switch (astNode.type) {
|
|
12
|
+
case 'select':
|
|
13
|
+
complexity += 1;
|
|
14
|
+
if (astNode.from) {
|
|
15
|
+
astNode.from.forEach((from) => {
|
|
16
|
+
if (from.expr && from.expr.type === 'select') {
|
|
17
|
+
complexity += 10;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
break;
|
|
22
|
+
case 'join':
|
|
23
|
+
complexity += 5;
|
|
24
|
+
break;
|
|
25
|
+
case 'union':
|
|
26
|
+
complexity += 10;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
if (astNode.columns === '*') {
|
|
30
|
+
complexity += 2;
|
|
31
|
+
}
|
|
32
|
+
for (const key in astNode) {
|
|
33
|
+
if (Array.isArray(astNode[key])) {
|
|
34
|
+
astNode[key].forEach((item) => {
|
|
35
|
+
complexity += calculateComplexity(item);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else if (typeof astNode[key] === 'object') {
|
|
39
|
+
complexity += calculateComplexity(astNode[key]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return complexity;
|
|
43
|
+
}
|
|
44
|
+
function extractSchemasFromAST(ast, schemas) {
|
|
45
|
+
if (!ast || typeof ast !== 'object') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (ast.db && typeof ast.db === 'string') {
|
|
49
|
+
schemas.add(ast.db);
|
|
50
|
+
}
|
|
51
|
+
for (const key in ast) {
|
|
52
|
+
if (Array.isArray(ast[key])) {
|
|
53
|
+
ast[key].forEach((item) => extractSchemasFromAST(item, schemas));
|
|
54
|
+
}
|
|
55
|
+
else if (typeof ast[key] === 'object') {
|
|
56
|
+
extractSchemasFromAST(ast[key], schemas);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function getAST(sql) {
|
|
61
|
+
try {
|
|
62
|
+
return parser.astify(sql, { database: "mysql" });
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
log("error", "Error parsing SQL for AST:", err);
|
|
66
|
+
throw new Error(`Parsing failed: ${err.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function getAllSchemasFromQuery(sql, astOrArray) {
|
|
70
|
+
const schemas = new Set();
|
|
71
|
+
const defaultSchema = process.env.MYSQL_DB || null;
|
|
72
|
+
try {
|
|
73
|
+
const statements = Array.isArray(astOrArray) ? astOrArray : [astOrArray];
|
|
74
|
+
for (const ast of statements) {
|
|
75
|
+
extractSchemasFromAST(ast, schemas);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
log("error", "Error parsing SQL for schema extraction:", err);
|
|
80
|
+
const schema = extractSchemaFromQuery(sql);
|
|
81
|
+
if (schema) {
|
|
82
|
+
schemas.add(schema);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (schemas.size === 0 && defaultSchema && !isMultiDbMode) {
|
|
86
|
+
schemas.add(defaultSchema);
|
|
87
|
+
}
|
|
88
|
+
return Array.from(schemas);
|
|
89
|
+
}
|
|
90
|
+
function extractSchemaFromQuery(sql) {
|
|
91
|
+
const defaultSchema = process.env.MYSQL_DB || null;
|
|
92
|
+
if (defaultSchema && !isMultiDbMode) {
|
|
93
|
+
return defaultSchema;
|
|
94
|
+
}
|
|
95
|
+
const useMatch = sql.match(/USE\s+`?([a-zA-Z0-9_]+)`?/i);
|
|
96
|
+
if (useMatch && useMatch[1]) {
|
|
97
|
+
return useMatch[1];
|
|
98
|
+
}
|
|
99
|
+
const dbTableMatch = sql.match(/`?([a-zA-Z0-9_]+)`?\.`?[a-zA-Z0-9_]+`?/i);
|
|
100
|
+
if (dbTableMatch && dbTableMatch[1]) {
|
|
101
|
+
return dbTableMatch[1];
|
|
102
|
+
}
|
|
103
|
+
return defaultSchema;
|
|
104
|
+
}
|
|
105
|
+
async function getQueryTypes(astOrArray) {
|
|
106
|
+
try {
|
|
107
|
+
const statements = Array.isArray(astOrArray) ? astOrArray : [astOrArray];
|
|
108
|
+
return statements.map((stmt) => stmt.type?.toLowerCase() ?? "unknown");
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
log("error", "sqlParser error, ast: ", astOrArray);
|
|
112
|
+
log("error", "Error getting query types from AST:", err);
|
|
113
|
+
throw new Error(`Getting query types failed: ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export { getAllSchemasFromQuery, getQueryTypes, extractSchemaFromQuery, getAST, calculateComplexity };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const ENABLE_LOGGING = process.env.ENABLE_LOGGING === "true" || process.env.ENABLE_LOGGING === "1";
|
|
2
|
+
export function log(type = "info", ...args) {
|
|
3
|
+
if (!ENABLE_LOGGING)
|
|
4
|
+
return;
|
|
5
|
+
switch (type) {
|
|
6
|
+
case "info":
|
|
7
|
+
console.info(...args);
|
|
8
|
+
break;
|
|
9
|
+
case "error":
|
|
10
|
+
console.error(...args);
|
|
11
|
+
break;
|
|
12
|
+
default:
|
|
13
|
+
console.log(...args);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function parseSchemaPermissions(permissionsString) {
|
|
17
|
+
const permissions = {};
|
|
18
|
+
if (!permissionsString) {
|
|
19
|
+
return permissions;
|
|
20
|
+
}
|
|
21
|
+
const permissionPairs = permissionsString.split(",");
|
|
22
|
+
for (const pair of permissionPairs) {
|
|
23
|
+
const [schema, value] = pair.split(":");
|
|
24
|
+
if (schema && value) {
|
|
25
|
+
permissions[schema.trim()] = value.trim() === "true";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return permissions;
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@praise25/mcp-server-mysql",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for interacting with MySQL databases based on Node",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SteveFunso",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/SteveFunso/mcp-server-mysql.git"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "dist/index.js",
|
|
12
|
+
"module": "index.ts",
|
|
13
|
+
"preferGlobal": true,
|
|
14
|
+
"bin": {
|
|
15
|
+
"mcp-server-mysql": "dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"assets"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"start": "node dist/index.js",
|
|
24
|
+
"dev": "ts-node index.ts",
|
|
25
|
+
"build": "tsc && shx chmod +x dist/*.js",
|
|
26
|
+
"prepare": "npm run build",
|
|
27
|
+
"watch": "tsc --watch",
|
|
28
|
+
"setup:test:db": "tsx scripts/setup-test-db.ts",
|
|
29
|
+
"pretest": "pnpm run setup:test:db",
|
|
30
|
+
"test": "pnpm run setup:test:db && vitest run",
|
|
31
|
+
"test:socket": "pnpm run setup:test:db && vitest run tests/integration/socket-connection.test.ts",
|
|
32
|
+
"test:watch": "pnpm run setup:test:db && vitest",
|
|
33
|
+
"test:coverage": "vitest run --coverage",
|
|
34
|
+
"test:unit": "vitest run --config vitest.unit.config.ts",
|
|
35
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
36
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
37
|
+
"stdio": "node dist/index.js --stdio",
|
|
38
|
+
"exec": " pnpm build && npx node --env-file=.env dist/index.js",
|
|
39
|
+
"lint": "eslint ."
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@ai-sdk/openai": "^1.3.22",
|
|
43
|
+
"@modelcontextprotocol/sdk": "1.15.1",
|
|
44
|
+
"dotenv": "^16.5.0",
|
|
45
|
+
"express": "^5.1.0",
|
|
46
|
+
"helmet": "^7.1.0",
|
|
47
|
+
"mcp-evals": "^1.0.18",
|
|
48
|
+
"mysql2": "^3.14.1",
|
|
49
|
+
"node-sql-parser": "^5.3.9",
|
|
50
|
+
"zod": "^3.25.67"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/express": "^5.0.3",
|
|
54
|
+
"@types/jest": "^29.5.14",
|
|
55
|
+
"@types/node": "^20.17.50",
|
|
56
|
+
"@typescript-eslint/eslint-plugin": "^8.35.0",
|
|
57
|
+
"@typescript-eslint/parser": "^8.35.0",
|
|
58
|
+
"eslint": "^9.27.0",
|
|
59
|
+
"shx": "^0.3.4",
|
|
60
|
+
"ts-node": "^10.9.2",
|
|
61
|
+
"tslib": "^2.8.1",
|
|
62
|
+
"tsx": "^4.19.4",
|
|
63
|
+
"typescript": "^5.8.3",
|
|
64
|
+
"vitest": "^1.6.1"
|
|
65
|
+
},
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public"
|
|
68
|
+
},
|
|
69
|
+
"keywords": [
|
|
70
|
+
"node",
|
|
71
|
+
"mcp",
|
|
72
|
+
"ai",
|
|
73
|
+
"cursor",
|
|
74
|
+
"mcp-server",
|
|
75
|
+
"modelcontextprotocol",
|
|
76
|
+
"smithery",
|
|
77
|
+
"mcp-get",
|
|
78
|
+
"mcp-put",
|
|
79
|
+
"mcp-post",
|
|
80
|
+
"mcp-delete",
|
|
81
|
+
"mcp-patch",
|
|
82
|
+
"mcp-options",
|
|
83
|
+
"mcp-head"
|
|
84
|
+
]
|
|
85
|
+
}
|