@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.
@@ -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;