@arvoretech/mysql-mcp 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/README.md +182 -0
- package/dist/database.d.ts +15 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +125 -0
- package/dist/database.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +106 -0
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +11 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +153 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +27 -0
- package/dist/types.js.map +1 -0
- package/eslint.config.js +54 -0
- package/package.json +42 -0
- package/src/database.test.ts +298 -0
- package/src/database.ts +163 -0
- package/src/index.ts +26 -0
- package/src/server.ts +157 -0
- package/src/tools.test.ts +228 -0
- package/src/tools.ts +212 -0
- package/src/types.ts +71 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const MySQLConfigSchema: z.ZodObject<{
|
|
3
|
+
host: z.ZodString;
|
|
4
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
5
|
+
user: z.ZodString;
|
|
6
|
+
password: z.ZodString;
|
|
7
|
+
database: z.ZodString;
|
|
8
|
+
ssl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
9
|
+
connectionTimeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
host: string;
|
|
12
|
+
port: number;
|
|
13
|
+
user: string;
|
|
14
|
+
password: string;
|
|
15
|
+
database: string;
|
|
16
|
+
ssl: boolean;
|
|
17
|
+
connectionTimeout: number;
|
|
18
|
+
}, {
|
|
19
|
+
host: string;
|
|
20
|
+
user: string;
|
|
21
|
+
password: string;
|
|
22
|
+
database: string;
|
|
23
|
+
port?: number | undefined;
|
|
24
|
+
ssl?: boolean | undefined;
|
|
25
|
+
connectionTimeout?: number | undefined;
|
|
26
|
+
}>;
|
|
27
|
+
export type MySQLConfig = z.output<typeof MySQLConfigSchema>;
|
|
28
|
+
export type MySQLConfigInput = z.input<typeof MySQLConfigSchema>;
|
|
29
|
+
export declare const ReadQueryParamsSchema: z.ZodObject<{
|
|
30
|
+
query: z.ZodString;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
query: string;
|
|
33
|
+
}, {
|
|
34
|
+
query: string;
|
|
35
|
+
}>;
|
|
36
|
+
export type ReadQueryParams = z.infer<typeof ReadQueryParamsSchema>;
|
|
37
|
+
export declare const DescribeTableParamsSchema: z.ZodObject<{
|
|
38
|
+
tableName: z.ZodString;
|
|
39
|
+
}, "strip", z.ZodTypeAny, {
|
|
40
|
+
tableName: string;
|
|
41
|
+
}, {
|
|
42
|
+
tableName: string;
|
|
43
|
+
}>;
|
|
44
|
+
export type DescribeTableParams = z.infer<typeof DescribeTableParamsSchema>;
|
|
45
|
+
export interface TableInfo {
|
|
46
|
+
TABLE_NAME: string;
|
|
47
|
+
TABLE_TYPE: string;
|
|
48
|
+
TABLE_SCHEMA: string;
|
|
49
|
+
}
|
|
50
|
+
export interface ColumnInfo {
|
|
51
|
+
COLUMN_NAME: string;
|
|
52
|
+
DATA_TYPE: string;
|
|
53
|
+
IS_NULLABLE: string;
|
|
54
|
+
COLUMN_DEFAULT: string | null;
|
|
55
|
+
COLUMN_KEY: string;
|
|
56
|
+
EXTRA: string;
|
|
57
|
+
COLUMN_COMMENT: string;
|
|
58
|
+
}
|
|
59
|
+
export interface DatabaseInfo {
|
|
60
|
+
Database: string;
|
|
61
|
+
}
|
|
62
|
+
export interface QueryResult {
|
|
63
|
+
data: Record<string, unknown>[];
|
|
64
|
+
rowCount: number;
|
|
65
|
+
executionTime: number;
|
|
66
|
+
}
|
|
67
|
+
export interface McpToolResult {
|
|
68
|
+
[x: string]: unknown;
|
|
69
|
+
content: Array<{
|
|
70
|
+
type: "text";
|
|
71
|
+
text: string;
|
|
72
|
+
}>;
|
|
73
|
+
}
|
|
74
|
+
export declare class MySQLMCPError extends Error {
|
|
75
|
+
readonly code?: string | undefined;
|
|
76
|
+
readonly sqlState?: string | undefined;
|
|
77
|
+
constructor(message: string, code?: string | undefined, sqlState?: string | undefined);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;EAQ5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC7D,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEjE,eAAO,MAAM,qBAAqB;;;;;;EAEhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,yBAAyB;;;;;;EAEpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACrB,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,qBAAa,aAAc,SAAQ,KAAK;aAGpB,IAAI,CAAC,EAAE,MAAM;aACb,QAAQ,CAAC,EAAE,MAAM;gBAFjC,OAAO,EAAE,MAAM,EACC,IAAI,CAAC,EAAE,MAAM,YAAA,EACb,QAAQ,CAAC,EAAE,MAAM,YAAA;CAKpC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const MySQLConfigSchema = z.object({
|
|
3
|
+
host: z.string(),
|
|
4
|
+
port: z.number().min(1).max(65535).default(3306),
|
|
5
|
+
user: z.string(),
|
|
6
|
+
password: z.string(),
|
|
7
|
+
database: z.string(),
|
|
8
|
+
ssl: z.boolean().optional().default(false),
|
|
9
|
+
connectionTimeout: z.number().optional().default(30000),
|
|
10
|
+
});
|
|
11
|
+
export const ReadQueryParamsSchema = z.object({
|
|
12
|
+
query: z.string().min(1),
|
|
13
|
+
});
|
|
14
|
+
export const DescribeTableParamsSchema = z.object({
|
|
15
|
+
tableName: z.string().min(1),
|
|
16
|
+
});
|
|
17
|
+
export class MySQLMCPError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
sqlState;
|
|
20
|
+
constructor(message, code, sqlState) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.sqlState = sqlState;
|
|
24
|
+
this.name = "MySQLMCPError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAChD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC1C,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CACxD,CAAC,CAAC;AAKH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACzB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC7B,CAAC,CAAC;AAsCH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAGpB;IACA;IAHlB,YACE,OAAe,EACC,IAAa,EACb,QAAiB;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAS;QACb,aAAQ,GAAR,QAAQ,CAAS;QAGjC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF"}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import eslint from "@eslint/js";
|
|
2
|
+
import tseslint from "@typescript-eslint/eslint-plugin";
|
|
3
|
+
import tsparser from "@typescript-eslint/parser";
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
eslint.configs.recommended,
|
|
7
|
+
{
|
|
8
|
+
files: ["src/**/*.ts"],
|
|
9
|
+
ignores: ["**/*.test.ts"],
|
|
10
|
+
languageOptions: {
|
|
11
|
+
parser: tsparser,
|
|
12
|
+
parserOptions: {
|
|
13
|
+
ecmaVersion: "latest",
|
|
14
|
+
sourceType: "module",
|
|
15
|
+
project: "./tsconfig.json",
|
|
16
|
+
},
|
|
17
|
+
globals: {
|
|
18
|
+
console: "readonly",
|
|
19
|
+
process: "readonly",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
plugins: {
|
|
23
|
+
"@typescript-eslint": tseslint,
|
|
24
|
+
},
|
|
25
|
+
rules: {
|
|
26
|
+
...tseslint.configs.recommended.rules,
|
|
27
|
+
"@typescript-eslint/no-unused-vars": "error",
|
|
28
|
+
"@typescript-eslint/explicit-function-return-type": "warn",
|
|
29
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
files: ["src/**/*.test.ts"],
|
|
34
|
+
languageOptions: {
|
|
35
|
+
parser: tsparser,
|
|
36
|
+
parserOptions: {
|
|
37
|
+
ecmaVersion: "latest",
|
|
38
|
+
sourceType: "module",
|
|
39
|
+
},
|
|
40
|
+
globals: {
|
|
41
|
+
console: "readonly",
|
|
42
|
+
process: "readonly",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
plugins: {
|
|
46
|
+
"@typescript-eslint": tseslint,
|
|
47
|
+
},
|
|
48
|
+
rules: {
|
|
49
|
+
...tseslint.configs.recommended.rules,
|
|
50
|
+
"@typescript-eslint/no-unused-vars": "error",
|
|
51
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arvoretech/mysql-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MySQL MCP Server for read-only database operations",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"mysql-mcp": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"test": "vitest",
|
|
18
|
+
"test:cov": "vitest --coverage",
|
|
19
|
+
"lint": "eslint src/**/*.ts",
|
|
20
|
+
"lint:fix": "eslint src/**/*.ts --fix"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"mysql",
|
|
25
|
+
"database",
|
|
26
|
+
"llm",
|
|
27
|
+
"ai",
|
|
28
|
+
"arvore"
|
|
29
|
+
],
|
|
30
|
+
"author": "Arvore",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/arvoreeducacao/arvore-mcp-servers.git",
|
|
35
|
+
"directory": "packages/mysql"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
|
+
"mysql2": "^3.6.5",
|
|
40
|
+
"zod": "^3.22.4"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { MySQLConnection } from "./database.js";
|
|
3
|
+
import { MySQLMCPError } from "./types.js";
|
|
4
|
+
import mysql from "mysql2/promise";
|
|
5
|
+
|
|
6
|
+
vi.mock("mysql2/promise", () => ({
|
|
7
|
+
default: {
|
|
8
|
+
createConnection: vi.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe("MySQLConnection", () => {
|
|
13
|
+
let connection: MySQLConnection;
|
|
14
|
+
let mockConnection: {
|
|
15
|
+
execute: ReturnType<typeof vi.fn>;
|
|
16
|
+
end: ReturnType<typeof vi.fn>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
mockConnection = {
|
|
22
|
+
execute: vi.fn(),
|
|
23
|
+
end: vi.fn(),
|
|
24
|
+
};
|
|
25
|
+
(mysql.createConnection as ReturnType<typeof vi.fn>).mockResolvedValue(
|
|
26
|
+
mockConnection
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
connection = new MySQLConnection({
|
|
30
|
+
host: "localhost",
|
|
31
|
+
port: 3306,
|
|
32
|
+
user: "testuser",
|
|
33
|
+
password: "testpass",
|
|
34
|
+
database: "testdb",
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("connect", () => {
|
|
39
|
+
it("should successfully connect to MySQL", async () => {
|
|
40
|
+
await expect(connection.connect()).resolves.toBeUndefined();
|
|
41
|
+
expect(mysql.createConnection).toHaveBeenCalledWith(
|
|
42
|
+
expect.objectContaining({
|
|
43
|
+
host: "localhost",
|
|
44
|
+
port: 3306,
|
|
45
|
+
user: "testuser",
|
|
46
|
+
password: "testpass",
|
|
47
|
+
database: "testdb",
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
expect(mockConnection.end).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should throw MySQLMCPError on connection failure", async () => {
|
|
54
|
+
(mysql.createConnection as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
55
|
+
new Error("Connection failed")
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await expect(connection.connect()).rejects.toThrow(MySQLMCPError);
|
|
59
|
+
await expect(connection.connect()).rejects.toThrow(
|
|
60
|
+
"Failed to connect to MySQL"
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should use SSL when configured", async () => {
|
|
65
|
+
const sslConnection = new MySQLConnection({
|
|
66
|
+
host: "localhost",
|
|
67
|
+
port: 3306,
|
|
68
|
+
user: "testuser",
|
|
69
|
+
password: "testpass",
|
|
70
|
+
database: "testdb",
|
|
71
|
+
ssl: true,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await sslConnection.connect();
|
|
75
|
+
|
|
76
|
+
expect(mysql.createConnection).toHaveBeenCalledWith(
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
ssl: {},
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("executeQuery", () => {
|
|
85
|
+
it("should execute SELECT query successfully", async () => {
|
|
86
|
+
const mockRows = [
|
|
87
|
+
{ id: 1, name: "John" },
|
|
88
|
+
{ id: 2, name: "Jane" },
|
|
89
|
+
];
|
|
90
|
+
mockConnection.execute.mockResolvedValue([mockRows, []]);
|
|
91
|
+
|
|
92
|
+
const result = await connection.executeQuery("SELECT * FROM users");
|
|
93
|
+
|
|
94
|
+
expect(result.data).toEqual(mockRows);
|
|
95
|
+
expect(result.rowCount).toBe(2);
|
|
96
|
+
expect(result.executionTime).toBeGreaterThanOrEqual(0);
|
|
97
|
+
expect(mockConnection.end).toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should allow SHOW queries", async () => {
|
|
101
|
+
mockConnection.execute.mockResolvedValue([[], []]);
|
|
102
|
+
|
|
103
|
+
await expect(
|
|
104
|
+
connection.executeQuery("SHOW TABLES")
|
|
105
|
+
).resolves.toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should allow DESCRIBE queries", async () => {
|
|
109
|
+
mockConnection.execute.mockResolvedValue([[], []]);
|
|
110
|
+
|
|
111
|
+
await expect(
|
|
112
|
+
connection.executeQuery("DESCRIBE users")
|
|
113
|
+
).resolves.toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should allow EXPLAIN queries", async () => {
|
|
117
|
+
mockConnection.execute.mockResolvedValue([[], []]);
|
|
118
|
+
|
|
119
|
+
await expect(
|
|
120
|
+
connection.executeQuery("EXPLAIN SELECT * FROM users")
|
|
121
|
+
).resolves.toBeDefined();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should reject INSERT queries", async () => {
|
|
125
|
+
await expect(
|
|
126
|
+
connection.executeQuery("INSERT INTO users VALUES (1, 'Test')")
|
|
127
|
+
).rejects.toThrow(MySQLMCPError);
|
|
128
|
+
await expect(
|
|
129
|
+
connection.executeQuery("INSERT INTO users VALUES (1, 'Test')")
|
|
130
|
+
).rejects.toThrow("Only read-only queries are allowed");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should reject UPDATE queries", async () => {
|
|
134
|
+
await expect(
|
|
135
|
+
connection.executeQuery("UPDATE users SET name='Test' WHERE id=1")
|
|
136
|
+
).rejects.toThrow(MySQLMCPError);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should reject DELETE queries", async () => {
|
|
140
|
+
await expect(
|
|
141
|
+
connection.executeQuery("DELETE FROM users WHERE id=1")
|
|
142
|
+
).rejects.toThrow(MySQLMCPError);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should reject DROP queries", async () => {
|
|
146
|
+
await expect(connection.executeQuery("DROP TABLE users")).rejects.toThrow(
|
|
147
|
+
MySQLMCPError
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should reject CREATE queries", async () => {
|
|
152
|
+
await expect(
|
|
153
|
+
connection.executeQuery("CREATE TABLE test (id INT)")
|
|
154
|
+
).rejects.toThrow(MySQLMCPError);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should throw MySQLMCPError on query execution failure", async () => {
|
|
158
|
+
const mockError = {
|
|
159
|
+
message: "Syntax error",
|
|
160
|
+
code: "ER_PARSE_ERROR",
|
|
161
|
+
sqlState: "42000",
|
|
162
|
+
};
|
|
163
|
+
mockConnection.execute.mockRejectedValue(mockError);
|
|
164
|
+
|
|
165
|
+
await expect(
|
|
166
|
+
connection.executeQuery("SELECT * FROM invalid")
|
|
167
|
+
).rejects.toThrow(MySQLMCPError);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should close connection even if query fails", async () => {
|
|
171
|
+
mockConnection.execute.mockRejectedValue(new Error("Query failed"));
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
await connection.executeQuery("SELECT * FROM users");
|
|
175
|
+
} catch (error) {
|
|
176
|
+
expect(error).toBeDefined();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
expect(mockConnection.end).toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("listTables", () => {
|
|
184
|
+
it("should list all tables in database", async () => {
|
|
185
|
+
const mockTables = [
|
|
186
|
+
{
|
|
187
|
+
TABLE_NAME: "users",
|
|
188
|
+
TABLE_TYPE: "BASE TABLE",
|
|
189
|
+
TABLE_SCHEMA: "testdb",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
TABLE_NAME: "posts",
|
|
193
|
+
TABLE_TYPE: "BASE TABLE",
|
|
194
|
+
TABLE_SCHEMA: "testdb",
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
mockConnection.execute.mockResolvedValue([mockTables, []]);
|
|
198
|
+
|
|
199
|
+
const result = await connection.listTables();
|
|
200
|
+
|
|
201
|
+
expect(result).toHaveLength(2);
|
|
202
|
+
expect(result[0].TABLE_NAME).toBe("users");
|
|
203
|
+
expect(result[1].TABLE_NAME).toBe("posts");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should return empty array when no tables exist", async () => {
|
|
207
|
+
mockConnection.execute.mockResolvedValue([[], []]);
|
|
208
|
+
|
|
209
|
+
const result = await connection.listTables();
|
|
210
|
+
|
|
211
|
+
expect(result).toEqual([]);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("describeTable", () => {
|
|
216
|
+
it("should describe table structure", async () => {
|
|
217
|
+
const mockColumns = [
|
|
218
|
+
{
|
|
219
|
+
COLUMN_NAME: "id",
|
|
220
|
+
DATA_TYPE: "int",
|
|
221
|
+
IS_NULLABLE: "NO",
|
|
222
|
+
COLUMN_DEFAULT: null,
|
|
223
|
+
COLUMN_KEY: "PRI",
|
|
224
|
+
EXTRA: "auto_increment",
|
|
225
|
+
COLUMN_COMMENT: "",
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
COLUMN_NAME: "name",
|
|
229
|
+
DATA_TYPE: "varchar",
|
|
230
|
+
IS_NULLABLE: "YES",
|
|
231
|
+
COLUMN_DEFAULT: null,
|
|
232
|
+
COLUMN_KEY: "",
|
|
233
|
+
EXTRA: "",
|
|
234
|
+
COLUMN_COMMENT: "User name",
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
mockConnection.execute.mockResolvedValue([mockColumns, []]);
|
|
238
|
+
|
|
239
|
+
const result = await connection.describeTable("users");
|
|
240
|
+
|
|
241
|
+
expect(result).toHaveLength(2);
|
|
242
|
+
expect(result[0].COLUMN_NAME).toBe("id");
|
|
243
|
+
expect(result[0].COLUMN_KEY).toBe("PRI");
|
|
244
|
+
expect(result[1].COLUMN_NAME).toBe("name");
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe("showDatabases", () => {
|
|
249
|
+
it("should list all databases", async () => {
|
|
250
|
+
const mockDatabases = [
|
|
251
|
+
{ Database: "testdb" },
|
|
252
|
+
{ Database: "production" },
|
|
253
|
+
];
|
|
254
|
+
mockConnection.execute.mockResolvedValue([mockDatabases, []]);
|
|
255
|
+
|
|
256
|
+
const result = await connection.showDatabases();
|
|
257
|
+
|
|
258
|
+
expect(result).toHaveLength(2);
|
|
259
|
+
expect(result[0].Database).toBe("testdb");
|
|
260
|
+
expect(result[1].Database).toBe("production");
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe("testConnection", () => {
|
|
265
|
+
it("should return true on successful connection test", async () => {
|
|
266
|
+
mockConnection.execute.mockResolvedValue([[{ test: 1 }], []]);
|
|
267
|
+
|
|
268
|
+
const result = await connection.testConnection();
|
|
269
|
+
|
|
270
|
+
expect(result).toBe(true);
|
|
271
|
+
expect(mockConnection.end).toHaveBeenCalled();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should return false on connection test failure", async () => {
|
|
275
|
+
(mysql.createConnection as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
276
|
+
new Error("Connection failed")
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const result = await connection.testConnection();
|
|
280
|
+
|
|
281
|
+
expect(result).toBe(false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should close connection even if test fails", async () => {
|
|
285
|
+
mockConnection.execute.mockRejectedValue(new Error("Test failed"));
|
|
286
|
+
|
|
287
|
+
await connection.testConnection();
|
|
288
|
+
|
|
289
|
+
expect(mockConnection.end).toHaveBeenCalled();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe("disconnect", () => {
|
|
294
|
+
it("should be a no-op", async () => {
|
|
295
|
+
await expect(connection.disconnect()).resolves.toBeUndefined();
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|
package/src/database.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
|
+
import {
|
|
3
|
+
MySQLConfig,
|
|
4
|
+
MySQLConfigInput,
|
|
5
|
+
MySQLConfigSchema,
|
|
6
|
+
QueryResult,
|
|
7
|
+
TableInfo,
|
|
8
|
+
ColumnInfo,
|
|
9
|
+
DatabaseInfo,
|
|
10
|
+
MySQLMCPError,
|
|
11
|
+
} from "./types.js";
|
|
12
|
+
|
|
13
|
+
export class MySQLConnection {
|
|
14
|
+
private readonly config: MySQLConfig;
|
|
15
|
+
|
|
16
|
+
constructor(config: MySQLConfigInput) {
|
|
17
|
+
this.config = MySQLConfigSchema.parse(config);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private async createConnection(): Promise<mysql.Connection> {
|
|
21
|
+
try {
|
|
22
|
+
const connectionOptions: mysql.ConnectionOptions = {
|
|
23
|
+
host: this.config.host,
|
|
24
|
+
port: this.config.port,
|
|
25
|
+
user: this.config.user,
|
|
26
|
+
password: this.config.password,
|
|
27
|
+
database: this.config.database,
|
|
28
|
+
connectTimeout: this.config.connectionTimeout,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (this.config.ssl) {
|
|
32
|
+
connectionOptions.ssl = {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return await mysql.createConnection(connectionOptions);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new MySQLMCPError(
|
|
38
|
+
`Failed to connect to MySQL: ${
|
|
39
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
40
|
+
}`,
|
|
41
|
+
"CONNECTION_ERROR"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async connect(): Promise<void> {
|
|
47
|
+
const connection = await this.createConnection();
|
|
48
|
+
await connection.end();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async disconnect(): Promise<void> {
|
|
52
|
+
// No-op: connections are managed per-query
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private validateReadOnlyQuery(query: string): void {
|
|
56
|
+
const trimmedQuery = query.trim().toLowerCase();
|
|
57
|
+
const readOnlyPatterns = [
|
|
58
|
+
/^select\s/,
|
|
59
|
+
/^show\s/,
|
|
60
|
+
/^describe\s/,
|
|
61
|
+
/^desc\s/,
|
|
62
|
+
/^explain\s/,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const isReadOnly = readOnlyPatterns.some((pattern) =>
|
|
66
|
+
pattern.test(trimmedQuery)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (!isReadOnly) {
|
|
70
|
+
throw new MySQLMCPError(
|
|
71
|
+
"Only read-only queries are allowed (SELECT, SHOW, DESCRIBE, EXPLAIN)",
|
|
72
|
+
"WRITE_OPERATION_NOT_ALLOWED"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async executeQuery(query: string): Promise<QueryResult> {
|
|
78
|
+
this.validateReadOnlyQuery(query);
|
|
79
|
+
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
let connection: mysql.Connection | null = null;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
connection = await this.createConnection();
|
|
85
|
+
const [rows] = await connection.execute(query);
|
|
86
|
+
const executionTime = Date.now() - startTime;
|
|
87
|
+
|
|
88
|
+
const data = Array.isArray(rows)
|
|
89
|
+
? (rows as Record<string, unknown>[])
|
|
90
|
+
: [];
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
data,
|
|
94
|
+
rowCount: data.length,
|
|
95
|
+
executionTime,
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
const mysqlError = error as mysql.QueryError;
|
|
99
|
+
throw new MySQLMCPError(
|
|
100
|
+
`Query execution failed: ${mysqlError.message}`,
|
|
101
|
+
mysqlError.code,
|
|
102
|
+
mysqlError.sqlState
|
|
103
|
+
);
|
|
104
|
+
} finally {
|
|
105
|
+
if (connection) {
|
|
106
|
+
await connection.end();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async listTables(): Promise<TableInfo[]> {
|
|
112
|
+
const result = await this.executeQuery(`
|
|
113
|
+
SELECT
|
|
114
|
+
TABLE_NAME,
|
|
115
|
+
TABLE_TYPE,
|
|
116
|
+
TABLE_SCHEMA
|
|
117
|
+
FROM information_schema.TABLES
|
|
118
|
+
WHERE TABLE_SCHEMA = '${this.config.database}'
|
|
119
|
+
ORDER BY TABLE_NAME
|
|
120
|
+
`);
|
|
121
|
+
|
|
122
|
+
return result.data as unknown as TableInfo[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async describeTable(tableName: string): Promise<ColumnInfo[]> {
|
|
126
|
+
const result = await this.executeQuery(`
|
|
127
|
+
SELECT
|
|
128
|
+
COLUMN_NAME,
|
|
129
|
+
DATA_TYPE,
|
|
130
|
+
IS_NULLABLE,
|
|
131
|
+
COLUMN_DEFAULT,
|
|
132
|
+
COLUMN_KEY,
|
|
133
|
+
EXTRA,
|
|
134
|
+
COLUMN_COMMENT
|
|
135
|
+
FROM information_schema.COLUMNS
|
|
136
|
+
WHERE TABLE_SCHEMA = '${this.config.database}'
|
|
137
|
+
AND TABLE_NAME = '${tableName}'
|
|
138
|
+
ORDER BY ORDINAL_POSITION
|
|
139
|
+
`);
|
|
140
|
+
|
|
141
|
+
return result.data as unknown as ColumnInfo[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async showDatabases(): Promise<DatabaseInfo[]> {
|
|
145
|
+
const result = await this.executeQuery("SHOW DATABASES");
|
|
146
|
+
return result.data as unknown as DatabaseInfo[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async testConnection(): Promise<boolean> {
|
|
150
|
+
let connection: mysql.Connection | null = null;
|
|
151
|
+
try {
|
|
152
|
+
connection = await this.createConnection();
|
|
153
|
+
await connection.execute("SELECT 1 as test");
|
|
154
|
+
return true;
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
} finally {
|
|
158
|
+
if (connection) {
|
|
159
|
+
await connection.end();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|