@berthojoris/mcp-mysql-server 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 +21 -0
- package/README.md +1142 -0
- package/bin/mcp-mysql.js +122 -0
- package/dist/auth/authService.d.ts +29 -0
- package/dist/auth/authService.js +114 -0
- package/dist/config/config.d.ts +12 -0
- package/dist/config/config.js +19 -0
- package/dist/config/featureConfig.d.ts +51 -0
- package/dist/config/featureConfig.js +130 -0
- package/dist/db/connection.d.ts +22 -0
- package/dist/db/connection.js +135 -0
- package/dist/index.d.ts +236 -0
- package/dist/index.js +273 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +748 -0
- package/dist/security/securityLayer.d.ts +52 -0
- package/dist/security/securityLayer.js +213 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +283 -0
- package/dist/tools/crudTools.d.ts +59 -0
- package/dist/tools/crudTools.js +443 -0
- package/dist/tools/databaseTools.d.ts +33 -0
- package/dist/tools/databaseTools.js +108 -0
- package/dist/tools/ddlTools.d.ts +69 -0
- package/dist/tools/ddlTools.js +199 -0
- package/dist/tools/queryTools.d.ts +29 -0
- package/dist/tools/queryTools.js +119 -0
- package/dist/tools/storedProcedureTools.d.ts +80 -0
- package/dist/tools/storedProcedureTools.js +411 -0
- package/dist/tools/transactionTools.d.ts +45 -0
- package/dist/tools/transactionTools.js +130 -0
- package/dist/tools/utilityTools.d.ts +30 -0
- package/dist/tools/utilityTools.js +121 -0
- package/dist/validation/schemas.d.ts +423 -0
- package/dist/validation/schemas.js +287 -0
- package/manifest.json +248 -0
- package/package.json +83 -0
package/bin/mcp-mysql.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP MySQL Server CLI
|
|
5
|
+
* This script allows the MySQL MCP server to be run via NPX
|
|
6
|
+
* It implements the Model Context Protocol using stdio transport
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { spawn } = require('child_process');
|
|
11
|
+
require('dotenv').config();
|
|
12
|
+
|
|
13
|
+
// Get MySQL connection string and optional permissions from command line arguments
|
|
14
|
+
const mysqlUrl = process.argv[2];
|
|
15
|
+
const permissions = process.argv[3]; // Optional: comma-separated list of permissions
|
|
16
|
+
|
|
17
|
+
if (!mysqlUrl) {
|
|
18
|
+
console.error('Error: MySQL connection URL is required');
|
|
19
|
+
console.error('Usage: mcp-mysql mysql://user:password@host:port/dbname [permissions]');
|
|
20
|
+
console.error('');
|
|
21
|
+
console.error('Examples:');
|
|
22
|
+
console.error(' mcp-mysql mysql://root:pass@localhost:3306/mydb');
|
|
23
|
+
console.error(' mcp-mysql mysql://root:pass@localhost:3306/mydb "list,read,utility"');
|
|
24
|
+
console.error(' mcp-mysql mysql://root:pass@localhost:3306/mydb "list,read,create,update,delete,utility"');
|
|
25
|
+
console.error('');
|
|
26
|
+
console.error('Available permissions: list, read, create, update, delete, execute, utility');
|
|
27
|
+
console.error('If not specified, all permissions are enabled.');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Parse the MySQL URL to extract connection details
|
|
32
|
+
let connectionConfig;
|
|
33
|
+
let database;
|
|
34
|
+
try {
|
|
35
|
+
const url = new URL(mysqlUrl);
|
|
36
|
+
|
|
37
|
+
// Remove leading slash from pathname and make database optional
|
|
38
|
+
database = url.pathname.replace(/^\//, '') || null;
|
|
39
|
+
|
|
40
|
+
// Extract username and password from auth
|
|
41
|
+
const auth = url.username && url.password
|
|
42
|
+
? { user: url.username, password: url.password }
|
|
43
|
+
: { user: url.username || 'root', password: url.password || '' };
|
|
44
|
+
|
|
45
|
+
connectionConfig = {
|
|
46
|
+
host: url.hostname,
|
|
47
|
+
port: url.port || 3306,
|
|
48
|
+
...auth,
|
|
49
|
+
...(database ? { database } : {})
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Set environment variables for the server
|
|
53
|
+
process.env.DB_HOST = connectionConfig.host;
|
|
54
|
+
process.env.DB_PORT = connectionConfig.port;
|
|
55
|
+
process.env.DB_USER = connectionConfig.user;
|
|
56
|
+
process.env.DB_PASSWORD = connectionConfig.password;
|
|
57
|
+
if (database) {
|
|
58
|
+
process.env.DB_NAME = database;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error parsing MySQL URL:', error.message);
|
|
63
|
+
console.error('Usage: npx @modelcontextprotocol/server-mysql mysql://user:password@host:port/dbname');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const dbMessage = database
|
|
68
|
+
? `${connectionConfig.host}:${connectionConfig.port}/${database}`
|
|
69
|
+
: `${connectionConfig.host}:${connectionConfig.port} (no specific database selected)`;
|
|
70
|
+
|
|
71
|
+
// Set permissions as environment variable if provided
|
|
72
|
+
if (permissions) {
|
|
73
|
+
process.env.MCP_PERMISSIONS = permissions;
|
|
74
|
+
console.error(`Permissions: ${permissions}`);
|
|
75
|
+
} else {
|
|
76
|
+
console.error('Permissions: all (default)');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Log to stderr (not stdout, which is used for MCP protocol)
|
|
80
|
+
console.error(`Starting MySQL MCP server with connection to ${dbMessage}`);
|
|
81
|
+
|
|
82
|
+
// Run the MCP server
|
|
83
|
+
try {
|
|
84
|
+
// Determine the path to the compiled MCP server file
|
|
85
|
+
const serverPath = path.resolve(__dirname, '../dist/mcp-server.js');
|
|
86
|
+
|
|
87
|
+
// Spawn the MCP server process with stdio transport
|
|
88
|
+
// stdin/stdout are used for MCP protocol communication
|
|
89
|
+
// stderr is used for logging
|
|
90
|
+
const server = spawn('node', [serverPath], {
|
|
91
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
92
|
+
env: process.env
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Handle server process events
|
|
96
|
+
server.on('error', (err) => {
|
|
97
|
+
console.error('Failed to start MCP server:', err);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
server.on('exit', (code) => {
|
|
102
|
+
if (code !== 0 && code !== null) {
|
|
103
|
+
console.error(`MCP server exited with code ${code}`);
|
|
104
|
+
process.exit(code);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Handle termination signals
|
|
109
|
+
process.on('SIGINT', () => {
|
|
110
|
+
console.error('Shutting down MySQL MCP server...');
|
|
111
|
+
server.kill('SIGINT');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
process.on('SIGTERM', () => {
|
|
115
|
+
console.error('Shutting down MySQL MCP server...');
|
|
116
|
+
server.kill('SIGTERM');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('Error starting MCP server:', error.message);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
export interface User {
|
|
3
|
+
id: string;
|
|
4
|
+
username: string;
|
|
5
|
+
role: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class AuthService {
|
|
8
|
+
/**
|
|
9
|
+
* Generate a JWT token for a user
|
|
10
|
+
*/
|
|
11
|
+
generateToken(user: User): string;
|
|
12
|
+
/**
|
|
13
|
+
* Verify a JWT token
|
|
14
|
+
*/
|
|
15
|
+
verifyToken(token: string): User;
|
|
16
|
+
/**
|
|
17
|
+
* Middleware to authenticate using JWT
|
|
18
|
+
*/
|
|
19
|
+
authenticateJWT(req: Request, res: Response, next: NextFunction): void;
|
|
20
|
+
/**
|
|
21
|
+
* Middleware to authenticate using API key
|
|
22
|
+
*/
|
|
23
|
+
authenticateApiKey(req: Request, res: Response, next: NextFunction): void;
|
|
24
|
+
/**
|
|
25
|
+
* Login endpoint handler
|
|
26
|
+
*/
|
|
27
|
+
login(req: Request, res: Response): void;
|
|
28
|
+
}
|
|
29
|
+
export declare const authService: AuthService;
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
exports.authService = exports.AuthService = void 0;
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
// Environment variables
|
|
9
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'default_secret_change_in_production';
|
|
10
|
+
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';
|
|
11
|
+
const API_KEY = process.env.API_KEY;
|
|
12
|
+
class AuthService {
|
|
13
|
+
/**
|
|
14
|
+
* Generate a JWT token for a user
|
|
15
|
+
*/
|
|
16
|
+
generateToken(user) {
|
|
17
|
+
// Cast to any to bypass TypeScript's strict type checking for JWT
|
|
18
|
+
return jsonwebtoken_1.default.sign(user, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Verify a JWT token
|
|
22
|
+
*/
|
|
23
|
+
verifyToken(token) {
|
|
24
|
+
// Cast to any to bypass TypeScript's strict type checking for JWT
|
|
25
|
+
return jsonwebtoken_1.default.verify(token, JWT_SECRET);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Middleware to authenticate using JWT
|
|
29
|
+
*/
|
|
30
|
+
authenticateJWT(req, res, next) {
|
|
31
|
+
const authHeader = req.headers['authorization'];
|
|
32
|
+
const token = authHeader && authHeader.split(' ')[1];
|
|
33
|
+
if (!token) {
|
|
34
|
+
res.status(401).json({
|
|
35
|
+
error: {
|
|
36
|
+
code: 'AUTH_TOKEN_MISSING',
|
|
37
|
+
message: 'Authentication token is required',
|
|
38
|
+
details: 'Please provide a valid JWT token in the Authorization header'
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const user = this.verifyToken(token);
|
|
45
|
+
req.user = user;
|
|
46
|
+
next();
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
res.status(403).json({
|
|
50
|
+
error: {
|
|
51
|
+
code: 'AUTH_TOKEN_INVALID',
|
|
52
|
+
message: 'Invalid or expired token',
|
|
53
|
+
details: 'Please provide a valid JWT token'
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Middleware to authenticate using API key
|
|
60
|
+
*/
|
|
61
|
+
authenticateApiKey(req, res, next) {
|
|
62
|
+
const providedApiKey = req.headers['x-api-key'];
|
|
63
|
+
if (!API_KEY) {
|
|
64
|
+
console.warn('API_KEY environment variable is not set');
|
|
65
|
+
res.status(500).json({
|
|
66
|
+
error: {
|
|
67
|
+
code: 'SERVER_CONFIG_ERROR',
|
|
68
|
+
message: 'Server configuration error',
|
|
69
|
+
details: 'API key authentication is not properly configured'
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!providedApiKey || providedApiKey !== API_KEY) {
|
|
75
|
+
res.status(401).json({
|
|
76
|
+
error: {
|
|
77
|
+
code: 'INVALID_API_KEY',
|
|
78
|
+
message: 'Invalid API key',
|
|
79
|
+
details: 'Please provide a valid API key in the X-API-Key header'
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
next();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Login endpoint handler
|
|
88
|
+
*/
|
|
89
|
+
login(req, res) {
|
|
90
|
+
const { username, password } = req.body;
|
|
91
|
+
// In a real application, you would validate credentials against a database
|
|
92
|
+
// This is a simplified example for demonstration purposes
|
|
93
|
+
if (username === 'admin' && password === 'password') {
|
|
94
|
+
const user = {
|
|
95
|
+
id: '1',
|
|
96
|
+
username: 'admin',
|
|
97
|
+
role: 'admin'
|
|
98
|
+
};
|
|
99
|
+
const token = this.generateToken(user);
|
|
100
|
+
res.json({ token });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
res.status(401).json({
|
|
104
|
+
error: {
|
|
105
|
+
code: 'INVALID_CREDENTIALS',
|
|
106
|
+
message: 'Invalid username or password',
|
|
107
|
+
details: 'Please check your credentials and try again'
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.AuthService = AuthService;
|
|
114
|
+
exports.authService = new AuthService();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface DatabaseConfig {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
user: string;
|
|
5
|
+
password: string;
|
|
6
|
+
database: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const dbConfig: DatabaseConfig;
|
|
9
|
+
declare const _default: {
|
|
10
|
+
database: DatabaseConfig;
|
|
11
|
+
};
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
exports.dbConfig = void 0;
|
|
7
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
|
+
// Load environment variables from .env file
|
|
9
|
+
dotenv_1.default.config();
|
|
10
|
+
exports.dbConfig = {
|
|
11
|
+
host: process.env.DB_HOST || 'localhost',
|
|
12
|
+
port: parseInt(process.env.DB_PORT || '3306', 10),
|
|
13
|
+
user: process.env.DB_USER || 'root',
|
|
14
|
+
password: process.env.DB_PASSWORD || '',
|
|
15
|
+
database: process.env.DB_NAME || 'mysql',
|
|
16
|
+
};
|
|
17
|
+
exports.default = {
|
|
18
|
+
database: exports.dbConfig,
|
|
19
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Available MCP tool categories
|
|
3
|
+
*/
|
|
4
|
+
export declare enum ToolCategory {
|
|
5
|
+
LIST = "list",// List databases, tables, etc.
|
|
6
|
+
READ = "read",// Read/select data from tables
|
|
7
|
+
CREATE = "create",// Insert new records
|
|
8
|
+
UPDATE = "update",// Update existing records
|
|
9
|
+
DELETE = "delete",// Delete records
|
|
10
|
+
EXECUTE = "execute",// Execute custom SQL
|
|
11
|
+
DDL = "ddl",// Data Definition Language (CREATE/ALTER/DROP tables)
|
|
12
|
+
UTILITY = "utility",// Utility functions like connection testing
|
|
13
|
+
TRANSACTION = "transaction",// Transaction management (BEGIN, COMMIT, ROLLBACK)
|
|
14
|
+
PROCEDURE = "procedure"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Map of tool names to their categories
|
|
18
|
+
*/
|
|
19
|
+
export declare const toolCategoryMap: Record<string, ToolCategory>;
|
|
20
|
+
/**
|
|
21
|
+
* Class to manage feature configuration based on runtime or environment variables
|
|
22
|
+
*/
|
|
23
|
+
export declare class FeatureConfig {
|
|
24
|
+
private enabledCategories;
|
|
25
|
+
constructor(configStr?: string);
|
|
26
|
+
/**
|
|
27
|
+
* Parse MCP_CONFIG from provided string or environment variables
|
|
28
|
+
*/
|
|
29
|
+
private parseConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Update configuration at runtime
|
|
32
|
+
*/
|
|
33
|
+
setConfig(configStr: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a specific tool is enabled
|
|
36
|
+
*/
|
|
37
|
+
isToolEnabled(toolName: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Check if a category is enabled
|
|
40
|
+
*/
|
|
41
|
+
isCategoryEnabled(category: ToolCategory): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Get all enabled categories
|
|
44
|
+
*/
|
|
45
|
+
getEnabledCategories(): ToolCategory[];
|
|
46
|
+
/**
|
|
47
|
+
* Get all available categories with their status
|
|
48
|
+
*/
|
|
49
|
+
getCategoryStatus(): Record<ToolCategory, boolean>;
|
|
50
|
+
}
|
|
51
|
+
export declare const featureConfig: FeatureConfig;
|
|
@@ -0,0 +1,130 @@
|
|
|
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
|
+
exports.featureConfig = exports.FeatureConfig = exports.toolCategoryMap = exports.ToolCategory = void 0;
|
|
7
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
|
+
// Load environment variables
|
|
9
|
+
dotenv_1.default.config();
|
|
10
|
+
/**
|
|
11
|
+
* Available MCP tool categories
|
|
12
|
+
*/
|
|
13
|
+
var ToolCategory;
|
|
14
|
+
(function (ToolCategory) {
|
|
15
|
+
ToolCategory["LIST"] = "list";
|
|
16
|
+
ToolCategory["READ"] = "read";
|
|
17
|
+
ToolCategory["CREATE"] = "create";
|
|
18
|
+
ToolCategory["UPDATE"] = "update";
|
|
19
|
+
ToolCategory["DELETE"] = "delete";
|
|
20
|
+
ToolCategory["EXECUTE"] = "execute";
|
|
21
|
+
ToolCategory["DDL"] = "ddl";
|
|
22
|
+
ToolCategory["UTILITY"] = "utility";
|
|
23
|
+
ToolCategory["TRANSACTION"] = "transaction";
|
|
24
|
+
ToolCategory["PROCEDURE"] = "procedure"; // Stored procedure operations (CREATE, EXECUTE, DROP procedures)
|
|
25
|
+
})(ToolCategory || (exports.ToolCategory = ToolCategory = {}));
|
|
26
|
+
/**
|
|
27
|
+
* Map of tool names to their categories
|
|
28
|
+
*/
|
|
29
|
+
exports.toolCategoryMap = {
|
|
30
|
+
// Database tools
|
|
31
|
+
'listDatabases': ToolCategory.LIST,
|
|
32
|
+
'listTables': ToolCategory.LIST,
|
|
33
|
+
'readTableSchema': ToolCategory.LIST,
|
|
34
|
+
// CRUD tools
|
|
35
|
+
'createRecord': ToolCategory.CREATE,
|
|
36
|
+
'readRecords': ToolCategory.READ,
|
|
37
|
+
'updateRecord': ToolCategory.UPDATE,
|
|
38
|
+
'deleteRecord': ToolCategory.DELETE,
|
|
39
|
+
// Query tools
|
|
40
|
+
'runQuery': ToolCategory.READ,
|
|
41
|
+
'executeSql': ToolCategory.EXECUTE,
|
|
42
|
+
// DDL tools
|
|
43
|
+
'createTable': ToolCategory.DDL,
|
|
44
|
+
'alterTable': ToolCategory.DDL,
|
|
45
|
+
'dropTable': ToolCategory.DDL,
|
|
46
|
+
'executeDdl': ToolCategory.DDL,
|
|
47
|
+
// Utility tools
|
|
48
|
+
'describeConnection': ToolCategory.UTILITY,
|
|
49
|
+
'testConnection': ToolCategory.UTILITY,
|
|
50
|
+
'getTableRelationships': ToolCategory.UTILITY,
|
|
51
|
+
// Transaction tools
|
|
52
|
+
'beginTransaction': ToolCategory.TRANSACTION,
|
|
53
|
+
'commitTransaction': ToolCategory.TRANSACTION,
|
|
54
|
+
'rollbackTransaction': ToolCategory.TRANSACTION,
|
|
55
|
+
'getTransactionStatus': ToolCategory.TRANSACTION,
|
|
56
|
+
'executeInTransaction': ToolCategory.TRANSACTION,
|
|
57
|
+
// Stored procedure tools
|
|
58
|
+
'listStoredProcedures': ToolCategory.LIST,
|
|
59
|
+
'getStoredProcedureInfo': ToolCategory.LIST,
|
|
60
|
+
'executeStoredProcedure': ToolCategory.PROCEDURE,
|
|
61
|
+
'createStoredProcedure': ToolCategory.PROCEDURE,
|
|
62
|
+
'dropStoredProcedure': ToolCategory.PROCEDURE,
|
|
63
|
+
'showCreateProcedure': ToolCategory.LIST
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Class to manage feature configuration based on runtime or environment variables
|
|
67
|
+
*/
|
|
68
|
+
class FeatureConfig {
|
|
69
|
+
constructor(configStr) {
|
|
70
|
+
this.enabledCategories = this.parseConfig(configStr);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse MCP_CONFIG from provided string or environment variables
|
|
74
|
+
*/
|
|
75
|
+
parseConfig(configStr) {
|
|
76
|
+
// Priority: 1. Provided config, 2. Environment variable, 3. Enable all
|
|
77
|
+
const config = configStr || process.env.MCP_CONFIG || '';
|
|
78
|
+
// If config is empty, enable all features
|
|
79
|
+
if (!config.trim()) {
|
|
80
|
+
return new Set(Object.values(ToolCategory));
|
|
81
|
+
}
|
|
82
|
+
// Parse comma-separated list
|
|
83
|
+
const categories = config.split(',').map(c => c.trim().toLowerCase());
|
|
84
|
+
const validCategories = categories.filter(c => Object.values(ToolCategory).includes(c));
|
|
85
|
+
return new Set(validCategories);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Update configuration at runtime
|
|
89
|
+
*/
|
|
90
|
+
setConfig(configStr) {
|
|
91
|
+
this.enabledCategories = this.parseConfig(configStr);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if a specific tool is enabled
|
|
95
|
+
*/
|
|
96
|
+
isToolEnabled(toolName) {
|
|
97
|
+
const category = exports.toolCategoryMap[toolName];
|
|
98
|
+
// If tool is not in the map, default to disabled
|
|
99
|
+
if (!category) {
|
|
100
|
+
console.warn(`Unknown tool: ${toolName}`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return this.enabledCategories.has(category);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if a category is enabled
|
|
107
|
+
*/
|
|
108
|
+
isCategoryEnabled(category) {
|
|
109
|
+
return this.enabledCategories.has(category);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get all enabled categories
|
|
113
|
+
*/
|
|
114
|
+
getEnabledCategories() {
|
|
115
|
+
return Array.from(this.enabledCategories);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get all available categories with their status
|
|
119
|
+
*/
|
|
120
|
+
getCategoryStatus() {
|
|
121
|
+
const result = {};
|
|
122
|
+
for (const category of Object.values(ToolCategory)) {
|
|
123
|
+
result[category] = this.enabledCategories.has(category);
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.FeatureConfig = FeatureConfig;
|
|
129
|
+
// Export singleton instance
|
|
130
|
+
exports.featureConfig = new FeatureConfig();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
declare class DatabaseConnection {
|
|
3
|
+
private static instance;
|
|
4
|
+
private pool;
|
|
5
|
+
private activeTransactions;
|
|
6
|
+
private constructor();
|
|
7
|
+
static getInstance(): DatabaseConnection;
|
|
8
|
+
getConnection(): Promise<mysql.PoolConnection>;
|
|
9
|
+
query<T>(sql: string, params?: any[]): Promise<T>;
|
|
10
|
+
testConnection(): Promise<{
|
|
11
|
+
connected: boolean;
|
|
12
|
+
latency: number;
|
|
13
|
+
}>;
|
|
14
|
+
closePool(): Promise<void>;
|
|
15
|
+
beginTransaction(transactionId: string): Promise<void>;
|
|
16
|
+
commitTransaction(transactionId: string): Promise<void>;
|
|
17
|
+
rollbackTransaction(transactionId: string): Promise<void>;
|
|
18
|
+
getActiveTransactionIds(): string[];
|
|
19
|
+
hasActiveTransaction(transactionId: string): boolean;
|
|
20
|
+
executeInTransaction<T>(transactionId: string, sql: string, params?: any[]): Promise<T>;
|
|
21
|
+
}
|
|
22
|
+
export default DatabaseConnection;
|
|
@@ -0,0 +1,135 @@
|
|
|
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 promise_1 = __importDefault(require("mysql2/promise"));
|
|
7
|
+
const config_1 = require("../config/config");
|
|
8
|
+
class DatabaseConnection {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.pool = promise_1.default.createPool({
|
|
11
|
+
host: config_1.dbConfig.host,
|
|
12
|
+
port: config_1.dbConfig.port,
|
|
13
|
+
user: config_1.dbConfig.user,
|
|
14
|
+
password: config_1.dbConfig.password,
|
|
15
|
+
database: config_1.dbConfig.database,
|
|
16
|
+
waitForConnections: true,
|
|
17
|
+
connectionLimit: 10,
|
|
18
|
+
queueLimit: 0
|
|
19
|
+
});
|
|
20
|
+
this.activeTransactions = new Map();
|
|
21
|
+
}
|
|
22
|
+
static getInstance() {
|
|
23
|
+
if (!DatabaseConnection.instance) {
|
|
24
|
+
DatabaseConnection.instance = new DatabaseConnection();
|
|
25
|
+
}
|
|
26
|
+
return DatabaseConnection.instance;
|
|
27
|
+
}
|
|
28
|
+
async getConnection() {
|
|
29
|
+
try {
|
|
30
|
+
return await this.pool.getConnection();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new Error(`Failed to get database connection: ${error}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async query(sql, params) {
|
|
37
|
+
try {
|
|
38
|
+
const [results] = await this.pool.query(sql, params);
|
|
39
|
+
return results;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new Error(`Query execution failed: ${error}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async testConnection() {
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
try {
|
|
48
|
+
const connection = await this.getConnection();
|
|
49
|
+
connection.release();
|
|
50
|
+
const endTime = Date.now();
|
|
51
|
+
return { connected: true, latency: endTime - startTime };
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return { connected: false, latency: -1 };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async closePool() {
|
|
58
|
+
try {
|
|
59
|
+
await this.pool.end();
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new Error(`Failed to close connection pool: ${error}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Transaction Management Methods
|
|
66
|
+
async beginTransaction(transactionId) {
|
|
67
|
+
try {
|
|
68
|
+
const connection = await this.getConnection();
|
|
69
|
+
await connection.beginTransaction();
|
|
70
|
+
this.activeTransactions.set(transactionId, connection);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new Error(`Failed to begin transaction: ${error}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async commitTransaction(transactionId) {
|
|
77
|
+
const connection = this.activeTransactions.get(transactionId);
|
|
78
|
+
if (!connection) {
|
|
79
|
+
throw new Error(`No active transaction found with ID: ${transactionId}`);
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
await connection.commit();
|
|
83
|
+
connection.release();
|
|
84
|
+
this.activeTransactions.delete(transactionId);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// If commit fails, rollback and release connection
|
|
88
|
+
try {
|
|
89
|
+
await connection.rollback();
|
|
90
|
+
connection.release();
|
|
91
|
+
}
|
|
92
|
+
catch (rollbackError) {
|
|
93
|
+
console.error('Failed to rollback after commit error:', rollbackError);
|
|
94
|
+
}
|
|
95
|
+
this.activeTransactions.delete(transactionId);
|
|
96
|
+
throw new Error(`Failed to commit transaction: ${error}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async rollbackTransaction(transactionId) {
|
|
100
|
+
const connection = this.activeTransactions.get(transactionId);
|
|
101
|
+
if (!connection) {
|
|
102
|
+
throw new Error(`No active transaction found with ID: ${transactionId}`);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
await connection.rollback();
|
|
106
|
+
connection.release();
|
|
107
|
+
this.activeTransactions.delete(transactionId);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
connection.release();
|
|
111
|
+
this.activeTransactions.delete(transactionId);
|
|
112
|
+
throw new Error(`Failed to rollback transaction: ${error}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
getActiveTransactionIds() {
|
|
116
|
+
return Array.from(this.activeTransactions.keys());
|
|
117
|
+
}
|
|
118
|
+
hasActiveTransaction(transactionId) {
|
|
119
|
+
return this.activeTransactions.has(transactionId);
|
|
120
|
+
}
|
|
121
|
+
async executeInTransaction(transactionId, sql, params) {
|
|
122
|
+
const connection = this.activeTransactions.get(transactionId);
|
|
123
|
+
if (!connection) {
|
|
124
|
+
throw new Error(`No active transaction found with ID: ${transactionId}`);
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const [results] = await connection.query(sql, params);
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
throw new Error(`Query execution in transaction failed: ${error}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.default = DatabaseConnection;
|