UNPKG

@tiflux/mcp

Version:

TiFlux MCP Server - Model Context Protocol integration for Claude Code and other AI clients

397 lines (340 loc) 11.2 kB
/** * ClientHandler - Handler limpo para operações de cliente * * Responsabilidades: * - Receber requests MCP e validar parâmetros básicos * - Delegar lógica de negócio para ClientService * - Aplicar formatação de resposta via middleware * - Error handling padronizado * - Logging de requests/responses */ class ClientHandler { constructor(container) { this.container = container; this.logger = container.resolve('logger'); this.clientService = container.resolve('clientService'); this.domainValidator = container.resolve('domainValidator'); this.responseFormatter = null; // Lazy loading } /** * Handler para buscar clientes por nome */ async handleSearchClient(args) { const timer = this.logger.startTimer('handle_search_client'); try { this.logger.info('Handling search client request', { hasClientName: !!args.client_name, clientNameLength: args.client_name?.length || 0 }); // Validação básica de parâmetros if (!args.client_name) { throw new ValidationError('client_name é obrigatório'); } // Validação usando domain validator await this.domainValidator.validateClientSearch(args.client_name); // Delega para o domain service const result = await this.clientService.searchClientsByName(args.client_name); timer(); return this._formatSearchResults(result); } catch (error) { timer(); this.logger.error('Failed to handle search client', { clientName: args.client_name?.substring(0, 50), error: error.message }); return this._formatErrorResponse(error, 'search_client'); } } /** * Handler para buscar cliente por ID */ async handleGetClient(args) { const timer = this.logger.startTimer('handle_get_client'); try { this.logger.info('Handling get client request', { clientId: args.client_id }); // Validação básica de parâmetros if (!args.client_id) { throw new ValidationError('client_id é obrigatório'); } const clientId = parseInt(args.client_id, 10); if (isNaN(clientId) || clientId <= 0) { throw new ValidationError('client_id deve ser um número válido maior que zero'); } // Delega para o domain service const result = await this.clientService.getClientById(clientId); timer(); return this._formatClientResult(result); } catch (error) { timer(); this.logger.error('Failed to handle get client', { clientId: args.client_id, error: error.message }); return this._formatErrorResponse(error, 'get_client'); } } /** * Handler para resolver client_name para client_id */ async handleResolveClientName(args) { const timer = this.logger.startTimer('handle_resolve_client_name'); try { this.logger.info('Handling resolve client name request', { hasClientName: !!args.client_name, clientNameLength: args.client_name?.length || 0 }); // Validação básica de parâmetros if (!args.client_name) { throw new ValidationError('client_name é obrigatório'); } // Validação usando domain validator await this.domainValidator.validateClientSearch(args.client_name); // Delega para o domain service const clientId = await this.clientService.resolveClientNameToId(args.client_name); timer(); return this._formatResolveResult(clientId, args.client_name); } catch (error) { timer(); this.logger.error('Failed to handle resolve client name', { clientName: args.client_name?.substring(0, 50), error: error.message }); return this._formatErrorResponse(error, 'resolve_client_name'); } } /** * Formata resultado da busca de clientes */ _formatSearchResults(clients) { if (!clients || clients.length === 0) { return { content: [ { type: 'text', text: '🔍 **Nenhum cliente encontrado**\n\n' + 'Não foram encontrados clientes que correspondam aos critérios de busca.\n\n' + '*Verifique a grafia do nome e tente novamente.*' } ] }; } let content = `🏢 **Encontrados ${clients.length} cliente(s)**\n\n`; clients.forEach((client, index) => { content += `**${index + 1}. ${client.name || 'Nome não informado'}** (ID: ${client.id})\n`; if (client.email) { content += ` 📧 Email: ${client.email}\n`; } if (client.phone) { content += ` 📱 Telefone: ${client.phone}\n`; } if (client.document) { content += ` 📄 Documento: ${client.document}\n`; } if (client.company_name) { content += ` 🏢 Empresa: ${client.company_name}\n`; } // Status do cliente const status = client.active ? '✅ Ativo' : '❌ Inativo'; content += ` 📊 Status: ${status}\n`; content += '\n'; }); content += `*Total encontrado: ${clients.length} cliente(s)*`; return { content: [ { type: 'text', text: content } ] }; } /** * Formata resultado de cliente individual */ _formatClientResult(client) { if (!client) { return { content: [ { type: 'text', text: '🔍 **Cliente não encontrado**\n\n' + 'O cliente solicitado não foi encontrado ou não está acessível.\n\n' + '*Verifique o ID do cliente e tente novamente.*' } ] }; } let content = `🏢 **Detalhes do Cliente**\n\n`; content += `**Nome:** ${client.name || 'Não informado'}\n`; content += `**ID:** ${client.id}\n\n`; // Dados de contato if (client.email || client.phone) { content += `📞 **Contato**\n`; if (client.email) { content += `• Email: ${client.email}\n`; } if (client.phone) { content += `• Telefone: ${client.phone}\n`; } content += '\n'; } // Documentos if (client.document || client.company_document) { content += `📄 **Documentos**\n`; if (client.document) { content += `• Documento: ${client.document}\n`; } if (client.company_document) { content += `• CNPJ: ${client.company_document}\n`; } content += '\n'; } // Dados empresariais if (client.company_name || client.business_activity) { content += `🏢 **Dados Empresariais**\n`; if (client.company_name) { content += `• Razão Social: ${client.company_name}\n`; } if (client.business_activity) { content += `• Atividade: ${client.business_activity}\n`; } content += '\n'; } // Endereço if (client.address_full) { content += `📍 **Endereço**\n`; content += `${client.address_full}\n\n`; } // Status e metadados content += `📊 **Status**\n`; content += `• Status: ${client.active ? '✅ Ativo' : '❌ Inativo'}\n`; content += `• Tipo: ${client.client_type || 'Não especificado'}\n`; if (client.created_at) { content += `• Cadastrado em: ${new Date(client.created_at).toLocaleDateString('pt-BR')}\n`; } // URLs úteis if (client.client_url) { content += `\n🔗 **Links Úteis**\n`; content += `• [Ver Cliente no TiFlux](${client.client_url})\n`; if (client.portal_url) { content += `• [Portal do Cliente](${client.portal_url})\n`; } } return { content: [ { type: 'text', text: content } ] }; } /** * Formata resultado da resolução de nome */ _formatResolveResult(clientId, clientName) { if (!clientId) { return { content: [ { type: 'text', text: `🔍 **Cliente "${clientName}" não encontrado**\n\n` + 'Não foi possível encontrar um cliente com este nome.\n\n' + '*Possíveis soluções:*\n' + '• Verifique a grafia do nome\n' + '• Use apenas parte do nome\n' + '• Tente buscar por outros critérios (email, documento)\n\n' + '*Use o comando search_client para ver clientes disponíveis.*' } ] }; } return { content: [ { type: 'text', text: `✅ **Cliente resolvido com sucesso**\n\n` + `**Nome:** "${clientName}"\n` + `**ID:** ${clientId}\n\n` + `*Este ID pode ser usado para criar tickets ou outras operações que requerem client_id.*` } ] }; } /** * Formata resposta de erro padronizada */ _formatErrorResponse(error, operation) { // Tipos de erro conhecidos const errorMap = { ValidationError: '❌', NotFoundError: '🔍', APIError: '🔌', TimeoutError: '⏱️', NetworkError: '🌐' }; const icon = errorMap[error.constructor.name] || '❌'; const operationText = { search_client: 'buscar clientes', get_client: 'buscar cliente', resolve_client_name: 'resolver nome de cliente' }; let errorMessage = error.message; // Adiciona contexto específico para alguns erros if (error.constructor.name === 'ValidationError') { errorMessage = `Dados inválidos: ${error.message}`; } else if (error.constructor.name === 'NotFoundError') { errorMessage = `Cliente não encontrado: ${error.message}`; } else if (error.constructor.name === 'APIError') { errorMessage = `Erro na API: ${error.message}`; if (error.statusCode) { errorMessage += ` (HTTP ${error.statusCode})`; } } return { content: [ { type: 'text', text: `**${icon} Erro ao ${operationText[operation] || 'processar solicitação'}**\n\n` + `**Erro:** ${errorMessage}\n\n` + `*Verifique os parâmetros fornecidos e tente novamente.*` } ] }; } /** * Lazy loading do ResponseFormatter */ _getResponseFormatter() { if (!this.responseFormatter) { this.responseFormatter = this.container.resolve('responseFormatter'); } return this.responseFormatter; } /** * Estatísticas do handler */ getStats() { return { operations: ['search_client', 'get_client', 'resolve_client_name'], features: { domain_service_integration: true, validation_support: true, error_formatting: true, request_logging: true, performance_timing: true, result_formatting: true }, dependencies: { clientService: !!this.clientService, domainValidator: !!this.domainValidator, logger: !!this.logger } }; } } // Import das classes de erro const { ValidationError } = require('../../utils/errors'); module.exports = ClientHandler;