UNPKG

@iriseller/mcp-server

Version:

Model Context Protocol (MCP) server providing access to IRISeller's AI sales intelligence platform with 7 AI agents, multi-CRM integration, advanced sales workflows, email automation, Rosa demo functionality with action scoring, DNC compliance checking, G

766 lines (765 loc) 31.8 kB
export class EmailToolHandlers { apiService; // Debug logging utilities that respect debug flag debugLog = (message, ...args) => { if (this.apiService.config?.debug) { console.error(message, ...args); } }; errorLog = (message, ...args) => { // Always log errors to stderr (for critical issues only) console.error(message, ...args); }; constructor(apiService) { this.apiService = apiService; } /** * Handle automatic email response generation */ async handleEmailAutoRespond(params) { const { email, options = {} } = params; try { this.debugLog(`[MCP] Auto-responding to email from ${email.from}: "${email.subject}"`); // Step 1: Analyze email intent const intentAnalysis = await this.handleEmailIntentAnalysis({ emailContent: email.content, subject: email.subject, senderInfo: { email: email.from, name: email.fromName } }); // Step 2: Compile sales information using multiple agents const salesInfo = await this.handleCompileSalesInfo({ prospect: { email: email.from, name: email.fromName, company: await this.extractCompanyFromEmail(email.content) }, inquiry: { type: intentAnalysis.result?.inquiryType || 'general_inquiry', details: email.content, urgency: options.urgency || intentAnalysis.result?.urgency || 'medium' }, agentWorkflows: intentAnalysis.result?.recommendedWorkflows || [] }); // Step 3: Generate personalized response const response = await this.handleGenerateEmailResponse({ originalEmail: email, salesInfo: salesInfo, responseStyle: 'professional' }); // Step 4: Schedule follow-up if needed if (intentAnalysis.result?.needsFollowup) { await this.handleScheduleEmailFollowup({ prospect: { email: email.from, name: email.fromName }, followupType: intentAnalysis.result?.followupType || 'general', delay: { value: 3, unit: 'days' }, conditions: { ifNoResponse: true } }); } return { success: true, response: { content: response.content, htmlContent: response.htmlContent, agentsUsed: salesInfo.agentsUsed, salesInfo: salesInfo.data, confidence: intentAnalysis.result?.confidence || 0.5, followupScheduled: intentAnalysis.result?.needsFollowup || false } }; } catch (error) { this.errorLog('[MCP] Error in email auto-respond:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Analyze email intent and classify inquiry type */ async handleEmailIntentAnalysis(params) { const { emailContent, subject, senderInfo } = params; try { // Use AI to analyze email intent via agent execution const analysisResult = await this.apiService.executeAgent({ agent_name: 'rosa_sdr', input_data: { task: 'analyze_email_intent', content: emailContent, subject: subject, sender: senderInfo }, options: { timeout: 30, priority: 'medium' } }); // Classify inquiry type based on content analysis const inquiryType = this.classifyInquiryType(emailContent, subject); const urgency = this.determineUrgency(emailContent, subject); const recommendedWorkflows = this.getRecommendedWorkflows(inquiryType); // Handle forecast requests specially if (inquiryType === 'forecast_request') { return { success: true, result: { inquiryType: inquiryType, urgency: urgency, recommendedWorkflows: recommendedWorkflows, confidence: 0.95, needsFollowup: false, followupType: 'none', aiAnalysis: { message: 'Forecast request detected. Route to analytics/reporting tools.', recommended_tools: ['forecast_sales', 'query_crm'], status: 'ready' } } }; } return { success: true, result: { inquiryType: inquiryType, urgency: urgency, recommendedWorkflows: recommendedWorkflows, confidence: 0.8, // Default confidence since API doesn't return this needsFollowup: this.shouldScheduleFollowup(inquiryType), followupType: this.getFollowupType(inquiryType), aiAnalysis: analysisResult.results || analysisResult } }; } catch (error) { this.errorLog('[MCP] Error in email intent analysis:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Compile comprehensive sales information using multiple agents */ async handleCompileSalesInfo(params) { const { prospect, inquiry, agentWorkflows = [] } = params; try { this.debugLog(`[MCP] Compiling sales info for ${prospect.email} with workflows: ${agentWorkflows.join(', ')}`); const salesData = { prospect: prospect, company: {}, opportunities: [], recommendations: [], agentsUsed: [] }; // Execute agent workflows based on inquiry type for (const workflow of agentWorkflows) { try { const agentResult = await this.executeAgentWorkflow(workflow, prospect, inquiry); const agentResultAny = agentResult; if (agentResultAny.status === 'success' || agentResultAny.success) { salesData.agentsUsed.push(workflow); // Merge agent-specific data from results const results = agentResultAny.results || agentResultAny.data; if (workflow === 'qualification') { salesData.qualification = results?.qualification || {}; } else if (workflow === 'research') { salesData.research = results?.research || {}; } else if (workflow === 'personalization') { salesData.personalization = results?.personalization || {}; } else if (workflow === 'objection_handling') { salesData.objectionHandling = results?.objectionHandling || {}; } } } catch (agentError) { this.debugLog(`[MCP] Error in ${workflow} workflow:`, agentError); } } // Research company information if (prospect.company) { try { const companyResearch = await this.apiService.researchCompany({ company_name: prospect.company, research_depth: 'basic', include_competitors: false, include_news: true, include_financials: false }); if (companyResearch.success) { salesData.company = companyResearch.data; salesData.agentsUsed.push('company_research'); } } catch (researchError) { this.debugLog('[MCP] Company research not available:', researchError); } } // Lead qualification using the built-in method try { const qualification = await this.apiService.qualifyLead({ contact_name: prospect.name, company_name: prospect.company || 'Unknown', email: prospect.email, additional_context: `Email inquiry: ${inquiry.type}` }); if (qualification.status === 'success') { salesData.qualification = qualification.results; salesData.agentsUsed.push('lead_qualification'); } } catch (qualError) { this.debugLog('[MCP] Lead qualification not available:', qualError); } return { success: true, data: salesData, agentsUsed: salesData.agentsUsed }; } catch (error) { this.errorLog('[MCP] Error compiling sales info:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Generate personalized email response */ async handleGenerateEmailResponse(params) { const { originalEmail, salesInfo, responseStyle = 'professional' } = params; try { // Use personalization agent to generate response const responseResult = await this.apiService.personalizeOutreach({ prospect_data: { name: originalEmail.fromName || 'Prospect', company: originalEmail.from.split('@')[1] || 'Unknown Company', email: originalEmail.from }, message_type: 'email', tone: responseStyle, context: JSON.stringify({ original_email: originalEmail, sales_info: salesInfo, style: responseStyle }) }); if (responseResult.success) { return { success: true, content: responseResult.data?.message || responseResult.data?.response || 'Thank you for your email. We will get back to you soon.', htmlContent: responseResult.data?.html_message || null, agentsUsed: ['personalization'] }; } else { throw new Error('Personalization service failed'); } } catch (error) { this.errorLog('[MCP] Error generating email response:', error); // Fallback response return { success: true, content: `Thank you for your email regarding "${originalEmail.subject}". We appreciate your interest and will get back to you within 24 hours.`, htmlContent: null, agentsUsed: ['fallback'] }; } } /** * Schedule email follow-up */ async handleScheduleEmailFollowup(params) { const { prospect, followupType, delay, conditions } = params; try { // Schedule follow-up - placeholder implementation const scheduleResult = { success: true, data: { followup_id: `followup_${Date.now()}`, scheduled_date: new Date(Date.now() + delay.value * 24 * 60 * 60 * 1000).toISOString(), prospect: prospect, type: followupType } }; return { success: true, followupId: scheduleResult.data?.followup_id, scheduledFor: this.calculateFollowupDate(delay), conditions: conditions }; } catch (error) { console.error('[MCP] Error scheduling email followup:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Track email analytics */ async handleEmailAnalytics(params) { const { email, intentAnalysis, response } = params; try { // Analytics tracking - placeholder implementation const analyticsResult = { success: true, data: { event_id: `analytics_${Date.now()}`, tracked: true } }; return { success: true, analyticsId: analyticsResult.data?.event_id, tracked: true }; } catch (error) { console.error('[MCP] Error tracking email analytics:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } // Private helper methods async executeAgentWorkflow(workflow, prospect, inquiry) { try { // Map workflow names to valid agent names const agentMap = { 'qualification': 'rosa_sdr', 'research': 'prospecting', 'personalization': 'personalization', 'objection_handling': 'objection_handling', 'nurturing': 'nurturing', 'sequence': 'sequence', 'social_selling': 'social_selling' }; const agentName = agentMap[workflow] || 'rosa_sdr'; return await this.apiService.executeAgent({ agent_name: agentName, input_data: { prospect: prospect, inquiry: inquiry, context: `Email-based ${workflow} workflow` }, options: { timeout: 30, priority: 'medium' } }); } catch (error) { console.error(`[MCP] Error executing ${workflow}:`, error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error', data: { prospect, inquiry } }; } } async extractCompanyFromEmail(content) { // Simple company extraction - in production you'd use NLP const emailDomainMatch = content.match(/@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/); if (emailDomainMatch) { return emailDomainMatch[1].replace(/^www\./, ''); } return 'Unknown Company'; } classifyInquiryType(content, subject) { const text = (content + ' ' + subject).toLowerCase(); // Check for forecast/analytics requests FIRST if (text.includes('forecast') || text.includes('projection') || text.includes('pipeline') || text.includes('revenue') || text.match(/\bq[1-4]\b/) || text.includes('quarterly') || text.includes('analytics') || text.includes('metrics')) return 'forecast_request'; if (text.includes('demo') || text.includes('trial')) return 'demo_request'; if (text.includes('price') || text.includes('cost') || text.includes('pricing')) return 'pricing_question'; if (text.includes('meeting') || text.includes('call') || text.includes('schedule')) return 'meeting_request'; if (text.includes('partner') || text.includes('integration')) return 'partnership_inquiry'; if (text.includes('support') || text.includes('help') || text.includes('issue')) return 'support_request'; if (text.includes('follow') || text.includes('checking in')) return 'follow_up'; if (text.includes('buy') || text.includes('purchase') || text.includes('interested')) return 'sales_inquiry'; return 'information_request'; } determineUrgency(content, subject) { const text = (content + ' ' + subject).toLowerCase(); if (text.includes('urgent') || text.includes('asap') || text.includes('immediately')) return 'high'; if (text.includes('soon') || text.includes('quickly')) return 'medium'; return 'low'; } getRecommendedWorkflows(inquiryType) { const workflowMap = { 'forecast_request': ['analytics', 'reporting'], 'sales_inquiry': ['qualification', 'research', 'personalization'], 'demo_request': ['qualification', 'personalization'], 'pricing_question': ['qualification', 'research'], 'meeting_request': ['qualification', 'personalization'], 'partnership_inquiry': ['research', 'personalization'], 'support_request': ['qualification'], 'follow_up': ['personalization'], 'information_request': ['research', 'personalization'] }; return workflowMap[inquiryType] || ['personalization']; } shouldScheduleFollowup(inquiryType) { const followupTypes = ['sales_inquiry', 'demo_request', 'pricing_question', 'meeting_request']; return followupTypes.includes(inquiryType); } getFollowupType(inquiryType) { const followupMap = { 'sales_inquiry': 'gentle_reminder', 'demo_request': 'demo_followup', 'pricing_question': 'pricing_followup', 'meeting_request': 'meeting_followup' }; return followupMap[inquiryType] || 'general'; } calculateFollowupDate(delay) { const multipliers = { 'hours': 60 * 60 * 1000, 'days': 24 * 60 * 60 * 1000, 'weeks': 7 * 24 * 60 * 60 * 1000 }; const multiplier = multipliers[delay.unit] || multipliers['days']; return new Date(Date.now() + delay.value * multiplier).toISOString(); } /** * Handle email detection - Monitor for new incoming emails */ async handleEmailDetection(params) { const { accountType = 'zoho', filters = {}, limit = 10, includeContent = true, markAsRead = false } = params; try { console.error(`[MCP] Detecting emails from ${accountType} account with filters:`, filters); // Call the backend API to detect new emails const response = await this.apiService.makeRequest('GET', '/api/email-automation/detect-emails', { params: { accountType, unreadOnly: filters.unreadOnly ?? true, fromDomain: filters.fromDomain, keywords: filters.keywords ? filters.keywords.join(',') : undefined, excludeAutomated: filters.excludeAutomated ?? true, sinceTimestamp: filters.sinceTimestamp, limit, includeContent, markAsRead } }); if (response.success) { return { success: true, emails: response.data?.emails || [], count: response.data?.count || 0, hasMore: response.data?.hasMore || false, nextTimestamp: response.data?.nextTimestamp, accountType: accountType }; } else { throw new Error(response.error || 'Email detection failed'); } } catch (error) { console.error('[MCP] Error in email detection:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Handle email sending - Send emails via automation system */ async handleEmailSending(params) { const { to, subject, content, template, options = {} } = params; try { console.error(`[MCP] Sending email to ${Array.isArray(to) ? to.join(', ') : to}: "${subject}"`); let emailData = { to, subject, options: { replyTo: options.replyTo, priority: options.priority || 'normal', trackOpens: options.trackOpens ?? true, trackClicks: options.trackClicks ?? true, scheduleTime: options.scheduleTime } }; // Handle template-based emails if (template) { emailData.template = { type: template.type, data: template.data || {} }; } else if (content) { // Handle direct content emails - support both string and object formats if (typeof content === 'string') { // If content is passed as a string, use it as both text and html emailData.content = { text: content, html: content.replace(/\n/g, '<br>') }; } else { // If content is an object, use the text and html properties emailData.content = { text: content.text, html: content.html }; } } else { throw new Error('Either content or template must be provided'); } // Call the backend API to send email using the existing custom email endpoint let apiResponse; if (template) { // Use template-based sending (would need template processing) apiResponse = await this.apiService.makeRequest('POST', '/api/email/custom', { body: { to: Array.isArray(to) ? to[0] : to, // Custom endpoint handles single recipient subject: subject, template: template.data?.html || '<html><body>{{content}}</body></html>', data: { content: template.data?.content || 'Template content', ...template.data } } }); } else { // Use the send endpoint for direct content apiResponse = await this.apiService.makeRequest('POST', '/api/email/send', { body: { emails: (Array.isArray(to) ? to : [to]).map(recipient => ({ recipient: recipient, subject: subject, body: content?.html || content?.text || 'Email content' })), campaignName: `MCP-${Date.now()}`, scheduledTime: options.scheduleTime } }); } const response = apiResponse; if (response.success) { return { success: true, messageId: response.data?.messageId, status: response.data?.status || 'sent', recipients: Array.isArray(to) ? to : [to], scheduledFor: options.scheduleTime || null, trackingEnabled: { opens: options.trackOpens ?? true, clicks: options.trackClicks ?? true } }; } else { throw new Error(response.error || 'Email sending failed'); } } catch (error) { console.error('[MCP] Error in email sending:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Handle email status monitoring */ async handleEmailStatus(params) { const { includeStats = true, includeQueue = false, timeRange } = params; try { console.error(`[MCP] Getting email automation status`); const response = await this.apiService.makeRequest('GET', '/api/email-automation/status', { params: { includeStats, includeQueue, startTime: timeRange?.start, endTime: timeRange?.end } }); if (response.success) { return { success: true, status: response.data?.status || 'unknown', services: response.data?.services || {}, stats: includeStats ? response.data?.stats || {} : undefined, queue: includeQueue ? response.data?.queue || {} : undefined, lastUpdated: response.data?.lastUpdated || new Date().toISOString() }; } else { throw new Error(response.error || 'Failed to get email status'); } } catch (error) { console.error('[MCP] Error getting email status:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Handle email template management */ async handleEmailTemplates(params) { const { action, templateId, template } = params; try { console.error(`[MCP] Managing email templates - action: ${action}`); let endpoint = '/api/email-automation/templates'; let method = 'GET'; let requestData = {}; switch (action) { case 'list': method = 'GET'; break; case 'get': if (!templateId) throw new Error('templateId is required for get action'); endpoint = `${endpoint}/${templateId}`; method = 'GET'; break; case 'create': if (!template) throw new Error('template data is required for create action'); method = 'POST'; requestData.body = template; break; case 'update': if (!templateId || !template) throw new Error('templateId and template data are required for update action'); endpoint = `${endpoint}/${templateId}`; method = 'PUT'; requestData.body = template; break; case 'delete': if (!templateId) throw new Error('templateId is required for delete action'); endpoint = `${endpoint}/${templateId}`; method = 'DELETE'; break; default: throw new Error(`Invalid action: ${action}`); } const response = await this.apiService.makeRequest(method, endpoint, requestData); if (response.success) { return { success: true, action: action, data: response.data, templateId: templateId }; } else { throw new Error(response.error || `Failed to ${action} email template`); } } catch (error) { console.error('[MCP] Error managing email templates:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Handle email campaign management */ async handleEmailCampaigns(params) { const { action, campaignId, campaign } = params; try { console.error(`[MCP] Managing email campaigns - action: ${action}`); let endpoint = '/api/email-automation/campaigns'; let method = 'GET'; let requestData = {}; switch (action) { case 'list': method = 'GET'; break; case 'create': if (!campaign) throw new Error('campaign data is required for create action'); method = 'POST'; requestData.body = campaign; break; case 'start': if (!campaignId) throw new Error('campaignId is required for start action'); endpoint = `${endpoint}/${campaignId}/start`; method = 'POST'; break; case 'pause': if (!campaignId) throw new Error('campaignId is required for pause action'); endpoint = `${endpoint}/${campaignId}/pause`; method = 'POST'; break; case 'stop': if (!campaignId) throw new Error('campaignId is required for stop action'); endpoint = `${endpoint}/${campaignId}/stop`; method = 'POST'; break; case 'get_stats': if (!campaignId) throw new Error('campaignId is required for get_stats action'); endpoint = `${endpoint}/${campaignId}/stats`; method = 'GET'; break; default: throw new Error(`Invalid action: ${action}`); } const response = await this.apiService.makeRequest(method, endpoint, requestData); if (response.success) { return { success: true, action: action, data: response.data, campaignId: campaignId }; } else { throw new Error(response.error || `Failed to ${action} email campaign`); } } catch (error) { console.error('[MCP] Error managing email campaigns:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } }