UNPKG

mysql-mcp-server-v1

Version:

Servidor MCP completo para MySQL Database com sistema hierárquico de configuração, operações DDL, DML, DCL, DDM, DCM, DLM, monitoramento, auditoria e gestão de ciclo de vida de dados

620 lines (499 loc) 18.1 kB
import { Logger } from './logger.js'; import mysql from 'mysql2/promise'; import { ConnectionManager } from './connection-manager.js'; export class DCLOperations { constructor(connectionConfig = null, connectionManager = null) { this.logger = new Logger(); this.connectionConfig = connectionConfig; this.connectionManager = connectionManager || new ConnectionManager(); } async getConnection(connectionName = null) { try { // Se temos um ConnectionManager, usar ele if (this.connectionManager) { return await this.connectionManager.getConnection(connectionName); } // Fallback para configuração antiga if (this.connectionConfig) { const connection = await mysql.createConnection(this.connectionConfig); return connection; } throw new Error('Nenhuma configuração de conexão disponível'); } catch (error) { this.logger.error('Erro ao conectar com MySQL:', error); throw new Error(`Falha na conexão: ${error.message}`); } } // ===== OPERAÇÕES DE USUÁRIO ===== async createUser(options = {}) { const { username, password, host = '%', ifNotExists = true, connectionName = null } = options; if (!username || !password) { throw new Error('Nome de usuário e senha são obrigatórios'); } let connection; try { connection = await this.getConnection(connectionName); // Verificar se o usuário já existe if (ifNotExists) { const exists = await this.userExists(connection, username, host); if (exists) { return `⚠️ Usuário '${username}'@'${host}' já existe. Operação ignorada.`; } } const createQuery = ` CREATE USER ${ifNotExists ? 'IF NOT EXISTS ' : ''}'${username}'@'${host}' IDENTIFIED BY '${password}' `; await connection.execute(createQuery); this.logger.info(`Usuário '${username}'@'${host}' criado com sucesso`); return `✅ Usuário '${username}'@'${host}' criado com sucesso!`; } catch (error) { this.logger.error('Erro ao criar usuário:', error); throw new Error(`Erro ao criar usuário: ${error.message}`); } finally { if (connection) { await connection.end(); } } } async dropUser(options = {}) { const { username, host = '%', ifExists = true, connectionName = null } = options; if (!username) { throw new Error('Nome de usuário é obrigatório'); } let connection; try { connection = await this.getConnection(connectionName); // Verificar se o usuário existe if (ifExists) { const exists = await this.userExists(connection, username, host); if (!exists) { return `⚠️ Usuário '${username}'@'${host}' não existe. Operação ignorada.`; } } const dropQuery = `DROP USER ${ifExists ? 'IF EXISTS ' : ''}'${username}'@'${host}'`; await connection.execute(dropQuery); this.logger.info(`Usuário '${username}'@'${host}' removido com sucesso`); return `✅ Usuário '${username}'@'${host}' removido com sucesso!`; } catch (error) { this.logger.error('Erro ao remover usuário:', error); throw new Error(`Erro ao remover usuário: ${error.message}`); } finally { if (connection) { await connection.end(); } } } async alterUser(options = {}) { const { username, host = '%', password, connectionName = null } = options; if (!username || !password) { throw new Error('Nome de usuário e nova senha são obrigatórios'); } let connection; try { connection = await this.getConnection(connectionName); const alterQuery = ` ALTER USER '${username}'@'${host}' IDENTIFIED BY '${password}' `; await connection.execute(alterQuery); this.logger.info(`Senha do usuário '${username}'@'${host}' alterada com sucesso`); return `✅ Senha do usuário '${username}'@'${host}' alterada com sucesso!`; } catch (error) { this.logger.error('Erro ao alterar usuário:', error); throw new Error(`Erro ao alterar usuário: ${error.message}`); } finally { if (connection) { await connection.end(); } } } // ===== OPERAÇÕES DE PRIVILÉGIOS ===== async grantPrivileges(options = {}) { const { privileges, onObject, toUser, toHost = '%', withGrantOption = false, connectionName = null } = options; if (!privileges || !Array.isArray(privileges) || privileges.length === 0) { throw new Error('Lista de privilégios é obrigatória'); } if (!onObject || !toUser) { throw new Error('Objeto e usuário de destino são obrigatórios'); } let connection; try { connection = await this.getConnection(connectionName); const privilegesList = privileges.join(', '); const grantOption = withGrantOption ? ' WITH GRANT OPTION' : ''; const grantQuery = ` GRANT ${privilegesList} ON ${onObject} TO '${toUser}'@'${toHost}'${grantOption} `; await connection.execute(grantQuery); this.logger.info(`Privilégios concedidos para '${toUser}'@'${toHost}' em ${onObject}`); return `✅ Privilégios ${privilegesList} concedidos para '${toUser}'@'${toHost}' em ${onObject}!`; } catch (error) { this.logger.error('Erro ao conceder privilégios:', error); throw new Error(`Erro ao conceder privilégios: ${error.message}`); } finally { if (connection) { await connection.end(); } } } async revokePrivileges(options = {}) { const { privileges, onObject, fromUser, fromHost = '%', connectionName = null } = options; if (!privileges || !Array.isArray(privileges) || privileges.length === 0) { throw new Error('Lista de privilégios é obrigatória'); } if (!onObject || !fromUser) { throw new Error('Objeto e usuário de origem são obrigatórios'); } let connection; try { connection = await this.getConnection(connectionName); const privilegesList = privileges.join(', '); const revokeQuery = ` REVOKE ${privilegesList} ON ${onObject} FROM '${fromUser}'@'${fromHost}' `; await connection.execute(revokeQuery); this.logger.info(`Privilégios revogados de '${fromUser}'@'${fromHost}' em ${onObject}`); return `✅ Privilégios ${privilegesList} revogados de '${fromUser}'@'${fromHost}' em ${onObject}!`; } catch (error) { this.logger.error('Erro ao revogar privilégios:', error); throw new Error(`Erro ao revogar privilégios: ${error.message}`); } finally { if (connection) { await connection.end(); } } } // ===== OPERAÇÕES DE ROLE (MySQL 8.0+) ===== async createRole(options = {}) { const { roleName, ifNotExists = true, connectionName = null } = options; if (!roleName) { throw new Error('Nome do role é obrigatório'); } let connection; try { connection = await this.getConnection(connectionName); // Verificar se o role já existe if (ifNotExists) { const exists = await this.roleExists(connection, roleName); if (exists) { return `⚠️ Role '${roleName}' já existe. Operação ignorada.`; } } const createQuery = `CREATE ROLE ${ifNotExists ? 'IF NOT EXISTS ' : ''}'${roleName}'`; await connection.execute(createQuery); this.logger.info(`Role '${roleName}' criado com sucesso`); return `✅ Role '${roleName}' criado com sucesso!`; } catch (error) { this.logger.error('Erro ao criar role:', error); throw new Error(`Erro ao criar role: ${error.message}`); } finally { if (connection) { await connection.end(); } } } async dropRole(options = {}) { const { roleName, ifExists = true, connectionName = null } = options; if (!roleName) { throw new Error('Nome do role é obrigatório'); } let connection; try { connection = await this.getConnection(connectionName); // Verificar se o role existe if (ifExists) { const exists = await this.roleExists(connection, roleName); if (!exists) { return `⚠️ Role '${roleName}' não existe. Operação ignorada.`; } } const dropQuery = `DROP ROLE ${ifExists ? 'IF EXISTS ' : ''}'${roleName}'`; await connection.execute(dropQuery); this.logger.info(`Role '${roleName}' removido com sucesso`); return `✅ Role '${roleName}' removido com sucesso!`; } catch (error) { this.logger.error('Erro ao remover role:', error); throw new Error(`Erro ao remover role: ${error.message}`); } finally { if (connection) { await connection.end(); } } } async grantRole(options = {}) { const { roleName, toUser, toHost = '%', connectionName = null } = options; if (!roleName || !toUser) { throw new Error('Nome do role e usuário de destino são obrigatórios'); } let connection; try { connection = await this.getConnection(connectionName); const grantQuery = `GRANT '${roleName}' TO '${toUser}'@'${toHost}'`; await connection.execute(grantQuery); this.logger.info(`Role '${roleName}' concedido para '${toUser}'@'${toHost}'`); return `✅ Role '${roleName}' concedido para '${toUser}'@'${toHost}'!`; } catch (error) { this.logger.error('Erro ao conceder role:', error); throw new Error(`Erro ao conceder role: ${error.message}`); } finally { if (connection) { await connection.end(); } } } async revokeRole(options = {}) { const { roleName, fromUser, fromHost = '%', connectionName = null } = options; if (!roleName || !fromUser) { throw new Error('Nome do role e usuário de origem são obrigatórios'); } let connection; try { connection = await this.getConnection(connectionName); const revokeQuery = `REVOKE '${roleName}' FROM '${fromUser}'@'${fromHost}'`; await connection.execute(revokeQuery); this.logger.info(`Role '${roleName}' revogado de '${fromUser}'@'${fromHost}'`); return `✅ Role '${roleName}' revogado de '${fromUser}'@'${fromHost}'!`; } catch (error) { this.logger.error('Erro ao revogar role:', error); throw new Error(`Erro ao revogar role: ${error.message}`); } finally { if (connection) { await connection.end(); } } } // ===== OPERAÇÕES DE CONSULTA ===== async listUsers(connectionName = null) { let connection; try { connection = await this.getConnection(connectionName); const query = ` SELECT user, host, account_locked, password_expired, password_last_changed, password_lifetime FROM mysql.user WHERE user NOT IN ('mysql.session', 'mysql.sys', 'mysql.infoschema') ORDER BY user, host `; const [rows] = await connection.execute(query); let output = ''; if (rows.length === 0) { output = 'Nenhum usuário encontrado.'; } else { output += '| Usuário | Host | Conta Bloqueada | Senha Expirada | Última Alteração | Vida da Senha |\n'; output += '|---------|------|-----------------|----------------|------------------|---------------|\n'; for (const row of rows) { output += `| ${row.user} | ${row.host} | ${row.account_locked} | ${row.password_expired} | ${row.password_last_changed || 'N/A'} | ${row.password_lifetime || 'N/A'} |\n`; } output += `\n**Total de usuários:** ${rows.length}`; } return output; } catch (error) { this.logger.error('Erro ao listar usuários:', error); throw new Error(`Erro ao listar usuários: ${error.message}`); } finally { if (connection) { await connection.end(); } } } async listPrivileges(user, host = '%', connectionName = null) { let connection; try { connection = await this.getConnection(connectionName); const query = ` SELECT table_schema, table_name, privilege_type, is_grantable FROM information_schema.table_privileges WHERE grantee = CONCAT('${user}', '@', '${host}') ORDER BY table_schema, table_name, privilege_type `; const [rows] = await connection.execute(query); let output = ''; if (rows.length === 0) { output = `Nenhum privilégio encontrado para '${user}'@'${host}'.`; } else { output += `## Privilégios de '${user}'@'${host}'\n\n`; output += '| Database | Tabela | Privilégio | Pode Conceder |\n'; output += '|----------|--------|------------|---------------|\n'; for (const row of rows) { output += `| ${row.table_schema} | ${row.table_name} | ${row.privilege_type} | ${row.is_grantable} |\n`; } output += `\n**Total de privilégios:** ${rows.length}`; } return output; } catch (error) { this.logger.error('Erro ao listar privilégios:', error); throw new Error(`Erro ao listar privilégios: ${error.message}`); } finally { if (connection) { await connection.end(); } } } async listRoles(connectionName = null) { let connection; try { connection = await this.getConnection(connectionName); const query = ` SELECT user as role_name, host, account_locked FROM mysql.user WHERE user LIKE '%_role_%' OR authentication_string = '' ORDER BY user `; const [rows] = await connection.execute(query); let output = ''; if (rows.length === 0) { output = 'Nenhum role encontrado.'; } else { output += '| Role | Host | Status |\n'; output += '|------|------|--------|\n'; for (const row of rows) { const status = row.account_locked === 'Y' ? 'Bloqueado' : 'Ativo'; output += `| ${row.role_name} | ${row.host} | ${status} |\n`; } output += `\n**Total de roles:** ${rows.length}`; } return output; } catch (error) { this.logger.error('Erro ao listar roles:', error); throw new Error(`Erro ao listar roles: ${error.message}`); } finally { if (connection) { await connection.end(); } } } // ===== MÉTODOS AUXILIARES ===== async userExists(connection, username, host) { try { const query = ` SELECT COUNT(*) FROM mysql.user WHERE user = ? AND host = ? `; const [rows] = await connection.execute(query, [username, host]); return rows[0]['COUNT(*)'] > 0; } catch (error) { return false; } } async roleExists(connection, roleName) { try { const query = ` SELECT COUNT(*) FROM mysql.user WHERE user = ? AND authentication_string = '' `; const [rows] = await connection.execute(query, [roleName]); return rows[0]['COUNT(*)'] > 0; } catch (error) { return false; } } // ===== VALIDAÇÕES DE SEGURANÇA ===== validateUsername(username) { if (!username || typeof username !== 'string') { throw new Error('Nome de usuário deve ser uma string válida'); } if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(username)) { throw new Error('Nome de usuário deve conter apenas letras, números e underscore, começando com letra'); } if (username.length > 16) { throw new Error('Nome de usuário não pode exceder 16 caracteres'); } } validatePassword(password) { if (!password || typeof password !== 'string') { throw new Error('Senha deve ser uma string válida'); } if (password.length < 8) { throw new Error('Senha deve ter pelo menos 8 caracteres'); } if (password.length > 128) { throw new Error('Senha não pode exceder 128 caracteres'); } } validatePrivileges(privileges) { if (!privileges || !Array.isArray(privileges) || privileges.length === 0) { throw new Error('Lista de privilégios é obrigatória'); } const validPrivileges = [ 'SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'RELOAD', 'SHUTDOWN', 'PROCESS', 'FILE', 'GRANT', 'REFERENCES', 'INDEX', 'ALTER', 'SHOW DATABASES', 'SUPER', 'CREATE TEMPORARY TABLES', 'LOCK TABLES', 'EXECUTE', 'REPLICATION SLAVE', 'REPLICATION CLIENT', 'CREATE VIEW', 'SHOW VIEW', 'CREATE ROUTINE', 'ALTER ROUTINE', 'CREATE USER', 'EVENT', 'TRIGGER', 'CREATE TABLESPACE', 'ALL', 'ALL PRIVILEGES' ]; for (const privilege of privileges) { if (!validPrivileges.includes(privilege.toUpperCase())) { throw new Error(`Privilégio inválido: ${privilege}`); } } } }