diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70c7966 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.DS_Store +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +logs/ +*.log +coverage/ +.tmp/ +dist/ +build/ \ No newline at end of file diff --git a/README.md b/README.md index eabb892..49bd613 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,285 @@ -# skills-copilot-codespaces-vscode -My clone repository +# MySQL MCP Servers + +A comprehensive set of 3 Model Context Protocol (MCP) servers for monitoring and managing MySQL database services. These servers provide tools, resources, and prompts for process monitoring, service management, and performance analysis. + +## ๐Ÿš€ Overview + +This project includes three specialized MCP servers: + +1. **MySQL Process Monitor** - Monitor MySQL processes and system resources +2. **MySQL Service Manager** - Manage MySQL services (start/stop/restart/reload) +3. **MySQL Performance Monitor** - Monitor MySQL performance metrics and analytics + +## ๐Ÿ“ Project Structure + +``` +mysql-mcp-servers/ +โ”œโ”€โ”€ servers/ +โ”‚ โ”œโ”€โ”€ mysql-process-monitor/ # Process monitoring MCP server +โ”‚ โ”œโ”€โ”€ mysql-service-manager/ # Service management MCP server +โ”‚ โ””โ”€โ”€ mysql-performance-monitor/ # Performance monitoring MCP server +โ”œโ”€โ”€ config/ +โ”‚ โ”œโ”€โ”€ mcp-config.json # MCP server configuration +โ”‚ โ””โ”€โ”€ mysql-config.json # MySQL connection configuration +โ”œโ”€โ”€ index.js # Main server manager +โ”œโ”€โ”€ package.json # Project dependencies +โ””โ”€โ”€ README.md # This file +``` + +## ๐Ÿ›  Installation + +1. **Clone the repository:** + ```bash + git clone + cd skills-copilot-codespaces-vscode + ``` + +2. **Install dependencies:** + ```bash + npm install + ``` + +3. **Configure MySQL connection (optional):** + Edit `config/mysql-config.json` to match your MySQL setup: + ```json + { + "mysql": { + "host": "localhost", + "port": 3306, + "user": "root", + "password": "your-password" + } + } + ``` + +## ๐ŸŽฏ Usage + +### Server Manager + +Use the main server manager to control all MCP servers: + +```bash +# Start all servers +node index.js start + +# Start specific server +node index.js start mysql-process-monitor + +# Stop all servers +node index.js stop + +# Restart specific server +node index.js restart mysql-service-manager + +# Check server status +node index.js status +``` + +### Individual Servers + +You can also run servers individually: + +```bash +# Process Monitor +npm run start:process-monitor + +# Service Manager +npm run start:service-manager + +# Performance Monitor +npm run start:performance-monitor +``` + +## ๐Ÿ“Š MCP Server Details + +### 1. MySQL Process Monitor + +**Purpose:** Monitor MySQL processes and system resources + +**Tools:** +- `check_mysql_process` - Check if MySQL process is running +- `get_mysql_pid` - Get MySQL process ID and details +- `mysql_process_stats` - Get detailed process statistics +- `kill_mysql_process` - Forcefully kill MySQL process + +**Resources:** +- `mysql://process/status` - Current process status +- `mysql://process/config` - Process configuration + +**Prompts:** +- `mysql_health_check` - Comprehensive health check +- `mysql_troubleshoot` - Process troubleshooting guide + +### 2. MySQL Service Manager + +**Purpose:** Manage MySQL services (systemctl operations) + +**Tools:** +- `mysql_service_status` - Check service status +- `start_mysql_service` - Start MySQL service +- `stop_mysql_service` - Stop MySQL service +- `restart_mysql_service` - Restart MySQL service +- `reload_mysql_service` - Reload service configuration +- `enable_mysql_service` - Enable service on boot +- `disable_mysql_service` - Disable service on boot + +**Resources:** +- `mysql://service/status` - Service status information +- `mysql://service/logs` - Recent service logs +- `mysql://service/config` - Service configuration + +**Prompts:** +- `mysql_service_management` - Service management guidance +- `mysql_service_troubleshoot` - Service troubleshooting +- `mysql_service_maintenance` - Maintenance procedures + +### 3. MySQL Performance Monitor + +**Purpose:** Monitor MySQL performance metrics and analytics + +**Tools:** +- `mysql_performance_snapshot` - Get current performance metrics +- `mysql_process_list` - Get current process list +- `mysql_slow_queries` - Get slow query information +- `mysql_connection_stats` - Get connection statistics +- `mysql_innodb_status` - Get InnoDB engine status +- `mysql_system_metrics` - Get system-level metrics +- `start_performance_monitoring` - Start continuous monitoring +- `stop_performance_monitoring` - Stop continuous monitoring + +**Resources:** +- `mysql://performance/current` - Real-time performance data +- `mysql://performance/history` - Historical performance data +- `mysql://performance/analysis` - Performance analysis and recommendations + +**Prompts:** +- `mysql_performance_analysis` - Comprehensive performance analysis +- `mysql_optimization_recommendations` - Optimization recommendations +- `mysql_troubleshoot_performance` - Performance troubleshooting + +## ๐Ÿ”ง Configuration + +### MCP Server Configuration (`config/mcp-config.json`) + +```json +{ + "mcpServers": { + "mysql-process-monitor": { + "command": "node", + "args": ["servers/mysql-process-monitor/index.js"], + "env": { + "NODE_ENV": "production" + } + } + } +} +``` + +### MySQL Configuration (`config/mysql-config.json`) + +```json +{ + "mysql": { + "host": "localhost", + "port": 3306, + "user": "root", + "password": "", + "connectTimeout": 5000 + }, + "monitoring": { + "performanceHistorySize": 100, + "defaultMonitoringInterval": 60 + }, + "security": { + "enableSudo": true, + "allowServiceManagement": true, + "allowProcessKill": true + } +} +``` + +## ๐Ÿ”’ Security Considerations + +- **Sudo Access:** Service management operations require sudo privileges +- **MySQL Credentials:** Store MySQL credentials securely +- **Process Control:** Be cautious with process kill operations +- **Network Access:** Ensure proper firewall configuration + +## ๐Ÿ“ Examples + +### Monitoring MySQL Process +```javascript +// Check if MySQL is running +const result = await callTool("check_mysql_process", {}); + +// Get detailed process statistics +const stats = await callTool("mysql_process_stats", {}); +``` + +### Managing MySQL Service +```javascript +// Restart MySQL service +const result = await callTool("restart_mysql_service", { + service_name: "mysql" +}); + +// Check service status +const status = await callTool("mysql_service_status", {}); +``` + +### Performance Monitoring +```javascript +// Get performance snapshot +const metrics = await callTool("mysql_performance_snapshot", { + host: "localhost", + user: "root", + password: "password" +}); + +// Start continuous monitoring +const monitoring = await callTool("start_performance_monitoring", { + interval: 30 +}); +``` + +## ๐Ÿ› Troubleshooting + +### Common Issues + +1. **MySQL Connection Failed** + - Check MySQL credentials in config + - Verify MySQL server is running + - Check network connectivity + +2. **Permission Denied (Service Management)** + - Ensure user has sudo privileges + - Check systemctl access + +3. **Process Not Found** + - Verify MySQL is installed + - Check if MySQL is running under different name + +### Logs and Debugging + +- Server logs are output to stderr +- Use `node index.js status` to check server status +- Individual servers can be run directly for debugging + +## ๐Ÿค Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests if applicable +5. Submit a pull request + +## ๐Ÿ“„ License + +MIT License - see LICENSE file for details + +## ๐Ÿ†˜ Support + +For support and questions: +- Create an issue in the repository +- Check the troubleshooting section +- Review the MCP documentation diff --git a/config/mcp-config.json b/config/mcp-config.json new file mode 100644 index 0000000..1c2732c --- /dev/null +++ b/config/mcp-config.json @@ -0,0 +1,25 @@ +{ + "mcpServers": { + "mysql-process-monitor": { + "command": "node", + "args": ["servers/mysql-process-monitor/index.js"], + "env": { + "NODE_ENV": "production" + } + }, + "mysql-service-manager": { + "command": "node", + "args": ["servers/mysql-service-manager/index.js"], + "env": { + "NODE_ENV": "production" + } + }, + "mysql-performance-monitor": { + "command": "node", + "args": ["servers/mysql-performance-monitor/index.js"], + "env": { + "NODE_ENV": "production" + } + } + } +} \ No newline at end of file diff --git a/config/mysql-config.json b/config/mysql-config.json new file mode 100644 index 0000000..b44ae86 --- /dev/null +++ b/config/mysql-config.json @@ -0,0 +1,26 @@ +{ + "mysql": { + "host": "localhost", + "port": 3306, + "user": "root", + "password": "", + "database": "mysql", + "connectTimeout": 5000, + "acquireTimeout": 5000 + }, + "monitoring": { + "performanceHistorySize": 100, + "defaultMonitoringInterval": 60, + "enableSystemMetrics": true + }, + "logging": { + "level": "info", + "enableConsole": true, + "enableFile": false + }, + "security": { + "enableSudo": true, + "allowServiceManagement": true, + "allowProcessKill": true + } +} \ No newline at end of file diff --git a/index.js b/index.js new file mode 100755 index 0000000..0242a49 --- /dev/null +++ b/index.js @@ -0,0 +1,203 @@ +#!/usr/bin/env node + +import { spawn } from 'child_process'; +import { readFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +class MCPServerManager { + constructor() { + this.servers = new Map(); + this.configPath = join(__dirname, 'config', 'mcp-config.json'); + } + + loadConfig() { + try { + const configData = readFileSync(this.configPath, 'utf8'); + return JSON.parse(configData); + } catch (error) { + console.error(`Error loading config from ${this.configPath}:`, error.message); + process.exit(1); + } + } + + startServer(name, config) { + if (this.servers.has(name)) { + console.log(`Server ${name} is already running`); + return; + } + + console.log(`Starting MCP server: ${name}`); + + const serverProcess = spawn(config.command, config.args, { + cwd: __dirname, + env: { ...process.env, ...config.env }, + stdio: ['pipe', 'pipe', 'pipe'] + }); + + serverProcess.stdout.on('data', (data) => { + console.log(`[${name}] ${data.toString().trim()}`); + }); + + serverProcess.stderr.on('data', (data) => { + console.error(`[${name}] ${data.toString().trim()}`); + }); + + serverProcess.on('close', (code) => { + console.log(`[${name}] Process exited with code ${code}`); + this.servers.delete(name); + }); + + serverProcess.on('error', (error) => { + console.error(`[${name}] Error:`, error.message); + this.servers.delete(name); + }); + + this.servers.set(name, serverProcess); + } + + stopServer(name) { + const server = this.servers.get(name); + if (!server) { + console.log(`Server ${name} is not running`); + return; + } + + console.log(`Stopping MCP server: ${name}`); + server.kill('SIGTERM'); + this.servers.delete(name); + } + + stopAllServers() { + console.log('Stopping all MCP servers...'); + for (const [name, server] of this.servers) { + console.log(`Stopping ${name}...`); + server.kill('SIGTERM'); + } + this.servers.clear(); + } + + listServers() { + console.log('MCP Server Status:'); + const config = this.loadConfig(); + + for (const [name, serverConfig] of Object.entries(config.mcpServers)) { + const status = this.servers.has(name) ? 'Running' : 'Stopped'; + console.log(` ${name}: ${status}`); + } + } + + run() { + const args = process.argv.slice(2); + const command = args[0]; + const serverName = args[1]; + + switch (command) { + case 'start': + if (serverName) { + const config = this.loadConfig(); + const serverConfig = config.mcpServers[serverName]; + if (serverConfig) { + this.startServer(serverName, serverConfig); + } else { + console.error(`Unknown server: ${serverName}`); + this.showUsage(); + } + } else { + // Start all servers + const config = this.loadConfig(); + for (const [name, serverConfig] of Object.entries(config.mcpServers)) { + this.startServer(name, serverConfig); + } + } + break; + + case 'stop': + if (serverName) { + this.stopServer(serverName); + } else { + this.stopAllServers(); + } + break; + + case 'restart': + if (serverName) { + this.stopServer(serverName); + setTimeout(() => { + const config = this.loadConfig(); + const serverConfig = config.mcpServers[serverName]; + if (serverConfig) { + this.startServer(serverName, serverConfig); + } + }, 1000); + } else { + this.stopAllServers(); + setTimeout(() => { + const config = this.loadConfig(); + for (const [name, serverConfig] of Object.entries(config.mcpServers)) { + this.startServer(name, serverConfig); + } + }, 1000); + } + break; + + case 'status': + this.listServers(); + break; + + case 'list': + this.listServers(); + break; + + default: + this.showUsage(); + } + + // Handle graceful shutdown + process.on('SIGINT', () => { + console.log('\nReceived SIGINT. Shutting down gracefully...'); + this.stopAllServers(); + setTimeout(() => process.exit(0), 2000); + }); + + process.on('SIGTERM', () => { + console.log('\nReceived SIGTERM. Shutting down gracefully...'); + this.stopAllServers(); + setTimeout(() => process.exit(0), 2000); + }); + } + + showUsage() { + console.log(` +MySQL MCP Server Manager + +Usage: + node index.js [server-name] + +Commands: + start [server-name] Start all servers or specific server + stop [server-name] Stop all servers or specific server + restart [server-name] Restart all servers or specific server + status Show server status + list List all available servers + +Available servers: + mysql-process-monitor - Monitor MySQL processes + mysql-service-manager - Manage MySQL services + mysql-performance-monitor - Monitor MySQL performance + +Examples: + node index.js start # Start all servers + node index.js start mysql-process-monitor # Start specific server + node index.js stop # Stop all servers + node index.js restart mysql-service-manager # Restart specific server + node index.js status # Show status +`); + } +} + +const manager = new MCPServerManager(); +manager.run(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a821432 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,337 @@ +{ + "name": "mysql-mcp-servers", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mysql-mcp-servers", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.4.0", + "mysql2": "^3.6.5", + "node-cron": "^3.0.3", + "systeminformation": "^5.21.20" + }, + "devDependencies": { + "@types/node": "^20.10.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.4.0.tgz", + "integrity": "sha512-79gx8xh4o9YzdbtqMukOe5WKzvEZpvBA1x8PAgJWL7J5k06+vJx8NK2kWzOazPgqnfDego7cNEO8tjai/nOPAA==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@types/node": { + "version": "20.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz", + "integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/mysql2": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.1.tgz", + "integrity": "sha512-WZMIRZstT2MFfouEaDz/AGFnGi1A2GwaDe7XvKTdRJEYiAHbOrh4S3d8KFmQeh11U85G+BFjIvS1Di5alusZsw==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/systeminformation": { + "version": "5.27.10", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.10.tgz", + "integrity": "sha512-jkeOerLSwLZqJrPHCYltlKHu0PisdepIuS4GwjFFtgQUG/5AQPVZekkECuULqdP0cgrrIHW8Nl8J7WQXo5ypEg==", + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..15d9b1a --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "mysql-mcp-servers", + "version": "1.0.0", + "description": "Three MCP servers for MySQL monitoring and management", + "main": "index.js", + "type": "module", + "scripts": { + "start": "node index.js start", + "stop": "node index.js stop", + "restart": "node index.js restart", + "status": "node index.js status", + "start:process-monitor": "node servers/mysql-process-monitor/index.js", + "start:service-manager": "node servers/mysql-service-manager/index.js", + "start:performance-monitor": "node servers/mysql-performance-monitor/index.js", + "setup": "./setup.sh", + "test": "node test-mcp.js", + "test:mcp": "node test-mcp.js" + }, + "keywords": ["mcp", "mysql", "monitoring", "server", "task-manager"], + "author": "Bekithembanyathi", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.4.0", + "mysql2": "^3.6.5", + "node-cron": "^3.0.3", + "systeminformation": "^5.21.20" + }, + "devDependencies": { + "@types/node": "^20.10.0" + }, + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/servers/mysql-performance-monitor/index.js b/servers/mysql-performance-monitor/index.js new file mode 100755 index 0000000..ee11aaa --- /dev/null +++ b/servers/mysql-performance-monitor/index.js @@ -0,0 +1,789 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, + ListPromptsRequestSchema, + GetPromptRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { execSync, exec } from 'child_process'; +import { promisify } from 'util'; +import mysql from 'mysql2/promise'; +import cron from 'node-cron'; +import si from 'systeminformation'; + +const execAsync = promisify(exec); + +class MySQLPerformanceMonitorServer { + constructor() { + this.server = new Server( + { + name: "mysql-performance-monitor", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, + } + ); + + this.performanceHistory = []; + this.maxHistorySize = 100; + this.setupToolHandlers(); + this.setupResourceHandlers(); + this.setupPromptHandlers(); + } + + setupToolHandlers() { + this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: "mysql_performance_snapshot", + description: "Get current MySQL performance metrics", + inputSchema: { + type: "object", + properties: { + host: { + type: "string", + description: "MySQL host", + default: "localhost", + }, + port: { + type: "number", + description: "MySQL port", + default: 3306, + }, + user: { + type: "string", + description: "MySQL username", + default: "root", + }, + password: { + type: "string", + description: "MySQL password", + default: "", + }, + }, + }, + }, + { + name: "mysql_process_list", + description: "Get current MySQL process list", + inputSchema: { + type: "object", + properties: { + host: { + type: "string", + description: "MySQL host", + default: "localhost", + }, + port: { + type: "number", + description: "MySQL port", + default: 3306, + }, + user: { + type: "string", + description: "MySQL username", + default: "root", + }, + password: { + type: "string", + description: "MySQL password", + default: "", + }, + }, + }, + }, + { + name: "mysql_slow_queries", + description: "Get MySQL slow query information", + inputSchema: { + type: "object", + properties: { + host: { + type: "string", + description: "MySQL host", + default: "localhost", + }, + port: { + type: "number", + description: "MySQL port", + default: 3306, + }, + user: { + type: "string", + description: "MySQL username", + default: "root", + }, + password: { + type: "string", + description: "MySQL password", + default: "", + }, + }, + }, + }, + { + name: "mysql_connection_stats", + description: "Get MySQL connection statistics", + inputSchema: { + type: "object", + properties: { + host: { + type: "string", + description: "MySQL host", + default: "localhost", + }, + port: { + type: "number", + description: "MySQL port", + default: 3306, + }, + user: { + type: "string", + description: "MySQL username", + default: "root", + }, + password: { + type: "string", + description: "MySQL password", + default: "", + }, + }, + }, + }, + { + name: "mysql_innodb_status", + description: "Get InnoDB engine status", + inputSchema: { + type: "object", + properties: { + host: { + type: "string", + description: "MySQL host", + default: "localhost", + }, + port: { + type: "number", + description: "MySQL port", + default: 3306, + }, + user: { + type: "string", + description: "MySQL username", + default: "root", + }, + password: { + type: "string", + description: "MySQL password", + default: "", + }, + }, + }, + }, + { + name: "mysql_system_metrics", + description: "Get system-level metrics affecting MySQL performance", + inputSchema: { + type: "object", + properties: {}, + }, + }, + { + name: "start_performance_monitoring", + description: "Start continuous performance monitoring (stores data for trends)", + inputSchema: { + type: "object", + properties: { + interval: { + type: "number", + description: "Monitoring interval in seconds", + default: 60, + }, + }, + }, + }, + { + name: "stop_performance_monitoring", + description: "Stop continuous performance monitoring", + inputSchema: { + type: "object", + properties: {}, + }, + }, + ], + })); + + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case "mysql_performance_snapshot": + return await this.getMySQLPerformanceSnapshot(args); + case "mysql_process_list": + return await this.getMySQLProcessList(args); + case "mysql_slow_queries": + return await this.getMySQLSlowQueries(args); + case "mysql_connection_stats": + return await this.getMySQLConnectionStats(args); + case "mysql_innodb_status": + return await this.getMySQLInnoDBStatus(args); + case "mysql_system_metrics": + return await this.getMySQLSystemMetrics(); + case "start_performance_monitoring": + return await this.startPerformanceMonitoring(args?.interval || 60); + case "stop_performance_monitoring": + return await this.stopPerformanceMonitoring(); + default: + throw new Error(`Unknown tool: ${name}`); + } + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error executing ${name}: ${error.message}`, + }, + ], + isError: true, + }; + } + }); + } + + setupResourceHandlers() { + this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ + resources: [ + { + uri: "mysql://performance/current", + name: "Current MySQL Performance Metrics", + description: "Real-time MySQL performance data", + mimeType: "application/json", + }, + { + uri: "mysql://performance/history", + name: "MySQL Performance History", + description: "Historical MySQL performance data", + mimeType: "application/json", + }, + { + uri: "mysql://performance/analysis", + name: "MySQL Performance Analysis", + description: "Performance analysis and recommendations", + mimeType: "application/json", + }, + ], + })); + + this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const { uri } = request.params; + + try { + switch (uri) { + case "mysql://performance/current": + return await this.getCurrentPerformanceResource(); + case "mysql://performance/history": + return await this.getPerformanceHistoryResource(); + case "mysql://performance/analysis": + return await this.getPerformanceAnalysisResource(); + default: + throw new Error(`Unknown resource: ${uri}`); + } + } catch (error) { + throw new Error(`Error reading resource ${uri}: ${error.message}`); + } + }); + } + + setupPromptHandlers() { + this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({ + prompts: [ + { + name: "mysql_performance_analysis", + description: "Comprehensive MySQL performance analysis prompt", + }, + { + name: "mysql_optimization_recommendations", + description: "MySQL optimization recommendations prompt", + }, + { + name: "mysql_troubleshoot_performance", + description: "MySQL performance troubleshooting prompt", + }, + ], + })); + + this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { + const { name } = request.params; + + switch (name) { + case "mysql_performance_analysis": + return { + description: "Comprehensive MySQL performance analysis", + messages: [ + { + role: "user", + content: { + type: "text", + text: "Please perform a comprehensive MySQL performance analysis. Get current performance metrics, analyze slow queries, check connection statistics, and provide insights about the database performance.", + }, + }, + ], + }; + case "mysql_optimization_recommendations": + return { + description: "MySQL optimization recommendations", + messages: [ + { + role: "user", + content: { + type: "text", + text: "Based on the current MySQL performance metrics and system state, provide optimization recommendations. Focus on configuration tuning, query optimization, and resource allocation improvements.", + }, + }, + ], + }; + case "mysql_troubleshoot_performance": + return { + description: "MySQL performance troubleshooting", + messages: [ + { + role: "user", + content: { + type: "text", + text: "Help me troubleshoot MySQL performance issues. Check for bottlenecks, analyze slow queries, examine connection patterns, and suggest solutions for performance problems.", + }, + }, + ], + }; + default: + throw new Error(`Unknown prompt: ${name}`); + } + }); + } + + async createConnection(config = {}) { + const connectionConfig = { + host: config.host || 'localhost', + port: config.port || 3306, + user: config.user || 'root', + password: config.password || '', + connectTimeout: 5000, + acquireTimeout: 5000, + }; + + return await mysql.createConnection(connectionConfig); + } + + async getMySQLPerformanceSnapshot(config = {}) { + try { + const connection = await this.createConnection(config); + + const queries = [ + "SHOW GLOBAL STATUS LIKE 'Queries'", + "SHOW GLOBAL STATUS LIKE 'Connections'", + "SHOW GLOBAL STATUS LIKE 'Threads_%'", + "SHOW GLOBAL STATUS LIKE 'Slow_queries'", + "SHOW GLOBAL STATUS LIKE 'Innodb_%'", + "SHOW GLOBAL STATUS LIKE 'Key_%'", + "SHOW GLOBAL STATUS LIKE 'Sort_%'", + "SHOW GLOBAL STATUS LIKE 'Table_locks_%'", + ]; + + const results = {}; + + for (const query of queries) { + try { + const [rows] = await connection.execute(query); + const category = query.split("LIKE '")[1].replace("'", "").replace("%", ""); + results[category] = rows; + } catch (error) { + results[query] = { error: error.message }; + } + } + + await connection.end(); + + const snapshot = { + timestamp: new Date().toISOString(), + metrics: results, + }; + + // Store in history + this.performanceHistory.push(snapshot); + if (this.performanceHistory.length > this.maxHistorySize) { + this.performanceHistory.shift(); + } + + return { + content: [ + { + type: "text", + text: JSON.stringify(snapshot, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error getting MySQL performance snapshot: ${error.message}`, + }, + ], + }; + } + } + + async getMySQLProcessList(config = {}) { + try { + const connection = await this.createConnection(config); + + const [rows] = await connection.execute("SHOW FULL PROCESSLIST"); + await connection.end(); + + const processInfo = { + timestamp: new Date().toISOString(), + totalProcesses: rows.length, + processes: rows, + summary: { + sleeping: rows.filter(p => p.Command === 'Sleep').length, + query: rows.filter(p => p.Command === 'Query').length, + connect: rows.filter(p => p.Command === 'Connect').length, + }, + }; + + return { + content: [ + { + type: "text", + text: JSON.stringify(processInfo, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error getting MySQL process list: ${error.message}`, + }, + ], + }; + } + } + + async getMySQLSlowQueries(config = {}) { + try { + const connection = await this.createConnection(config); + + const queries = [ + "SHOW GLOBAL STATUS LIKE 'Slow_queries'", + "SHOW GLOBAL VARIABLES LIKE 'slow_query_log'", + "SHOW GLOBAL VARIABLES LIKE 'long_query_time'", + "SHOW GLOBAL VARIABLES LIKE 'slow_query_log_file'", + ]; + + const results = {}; + + for (const query of queries) { + try { + const [rows] = await connection.execute(query); + const key = query.split("LIKE '")[1].replace("'", ""); + results[key] = rows; + } catch (error) { + results[query] = { error: error.message }; + } + } + + await connection.end(); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + timestamp: new Date().toISOString(), + slowQueryInfo: results, + }, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error getting MySQL slow queries: ${error.message}`, + }, + ], + }; + } + } + + async getMySQLConnectionStats(config = {}) { + try { + const connection = await this.createConnection(config); + + const queries = [ + "SHOW GLOBAL STATUS LIKE 'Connections'", + "SHOW GLOBAL STATUS LIKE 'Max_used_connections'", + "SHOW GLOBAL STATUS LIKE 'Threads_connected'", + "SHOW GLOBAL STATUS LIKE 'Threads_running'", + "SHOW GLOBAL STATUS LIKE 'Aborted_%'", + "SHOW GLOBAL VARIABLES LIKE 'max_connections'", + ]; + + const results = {}; + + for (const query of queries) { + try { + const [rows] = await connection.execute(query); + const key = query.split("LIKE '")[1].replace("'", "").replace("%", ""); + results[key] = rows; + } catch (error) { + results[query] = { error: error.message }; + } + } + + await connection.end(); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + timestamp: new Date().toISOString(), + connectionStats: results, + }, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error getting MySQL connection stats: ${error.message}`, + }, + ], + }; + } + } + + async getMySQLInnoDBStatus(config = {}) { + try { + const connection = await this.createConnection(config); + + const [rows] = await connection.execute("SHOW ENGINE INNODB STATUS"); + await connection.end(); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + timestamp: new Date().toISOString(), + innodbStatus: rows, + }, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error getting MySQL InnoDB status: ${error.message}`, + }, + ], + }; + } + } + + async getMySQLSystemMetrics() { + try { + const metrics = { + timestamp: new Date().toISOString(), + cpu: await si.currentLoad(), + memory: await si.mem(), + disk: await si.fsSize(), + network: await si.networkStats(), + load: await si.currentLoad(), + }; + + return { + content: [ + { + type: "text", + text: JSON.stringify(metrics, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error getting system metrics: ${error.message}`, + }, + ], + }; + } + } + + async startPerformanceMonitoring(interval = 60) { + try { + if (this.monitoringTask) { + this.monitoringTask.stop(); + } + + this.monitoringTask = cron.schedule(`*/${interval} * * * * *`, async () => { + try { + await this.getMySQLPerformanceSnapshot(); + } catch (error) { + console.error('Monitoring error:', error); + } + }); + + this.monitoringTask.start(); + + return { + content: [ + { + type: "text", + text: `โœ… Performance monitoring started with ${interval} second interval`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error starting performance monitoring: ${error.message}`, + }, + ], + }; + } + } + + async stopPerformanceMonitoring() { + try { + if (this.monitoringTask) { + this.monitoringTask.stop(); + this.monitoringTask = null; + } + + return { + content: [ + { + type: "text", + text: "โœ… Performance monitoring stopped", + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error stopping performance monitoring: ${error.message}`, + }, + ], + }; + } + } + + async getCurrentPerformanceResource() { + const currentMetrics = await this.getMySQLPerformanceSnapshot(); + + return { + contents: [ + { + uri: "mysql://performance/current", + mimeType: "application/json", + text: currentMetrics.content[0].text, + }, + ], + }; + } + + async getPerformanceHistoryResource() { + return { + contents: [ + { + uri: "mysql://performance/history", + mimeType: "application/json", + text: JSON.stringify({ + totalRecords: this.performanceHistory.length, + maxSize: this.maxHistorySize, + history: this.performanceHistory, + }, null, 2), + }, + ], + }; + } + + async getPerformanceAnalysisResource() { + const analysis = { + timestamp: new Date().toISOString(), + historySize: this.performanceHistory.length, + recommendations: [], + alerts: [], + }; + + if (this.performanceHistory.length > 1) { + const latest = this.performanceHistory[this.performanceHistory.length - 1]; + const previous = this.performanceHistory[this.performanceHistory.length - 2]; + + // Simple trend analysis + analysis.trends = { + queriesIncreasing: this.compareMetric(latest, previous, 'Queries'), + connectionsIncreasing: this.compareMetric(latest, previous, 'Connections'), + }; + } + + return { + contents: [ + { + uri: "mysql://performance/analysis", + mimeType: "application/json", + text: JSON.stringify(analysis, null, 2), + }, + ], + }; + } + + compareMetric(latest, previous, metricName) { + try { + const latestValue = latest.metrics[metricName]?.[0]?.Value; + const previousValue = previous.metrics[metricName]?.[0]?.Value; + + if (latestValue && previousValue) { + return parseFloat(latestValue) > parseFloat(previousValue); + } + } catch (error) { + // Ignore comparison errors + } + return null; + } + + async run() { + const transport = new StdioServerTransport(); + await this.server.connect(transport); + console.error("MySQL Performance Monitor MCP server running on stdio"); + } +} + +const server = new MySQLPerformanceMonitorServer(); +server.run().catch(console.error); \ No newline at end of file diff --git a/servers/mysql-process-monitor/index.js b/servers/mysql-process-monitor/index.js new file mode 100755 index 0000000..a04b118 --- /dev/null +++ b/servers/mysql-process-monitor/index.js @@ -0,0 +1,464 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, + ListPromptsRequestSchema, + GetPromptRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { execSync, exec } from 'child_process'; +import { promisify } from 'util'; +import si from 'systeminformation'; + +const execAsync = promisify(exec); + +class MySQLProcessMonitorServer { + constructor() { + this.server = new Server( + { + name: "mysql-process-monitor", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, + } + ); + + this.setupToolHandlers(); + this.setupResourceHandlers(); + this.setupPromptHandlers(); + } + + setupToolHandlers() { + this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: "check_mysql_process", + description: "Check if MySQL process is running and get process details", + inputSchema: { + type: "object", + properties: {}, + }, + }, + { + name: "get_mysql_pid", + description: "Get MySQL process ID and details", + inputSchema: { + type: "object", + properties: {}, + }, + }, + { + name: "mysql_process_stats", + description: "Get detailed MySQL process statistics including CPU and memory usage", + inputSchema: { + type: "object", + properties: {}, + }, + }, + { + name: "kill_mysql_process", + description: "Forcefully kill MySQL process by PID", + inputSchema: { + type: "object", + properties: { + force: { + type: "boolean", + description: "Whether to force kill (SIGKILL) or graceful kill (SIGTERM)", + default: false, + }, + }, + }, + }, + ], + })); + + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case "check_mysql_process": + return await this.checkMySQLProcess(); + case "get_mysql_pid": + return await this.getMySQLPID(); + case "mysql_process_stats": + return await this.getMySQLProcessStats(); + case "kill_mysql_process": + return await this.killMySQLProcess(args?.force || false); + default: + throw new Error(`Unknown tool: ${name}`); + } + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error executing ${name}: ${error.message}`, + }, + ], + isError: true, + }; + } + }); + } + + setupResourceHandlers() { + this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ + resources: [ + { + uri: "mysql://process/status", + name: "MySQL Process Status", + description: "Current MySQL process status and information", + mimeType: "application/json", + }, + { + uri: "mysql://process/config", + name: "MySQL Process Configuration", + description: "MySQL process configuration and runtime parameters", + mimeType: "application/json", + }, + ], + })); + + this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const { uri } = request.params; + + try { + switch (uri) { + case "mysql://process/status": + return await this.getProcessStatusResource(); + case "mysql://process/config": + return await this.getProcessConfigResource(); + default: + throw new Error(`Unknown resource: ${uri}`); + } + } catch (error) { + throw new Error(`Error reading resource ${uri}: ${error.message}`); + } + }); + } + + setupPromptHandlers() { + this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({ + prompts: [ + { + name: "mysql_health_check", + description: "Comprehensive MySQL process health check prompt", + }, + { + name: "mysql_troubleshoot", + description: "MySQL process troubleshooting guide prompt", + }, + ], + })); + + this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { + const { name } = request.params; + + switch (name) { + case "mysql_health_check": + return { + description: "Comprehensive MySQL process health check", + messages: [ + { + role: "user", + content: { + type: "text", + text: "Please perform a comprehensive health check of the MySQL process. Check if it's running, get process statistics, and provide recommendations for any issues found.", + }, + }, + ], + }; + case "mysql_troubleshoot": + return { + description: "MySQL process troubleshooting guide", + messages: [ + { + role: "user", + content: { + type: "text", + text: "Help me troubleshoot MySQL process issues. Check the process status, analyze any problems, and suggest solutions for common MySQL process problems like high CPU usage, memory leaks, or unresponsive processes.", + }, + }, + ], + }; + default: + throw new Error(`Unknown prompt: ${name}`); + } + }); + } + + async checkMySQLProcess() { + try { + const { stdout } = await execAsync('pgrep -f mysql'); + const pids = stdout.trim().split('\n').filter(pid => pid); + + if (pids.length === 0) { + return { + content: [ + { + type: "text", + text: "โŒ MySQL process is not running", + }, + ], + }; + } + + const processInfo = []; + for (const pid of pids) { + try { + const { stdout: psOutput } = await execAsync(`ps -p ${pid} -o pid,ppid,cmd,etime,%cpu,%mem --no-headers`); + processInfo.push(psOutput.trim()); + } catch (error) { + // Process might have died between pgrep and ps + } + } + + return { + content: [ + { + type: "text", + text: `โœ… MySQL process(es) running:\n${processInfo.join('\n')}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `โŒ MySQL process is not running or error checking: ${error.message}`, + }, + ], + }; + } + } + + async getMySQLPID() { + try { + const { stdout } = await execAsync('pgrep -f mysqld'); + const pids = stdout.trim().split('\n').filter(pid => pid); + + if (pids.length === 0) { + return { + content: [ + { + type: "text", + text: "No MySQL daemon process found", + }, + ], + }; + } + + const pidInfo = { + pids: pids, + count: pids.length, + details: [] + }; + + for (const pid of pids) { + try { + const { stdout: details } = await execAsync(`ps -p ${pid} -o pid,ppid,user,cmd,etime,%cpu,%mem,vsz,rss --no-headers`); + pidInfo.details.push(details.trim()); + } catch (error) { + pidInfo.details.push(`PID ${pid}: Process information unavailable`); + } + } + + return { + content: [ + { + type: "text", + text: JSON.stringify(pidInfo, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error getting MySQL PID: ${error.message}`, + }, + ], + }; + } + } + + async getMySQLProcessStats() { + try { + const processes = await si.processes(); + const mysqlProcesses = processes.list.filter(proc => + proc.name.toLowerCase().includes('mysql') || + proc.command.toLowerCase().includes('mysql') + ); + + if (mysqlProcesses.length === 0) { + return { + content: [ + { + type: "text", + text: "No MySQL processes found in system information", + }, + ], + }; + } + + const stats = { + processCount: mysqlProcesses.length, + totalCPU: mysqlProcesses.reduce((sum, proc) => sum + proc.cpu, 0), + totalMemory: mysqlProcesses.reduce((sum, proc) => sum + proc.mem, 0), + processes: mysqlProcesses.map(proc => ({ + pid: proc.pid, + name: proc.name, + cpu: proc.cpu, + memory: proc.mem, + state: proc.state, + started: proc.started, + user: proc.user, + })) + }; + + return { + content: [ + { + type: "text", + text: JSON.stringify(stats, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error getting MySQL process stats: ${error.message}`, + }, + ], + }; + } + } + + async killMySQLProcess(force = false) { + try { + const { stdout } = await execAsync('pgrep -f mysqld'); + const pids = stdout.trim().split('\n').filter(pid => pid); + + if (pids.length === 0) { + return { + content: [ + { + type: "text", + text: "No MySQL processes found to kill", + }, + ], + }; + } + + const signal = force ? 'SIGKILL' : 'SIGTERM'; + const results = []; + + for (const pid of pids) { + try { + await execAsync(`kill -${signal} ${pid}`); + results.push(`โœ… Sent ${signal} to PID ${pid}`); + } catch (error) { + results.push(`โŒ Failed to kill PID ${pid}: ${error.message}`); + } + } + + return { + content: [ + { + type: "text", + text: results.join('\n'), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error killing MySQL process: ${error.message}`, + }, + ], + }; + } + } + + async getProcessStatusResource() { + const processInfo = await this.checkMySQLProcess(); + const pidInfo = await this.getMySQLPID(); + const stats = await this.getMySQLProcessStats(); + + return { + contents: [ + { + uri: "mysql://process/status", + mimeType: "application/json", + text: JSON.stringify({ + timestamp: new Date().toISOString(), + status: processInfo, + pids: pidInfo, + statistics: stats, + }, null, 2), + }, + ], + }; + } + + async getProcessConfigResource() { + try { + // Try to get MySQL configuration + let config = {}; + + try { + const { stdout } = await execAsync('mysql --help --verbose | grep "Default options"'); + config.defaultOptions = stdout.trim(); + } catch (error) { + config.defaultOptions = "Unable to retrieve default options"; + } + + try { + const { stdout } = await execAsync('find /etc -name "my.cnf" -o -name "mysql.conf" 2>/dev/null | head -5'); + config.configFiles = stdout.trim().split('\n').filter(file => file); + } catch (error) { + config.configFiles = []; + } + + return { + contents: [ + { + uri: "mysql://process/config", + mimeType: "application/json", + text: JSON.stringify({ + timestamp: new Date().toISOString(), + configuration: config, + }, null, 2), + }, + ], + }; + } catch (error) { + throw new Error(`Error getting process config: ${error.message}`); + } + } + + async run() { + const transport = new StdioServerTransport(); + await this.server.connect(transport); + console.error("MySQL Process Monitor MCP server running on stdio"); + } +} + +const server = new MySQLProcessMonitorServer(); +server.run().catch(console.error); \ No newline at end of file diff --git a/servers/mysql-service-manager/index.js b/servers/mysql-service-manager/index.js new file mode 100755 index 0000000..12a0b7e --- /dev/null +++ b/servers/mysql-service-manager/index.js @@ -0,0 +1,605 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, + ListPromptsRequestSchema, + GetPromptRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { execSync, exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +class MySQLServiceManagerServer { + constructor() { + this.server = new Server( + { + name: "mysql-service-manager", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, + } + ); + + this.setupToolHandlers(); + this.setupResourceHandlers(); + this.setupPromptHandlers(); + } + + setupToolHandlers() { + this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: "mysql_service_status", + description: "Check MySQL service status using systemctl", + inputSchema: { + type: "object", + properties: {}, + }, + }, + { + name: "start_mysql_service", + description: "Start MySQL service", + inputSchema: { + type: "object", + properties: { + service_name: { + type: "string", + description: "MySQL service name (mysql, mysqld, mariadb)", + default: "mysql", + }, + }, + }, + }, + { + name: "stop_mysql_service", + description: "Stop MySQL service", + inputSchema: { + type: "object", + properties: { + service_name: { + type: "string", + description: "MySQL service name (mysql, mysqld, mariadb)", + default: "mysql", + }, + }, + }, + }, + { + name: "restart_mysql_service", + description: "Restart MySQL service", + inputSchema: { + type: "object", + properties: { + service_name: { + type: "string", + description: "MySQL service name (mysql, mysqld, mariadb)", + default: "mysql", + }, + }, + }, + }, + { + name: "reload_mysql_service", + description: "Reload MySQL service configuration", + inputSchema: { + type: "object", + properties: { + service_name: { + type: "string", + description: "MySQL service name (mysql, mysqld, mariadb)", + default: "mysql", + }, + }, + }, + }, + { + name: "enable_mysql_service", + description: "Enable MySQL service to start on boot", + inputSchema: { + type: "object", + properties: { + service_name: { + type: "string", + description: "MySQL service name (mysql, mysqld, mariadb)", + default: "mysql", + }, + }, + }, + }, + { + name: "disable_mysql_service", + description: "Disable MySQL service from starting on boot", + inputSchema: { + type: "object", + properties: { + service_name: { + type: "string", + description: "MySQL service name (mysql, mysqld, mariadb)", + default: "mysql", + }, + }, + }, + }, + ], + })); + + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case "mysql_service_status": + return await this.getMySQLServiceStatus(); + case "start_mysql_service": + return await this.startMySQLService(args?.service_name || "mysql"); + case "stop_mysql_service": + return await this.stopMySQLService(args?.service_name || "mysql"); + case "restart_mysql_service": + return await this.restartMySQLService(args?.service_name || "mysql"); + case "reload_mysql_service": + return await this.reloadMySQLService(args?.service_name || "mysql"); + case "enable_mysql_service": + return await this.enableMySQLService(args?.service_name || "mysql"); + case "disable_mysql_service": + return await this.disableMySQLService(args?.service_name || "mysql"); + default: + throw new Error(`Unknown tool: ${name}`); + } + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error executing ${name}: ${error.message}`, + }, + ], + isError: true, + }; + } + }); + } + + setupResourceHandlers() { + this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ + resources: [ + { + uri: "mysql://service/status", + name: "MySQL Service Status", + description: "Current MySQL service status and configuration", + mimeType: "application/json", + }, + { + uri: "mysql://service/logs", + name: "MySQL Service Logs", + description: "Recent MySQL service logs", + mimeType: "text/plain", + }, + { + uri: "mysql://service/config", + name: "MySQL Service Configuration", + description: "MySQL service configuration files and settings", + mimeType: "application/json", + }, + ], + })); + + this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const { uri } = request.params; + + try { + switch (uri) { + case "mysql://service/status": + return await this.getServiceStatusResource(); + case "mysql://service/logs": + return await this.getServiceLogsResource(); + case "mysql://service/config": + return await this.getServiceConfigResource(); + default: + throw new Error(`Unknown resource: ${uri}`); + } + } catch (error) { + throw new Error(`Error reading resource ${uri}: ${error.message}`); + } + }); + } + + setupPromptHandlers() { + this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({ + prompts: [ + { + name: "mysql_service_management", + description: "MySQL service management guidance prompt", + }, + { + name: "mysql_service_troubleshoot", + description: "MySQL service troubleshooting prompt", + }, + { + name: "mysql_service_maintenance", + description: "MySQL service maintenance procedures prompt", + }, + ], + })); + + this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { + const { name } = request.params; + + switch (name) { + case "mysql_service_management": + return { + description: "MySQL service management guidance", + messages: [ + { + role: "user", + content: { + type: "text", + text: "Please help me manage MySQL services. Check the current status, provide guidance on starting/stopping/restarting services, and explain best practices for MySQL service management.", + }, + }, + ], + }; + case "mysql_service_troubleshoot": + return { + description: "MySQL service troubleshooting", + messages: [ + { + role: "user", + content: { + type: "text", + text: "I'm having issues with MySQL service. Please check the service status, analyze any problems, review recent logs, and provide troubleshooting steps to resolve common MySQL service issues.", + }, + }, + ], + }; + case "mysql_service_maintenance": + return { + description: "MySQL service maintenance procedures", + messages: [ + { + role: "user", + content: { + type: "text", + text: "Guide me through MySQL service maintenance procedures including safe restart methods, configuration reloading, and ensuring service reliability.", + }, + }, + ], + }; + default: + throw new Error(`Unknown prompt: ${name}`); + } + }); + } + + async detectMySQLService() { + const services = ["mysql", "mysqld", "mariadb", "mysql.service", "mysqld.service", "mariadb.service"]; + + for (const service of services) { + try { + await execAsync(`systemctl list-unit-files | grep -q "^${service}"`); + return service; + } catch (error) { + // Service not found, try next + } + } + + return "mysql"; // Default fallback + } + + async getMySQLServiceStatus() { + try { + const serviceName = await this.detectMySQLService(); + const commands = [ + `systemctl status ${serviceName}`, + `systemctl is-active ${serviceName}`, + `systemctl is-enabled ${serviceName}`, + ]; + + const results = {}; + + for (const cmd of commands) { + try { + const { stdout, stderr } = await execAsync(cmd); + const cmdName = cmd.split(' ').slice(-1)[0]; + results[cmdName] = { + success: true, + output: stdout.trim(), + error: stderr.trim(), + }; + } catch (error) { + const cmdName = cmd.split(' ').slice(-1)[0]; + results[cmdName] = { + success: false, + output: error.stdout || "", + error: error.stderr || error.message, + }; + } + } + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + serviceName, + timestamp: new Date().toISOString(), + status: results, + }, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error getting MySQL service status: ${error.message}`, + }, + ], + }; + } + } + + async startMySQLService(serviceName) { + try { + const { stdout, stderr } = await execAsync(`sudo systemctl start ${serviceName}`); + + // Check if service started successfully + const { stdout: statusOutput } = await execAsync(`systemctl is-active ${serviceName}`); + + return { + content: [ + { + type: "text", + text: `โœ… MySQL service '${serviceName}' start command executed.\nStatus: ${statusOutput.trim()}\nOutput: ${stdout}\nErrors: ${stderr}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `โŒ Failed to start MySQL service '${serviceName}': ${error.message}`, + }, + ], + }; + } + } + + async stopMySQLService(serviceName) { + try { + const { stdout, stderr } = await execAsync(`sudo systemctl stop ${serviceName}`); + + // Check if service stopped successfully + const { stdout: statusOutput } = await execAsync(`systemctl is-active ${serviceName}`); + + return { + content: [ + { + type: "text", + text: `โœ… MySQL service '${serviceName}' stop command executed.\nStatus: ${statusOutput.trim()}\nOutput: ${stdout}\nErrors: ${stderr}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `โŒ Failed to stop MySQL service '${serviceName}': ${error.message}`, + }, + ], + }; + } + } + + async restartMySQLService(serviceName) { + try { + const { stdout, stderr } = await execAsync(`sudo systemctl restart ${serviceName}`); + + // Wait a moment for service to start + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Check if service restarted successfully + const { stdout: statusOutput } = await execAsync(`systemctl is-active ${serviceName}`); + + return { + content: [ + { + type: "text", + text: `โœ… MySQL service '${serviceName}' restart command executed.\nStatus: ${statusOutput.trim()}\nOutput: ${stdout}\nErrors: ${stderr}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `โŒ Failed to restart MySQL service '${serviceName}': ${error.message}`, + }, + ], + }; + } + } + + async reloadMySQLService(serviceName) { + try { + const { stdout, stderr } = await execAsync(`sudo systemctl reload ${serviceName}`); + + return { + content: [ + { + type: "text", + text: `โœ… MySQL service '${serviceName}' reload command executed.\nOutput: ${stdout}\nErrors: ${stderr}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `โŒ Failed to reload MySQL service '${serviceName}': ${error.message}`, + }, + ], + }; + } + } + + async enableMySQLService(serviceName) { + try { + const { stdout, stderr } = await execAsync(`sudo systemctl enable ${serviceName}`); + + // Check if service is enabled + const { stdout: enabledOutput } = await execAsync(`systemctl is-enabled ${serviceName}`); + + return { + content: [ + { + type: "text", + text: `โœ… MySQL service '${serviceName}' enable command executed.\nEnabled: ${enabledOutput.trim()}\nOutput: ${stdout}\nErrors: ${stderr}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `โŒ Failed to enable MySQL service '${serviceName}': ${error.message}`, + }, + ], + }; + } + } + + async disableMySQLService(serviceName) { + try { + const { stdout, stderr } = await execAsync(`sudo systemctl disable ${serviceName}`); + + // Check if service is disabled + const { stdout: enabledOutput } = await execAsync(`systemctl is-enabled ${serviceName}`); + + return { + content: [ + { + type: "text", + text: `โœ… MySQL service '${serviceName}' disable command executed.\nEnabled: ${enabledOutput.trim()}\nOutput: ${stdout}\nErrors: ${stderr}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `โŒ Failed to disable MySQL service '${serviceName}': ${error.message}`, + }, + ], + }; + } + } + + async getServiceStatusResource() { + const statusInfo = await this.getMySQLServiceStatus(); + + return { + contents: [ + { + uri: "mysql://service/status", + mimeType: "application/json", + text: statusInfo.content[0].text, + }, + ], + }; + } + + async getServiceLogsResource() { + try { + const serviceName = await this.detectMySQLService(); + const { stdout } = await execAsync(`journalctl -u ${serviceName} --no-pager -n 50`); + + return { + contents: [ + { + uri: "mysql://service/logs", + mimeType: "text/plain", + text: stdout, + }, + ], + }; + } catch (error) { + return { + contents: [ + { + uri: "mysql://service/logs", + mimeType: "text/plain", + text: `Error retrieving service logs: ${error.message}`, + }, + ], + }; + } + } + + async getServiceConfigResource() { + try { + const serviceName = await this.detectMySQLService(); + + let configData = { + serviceName, + timestamp: new Date().toISOString(), + systemdUnit: {}, + configFiles: [], + }; + + // Get systemd unit file information + try { + const { stdout } = await execAsync(`systemctl cat ${serviceName}`); + configData.systemdUnit.content = stdout; + } catch (error) { + configData.systemdUnit.error = error.message; + } + + // Find MySQL configuration files + try { + const { stdout } = await execAsync(`find /etc -name "*.cnf" -o -name "my.cnf" 2>/dev/null | head -10`); + configData.configFiles = stdout.trim().split('\n').filter(file => file); + } catch (error) { + configData.configFiles = []; + } + + return { + contents: [ + { + uri: "mysql://service/config", + mimeType: "application/json", + text: JSON.stringify(configData, null, 2), + }, + ], + }; + } catch (error) { + throw new Error(`Error getting service config: ${error.message}`); + } + } + + async run() { + const transport = new StdioServerTransport(); + await this.server.connect(transport); + console.error("MySQL Service Manager MCP server running on stdio"); + } +} + +const server = new MySQLServiceManagerServer(); +server.run().catch(console.error); \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..b4ee2b1 --- /dev/null +++ b/setup.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +# MySQL MCP Servers Setup and Test Script + +echo "๐Ÿš€ MySQL MCP Servers Setup and Test" +echo "====================================" + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Check Node.js +if ! command_exists node; then + echo "โŒ Node.js is not installed. Please install Node.js first." + exit 1 +fi + +echo "โœ… Node.js version: $(node --version)" + +# Check npm +if ! command_exists npm; then + echo "โŒ npm is not installed. Please install npm first." + exit 1 +fi + +echo "โœ… npm version: $(npm --version)" + +# Install dependencies if node_modules doesn't exist +if [ ! -d "node_modules" ]; then + echo "๐Ÿ“ฆ Installing dependencies..." + npm install +fi + +echo "โœ… Dependencies installed" + +# Make scripts executable +chmod +x index.js +chmod +x servers/*/index.js +echo "โœ… Scripts made executable" + +# Test server manager +echo "" +echo "๐Ÿ” Testing Server Manager..." +echo "Server Status:" +node index.js status + +echo "" +echo "๐Ÿ“‹ Available commands:" +echo " node index.js start # Start all servers" +echo " node index.js start mysql-process-monitor # Start specific server" +echo " node index.js stop # Stop all servers" +echo " node index.js restart # Restart all servers" +echo " node index.js status # Show server status" + +echo "" +echo "๐Ÿงช Testing individual servers (5 second timeout each)..." + +echo "Testing MySQL Process Monitor..." +timeout 5 node servers/mysql-process-monitor/index.js >/dev/null 2>&1 && echo "โœ… Process Monitor: OK" || echo "โŒ Process Monitor: Failed" + +echo "Testing MySQL Service Manager..." +timeout 5 node servers/mysql-service-manager/index.js >/dev/null 2>&1 && echo "โœ… Service Manager: OK" || echo "โŒ Service Manager: Failed" + +echo "Testing MySQL Performance Monitor..." +timeout 5 node servers/mysql-performance-monitor/index.js >/dev/null 2>&1 && echo "โœ… Performance Monitor: OK" || echo "โŒ Performance Monitor: Failed" + +echo "" +echo "๐ŸŽ‰ Setup complete! All MCP servers are ready to use." +echo "" +echo "๐Ÿ“š Next steps:" +echo "1. Configure MySQL connection in config/mysql-config.json" +echo "2. Start the servers: node index.js start" +echo "3. Use the MCP tools, resources, and prompts in your application" +echo "" +echo "๐Ÿ“– For detailed usage, see README.md" \ No newline at end of file diff --git a/test-mcp.js b/test-mcp.js new file mode 100755 index 0000000..f53c6f9 --- /dev/null +++ b/test-mcp.js @@ -0,0 +1,147 @@ +#!/usr/bin/env node + +import { spawn } from 'child_process'; +import { readFileSync } from 'fs'; + +/** + * Simple MCP client for testing server functionality + */ +class MCPTestClient { + constructor(serverPath) { + this.serverPath = serverPath; + } + + async sendMessage(message) { + return new Promise((resolve, reject) => { + const server = spawn('node', [this.serverPath], { + stdio: ['pipe', 'pipe', 'pipe'] + }); + + let responseData = ''; + let errorData = ''; + + server.stdout.on('data', (data) => { + responseData += data.toString(); + }); + + server.stderr.on('data', (data) => { + errorData += data.toString(); + }); + + server.on('close', (code) => { + if (code === 0) { + resolve({ stdout: responseData, stderr: errorData }); + } else { + reject(new Error(`Server exited with code ${code}: ${errorData}`)); + } + }); + + server.on('error', (error) => { + reject(error); + }); + + // Send the message + server.stdin.write(JSON.stringify(message) + '\n'); + server.stdin.end(); + + // Timeout after 5 seconds + setTimeout(() => { + server.kill('SIGTERM'); + reject(new Error('Test timeout')); + }, 5000); + }); + } + + async testListTools() { + const message = { + jsonrpc: "2.0", + id: 1, + method: "tools/list", + params: {} + }; + + try { + const response = await this.sendMessage(message); + console.log('โœ… List Tools Response:', response.stdout.trim()); + return true; + } catch (error) { + console.log('โŒ List Tools Error:', error.message); + return false; + } + } + + async testListResources() { + const message = { + jsonrpc: "2.0", + id: 2, + method: "resources/list", + params: {} + }; + + try { + const response = await this.sendMessage(message); + console.log('โœ… List Resources Response:', response.stdout.trim()); + return true; + } catch (error) { + console.log('โŒ List Resources Error:', error.message); + return false; + } + } + + async testListPrompts() { + const message = { + jsonrpc: "2.0", + id: 3, + method: "prompts/list", + params: {} + }; + + try { + const response = await this.sendMessage(message); + console.log('โœ… List Prompts Response:', response.stdout.trim()); + return true; + } catch (error) { + console.log('โŒ List Prompts Error:', error.message); + return false; + } + } +} + +async function testAllServers() { + const servers = [ + { + name: 'MySQL Process Monitor', + path: 'servers/mysql-process-monitor/index.js' + }, + { + name: 'MySQL Service Manager', + path: 'servers/mysql-service-manager/index.js' + }, + { + name: 'MySQL Performance Monitor', + path: 'servers/mysql-performance-monitor/index.js' + } + ]; + + console.log('๐Ÿงช Testing MCP Server Protocol Communication'); + console.log('=' .repeat(50)); + + for (const server of servers) { + console.log(`\n๐Ÿ“ก Testing ${server.name}...`); + console.log('-'.repeat(30)); + + const client = new MCPTestClient(server.path); + + // Test basic protocol methods + await client.testListTools(); + await client.testListResources(); + await client.testListPrompts(); + } + + console.log('\n๐ŸŽ‰ MCP Protocol Tests Complete!'); +} + +// Run tests if this script is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + testAllServers().catch(console.error); +} \ No newline at end of file