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 (detection/sending/campaigns), and robust asynchronous agent execution h

985 lines 106 kB
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; import { WebSearchService } from '../services/web-search-service.js'; import { StockDataService } from '../services/stock-data-service.js'; import { EmailToolHandlers } from './email-handlers.js'; import { CRMQuerySchema, CompanyResearchRequestSchema, PersonalizationRequestSchema, AgentExecutionRequestSchema, WorkflowExecutionRequestSchema } from '../types/index.js'; export class ToolHandlers { apiService; webSearchService; stockDataService; emailHandlers; userToken; // Store user token for CRM operations // 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; this.webSearchService = new WebSearchService(); this.stockDataService = new StockDataService(); this.emailHandlers = new EmailToolHandlers(apiService); } // Method to set user token for authenticated operations setUserToken(token) { this.userToken = token; this.apiService.setUserToken(token); } async handleToolCall(request) { try { switch (request.params.name) { case 'qualify_lead': return await this.handleQualifyLead(request); case 'query_crm': return await this.handleQueryCRM(request); case 'research_company': return await this.handleResearchCompany(request); case 'execute_agent': return await this.handleExecuteAgent(request); case 'execute_workflow': return await this.handleExecuteWorkflow(request); case 'personalize_outreach': return await this.handlePersonalizeOutreach(request); case 'get_agent_results': return await this.handleGetAgentResults(request); case 'wait_for_agent_completion': return await this.handleWaitForAgentCompletion(request); case 'forecast_sales': return await this.handleForecastSales(request); case 'list_agents': return await this.handleListAgents(request); case 'list_workflows': return await this.handleListWorkflows(request); case 'health_check': return await this.handleHealthCheck(request); case 'web_search': return await this.handleWebSearch(request); case 'get_stock_data': return await this.handleGetStockData(request); case 'prepare_meeting': return await this.handleMeetingPrep(request); // Email Integration Tools case 'email_auto_respond': return await this.handleEmailAutoRespond(request); case 'analyze_email_intent': return await this.handleEmailIntentAnalysis(request); case 'compile_sales_info': return await this.handleCompileSalesInfo(request); case 'generate_email_response': return await this.handleGenerateEmailResponse(request); case 'schedule_email_followup': return await this.handleScheduleEmailFollowup(request); case 'get_email_analytics': return await this.handleEmailAnalytics(request); // New Email Detection & Sending Tools case 'detect_emails': return await this.handleDetectEmails(request); case 'send_email': return await this.handleSendEmail(request); case 'get_email_status': return await this.handleGetEmailStatus(request); case 'manage_email_templates': return await this.handleManageEmailTemplates(request); case 'manage_email_campaigns': return await this.handleManageEmailCampaigns(request); default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); } } catch (error) { if (error instanceof McpError) { throw error; } this.errorLog(`[MCP] Tool execution error for ${request.params.name}:`, error); throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleQualifyLead(request) { const args = request.params.arguments || {}; // Validate required fields if (!args.contact_name || !args.company_name) { throw new McpError(ErrorCode.InvalidParams, 'contact_name and company_name are required'); } try { // For now, skip database creation and let CrewAI handle execution tracking // The email automation system should work with CrewAI execution_ids this.debugLog(`[MCP] Processing qualify_lead for: ${args.contact_name} at ${args.company_name}`); const result = await this.apiService.qualifyLead({ contact_name: args.contact_name, company_name: args.company_name, title: args.title, industry: args.industry, email: args.email, phone: args.phone, additional_context: args.additional_context }); // Handle both success and error cases if (result.status === 'error') { return { content: [ { type: 'text', text: JSON.stringify({ tool: 'qualify_lead', execution_id: result.execution_id, status: 'error', error: result.error || 'Lead qualification failed', contact: args.contact_name, company: args.company_name, timestamp: new Date().toISOString() }, null, 2) } ] }; } return { content: [ { type: 'text', text: JSON.stringify({ tool: 'qualify_lead', execution_id: result.execution_id, // Use CrewAI execution_id for follow-up tracking status: result.status, lead_qualification: { contact: args.contact_name, company: args.company_name, qualification_score: result.results?.enhanced_qualification?.enhanced_score || result.results?.bant_analysis?.overall_score || 0, qualification_status: result.results?.enhanced_qualification?.qualification_status || result.results?.bant_analysis?.qualification_status || 'Unknown', bant_breakdown: result.results?.bant_analysis || {}, research_insights: result.results?.research_insights || {}, recommendations: result.results?.recommendations || [], next_actions: result.results?.next_actions || [], revenue_potential: result.results?.revenue_potential || {} }, execution_time: result.execution_time, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] Lead qualification error:', error); throw new McpError(ErrorCode.InternalError, `Lead qualification failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleQueryCRM(request) { const args = request.params.arguments || {}; // Validate and parse the query const parseResult = CRMQuerySchema.safeParse(args); if (!parseResult.success) { throw new McpError(ErrorCode.InvalidParams, `Invalid CRM query parameters: ${parseResult.error.message}`); } // Smart sorting logic: For leads queries without explicit sort_by, default to value-based sorting const queryData = { ...parseResult.data }; const needsValueBasedSorting = queryData.entity_type === 'leads' && !queryData.sort_by; let originalLimit = queryData.limit; if (needsValueBasedSorting) { // For value-based sorting, fetch more records to sort client-side // since CRM Connect Gateway may not support AnnualRevenue sorting queryData.limit = Math.max(50, queryData.limit || 10); // Fetch at least 50 records this.debugLog(`[MCP] Fetching ${queryData.limit} leads for client-side value sorting (original limit: ${originalLimit})`); // Remove sort_by to let CRM return records in default order const modifiedQuery = { ...queryData }; delete modifiedQuery.sort_by; delete modifiedQuery.sort_order; Object.assign(queryData, modifiedQuery); } try { // Pass user token to CRM query for user-specific API key access const result = await this.apiService.queryCRM(queryData, this.userToken); let finalData = result.data; let finalQuery = queryData; // Apply client-side sorting if needed if (needsValueBasedSorting && result.success && Array.isArray(result.data)) { this.debugLog(`[MCP] Applying client-side sorting to ${result.data.length} leads by AnnualRevenue`); // Sort by AnnualRevenue (descending), handling various field name formats const sortedData = result.data.sort((a, b) => { const aRevenue = parseFloat(a.AnnualRevenue || a.annualRevenue || a.annual_revenue || 0); const bRevenue = parseFloat(b.AnnualRevenue || b.annualRevenue || b.annual_revenue || 0); return bRevenue - aRevenue; // Descending order }); // Limit to original requested amount finalData = sortedData.slice(0, originalLimit || 10); // Update query info to reflect what we actually did finalQuery = { ...queryData, sort_by: 'AnnualRevenue', sort_order: 'desc', limit: originalLimit }; const topLead = finalData[0]; const leadName = topLead ? `${topLead.FirstName || topLead.firstName || topLead.name || 'Unknown'} ${topLead.LastName || topLead.lastName || ''}`.trim() : 'None'; const leadRevenue = topLead ? (topLead.AnnualRevenue || topLead.annualRevenue || 0) : 0; this.debugLog(`[MCP] Client-side sorting complete. Top lead: ${leadName} ($${leadRevenue})`); } return { content: [ { type: 'text', text: JSON.stringify({ tool: 'query_crm', query: finalQuery, success: result.success, data: finalData, count: Array.isArray(finalData) ? finalData.length : 0, message: result.message, error: result.error, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] CRM query error:', error); throw new McpError(ErrorCode.InternalError, `CRM query failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleResearchCompany(request) { const args = request.params.arguments || {}; // Validate and parse the research request const parseResult = CompanyResearchRequestSchema.safeParse(args); if (!parseResult.success) { throw new McpError(ErrorCode.InvalidParams, `Invalid company research parameters: ${parseResult.error.message}`); } try { const result = await this.apiService.researchCompany(parseResult.data); return { content: [ { type: 'text', text: JSON.stringify({ tool: 'research_company', company: parseResult.data.company_name, research_depth: parseResult.data.research_depth, success: result.success, research_data: result.data, message: result.message, error: result.error, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] Company research error:', error); throw new McpError(ErrorCode.InternalError, `Company research failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleExecuteAgent(request) { const args = request.params.arguments || {}; // Validate and parse the agent execution request const parseResult = AgentExecutionRequestSchema.safeParse(args); if (!parseResult.success) { throw new McpError(ErrorCode.InvalidParams, `Invalid agent execution parameters: ${parseResult.error.message}`); } try { // First validate that the agent exists const availableAgents = await this.apiService.getAvailableAgents(); if (!availableAgents.success) { this.debugLog('[MCP] Could not validate agent availability, proceeding with execution'); } else { const validAgents = availableAgents.data?.map((agent) => agent.name) || []; if (validAgents.length > 0 && !validAgents.includes(parseResult.data.agent_name)) { throw new McpError(ErrorCode.InvalidParams, `Agent '${parseResult.data.agent_name}' not found. Available agents: ${validAgents.join(', ')}`); } } const result = await this.apiService.executeAgent(parseResult.data); // If the agent is running/pending, provide guidance on how to get results if (result.status === 'pending' || result.status === 'running') { return { content: [ { type: 'text', text: JSON.stringify({ tool: 'execute_agent', agent_name: result.agent_name, execution_id: result.execution_id, status: result.status, results: result.results, execution_time: result.execution_time, error: result.error, next_steps: { message: "Agent execution is running in the background. Use wait_for_agent_completion to get the final results.", recommended_action: `Use wait_for_agent_completion with execution_id: "${result.execution_id}" to wait for and receive the final results`, alternative_action: `Or use get_agent_results with execution_id: "${result.execution_id}" and wait_for_completion: true for shorter waits`, polling_suggestion: "You can also check periodically without waiting by using get_agent_results with wait_for_completion: false" }, timestamp: new Date().toISOString() }, null, 2) } ] }; } return { content: [ { type: 'text', text: JSON.stringify({ tool: 'execute_agent', agent_name: result.agent_name, execution_id: result.execution_id, status: result.status, results: result.results, execution_time: result.execution_time, error: result.error, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] Agent execution error:', error); if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Agent execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleExecuteWorkflow(request) { const args = request.params.arguments || {}; // Validate and parse the workflow execution request const parseResult = WorkflowExecutionRequestSchema.safeParse(args); if (!parseResult.success) { throw new McpError(ErrorCode.InvalidParams, `Invalid workflow execution parameters: ${parseResult.error.message}`); } try { // First validate that the workflow exists const availableWorkflows = await this.apiService.getAvailableWorkflows(); if (!availableWorkflows.success) { this.debugLog('[MCP] Could not validate workflow availability, proceeding with execution'); } else { const validWorkflows = availableWorkflows.data?.map((workflow) => workflow.name) || []; if (validWorkflows.length > 0 && !validWorkflows.includes(parseResult.data.workflow_type)) { throw new McpError(ErrorCode.InvalidParams, `Workflow '${parseResult.data.workflow_type}' not found. Available workflows: ${validWorkflows.join(', ')}`); } } const result = await this.apiService.executeWorkflow(parseResult.data); return { content: [ { type: 'text', text: JSON.stringify({ tool: 'execute_workflow', workflow_type: result.workflow_type, workflow_id: result.workflow_id, status: result.status, results: result.results, agents_executed: result.agents_executed, execution_time: result.execution_time, error: result.error, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] Workflow execution error:', error); if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Workflow execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handlePersonalizeOutreach(request) { const args = request.params.arguments || {}; // Validate and parse the personalization request const parseResult = PersonalizationRequestSchema.safeParse(args); if (!parseResult.success) { throw new McpError(ErrorCode.InvalidParams, `Invalid personalization parameters: ${parseResult.error.message}`); } try { const result = await this.apiService.personalizeOutreach(parseResult.data); return { content: [ { type: 'text', text: JSON.stringify({ tool: 'personalize_outreach', prospect: parseResult.data.prospect_data, message_type: parseResult.data.message_type, tone: parseResult.data.tone, success: result.success, personalized_content: result.data, message: result.message, error: result.error, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] Personalization error:', error); throw new McpError(ErrorCode.InternalError, `Personalization failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleGetAgentResults(request) { const args = request.params.arguments || {}; if (!args.execution_id || typeof args.execution_id !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'execution_id is required and must be a string'); } const executionId = args.execution_id; const waitForCompletion = args.wait_for_completion || false; const timeoutSeconds = Math.min(Math.max(args.timeout_seconds || 30, 5), 300); try { let results = await this.apiService.getCompletedExecution(executionId); // If no results found and wait_for_completion is true, poll for results if (!results && waitForCompletion) { this.debugLog(`[MCP] Waiting for agent execution completion: ${executionId}`); const startTime = Date.now(); const pollInterval = 2000; // 2 seconds while (!results && (Date.now() - startTime) < (timeoutSeconds * 1000)) { await new Promise(resolve => setTimeout(resolve, pollInterval)); results = await this.apiService.getCompletedExecution(executionId); if (results && (results.status === 'completed' || results.status === 'failed')) { break; } } } if (!results) { return { content: [ { type: 'text', text: JSON.stringify({ tool: 'get_agent_results', execution_id: executionId, status: 'not_found', message: 'Execution not found or still running. Use wait_for_completion=true to wait for results.', timestamp: new Date().toISOString() }, null, 2) } ] }; } return { content: [ { type: 'text', text: JSON.stringify({ tool: 'get_agent_results', execution_id: executionId, status: results.status, agent_name: results.agent_name || results.agentName, results: results.results || results.result, result_summary: results.result_summary || results.resultSummary, confidence: results.confidence, execution_time: results.execution_time || results.executionTime, completed_at: results.completed_at || results.completedAt, created_at: results.created_at || results.createdAt, retrieved: results.retrieved, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] Get agent results error:', error); if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Failed to get agent results: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleWaitForAgentCompletion(request) { const args = request.params.arguments || {}; if (!args.execution_id || typeof args.execution_id !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'execution_id is required and must be a string'); } const executionId = args.execution_id; const timeoutSeconds = Math.min(Math.max(args.timeout_seconds || 120, 10), 600); const checkIntervalSeconds = Math.min(Math.max(args.check_interval_seconds || 5, 2), 30); try { this.debugLog(`[MCP] Waiting for agent execution completion: ${executionId} (timeout: ${timeoutSeconds}s, interval: ${checkIntervalSeconds}s)`); const startTime = Date.now(); let results = null; let lastStatus = 'unknown'; while ((Date.now() - startTime) < (timeoutSeconds * 1000)) { results = await this.apiService.getCompletedExecution(executionId); if (results) { lastStatus = results.status; if (results.status === 'completed') { this.debugLog(`[MCP] Agent execution ${executionId} completed successfully`); break; } else if (results.status === 'failed') { this.debugLog(`[MCP] Agent execution ${executionId} failed`); break; } } // Wait before next check await new Promise(resolve => setTimeout(resolve, checkIntervalSeconds * 1000)); } const waitTime = Math.round((Date.now() - startTime) / 1000); if (!results) { return { content: [ { type: 'text', text: JSON.stringify({ tool: 'wait_for_agent_completion', execution_id: executionId, status: 'timeout', message: `Agent execution not found or still running after ${waitTime} seconds. The execution may still be processing in the background.`, wait_time_seconds: waitTime, timeout_seconds: timeoutSeconds, timestamp: new Date().toISOString() }, null, 2) } ] }; } if (results.status === 'completed') { return { content: [ { type: 'text', text: JSON.stringify({ tool: 'wait_for_agent_completion', execution_id: executionId, status: 'completed', agent_name: results.agent_name || results.agentName, results: results.results || results.result, result_summary: results.result_summary || results.resultSummary, confidence: results.confidence, execution_time: results.execution_time || results.executionTime, wait_time_seconds: waitTime, completed_at: results.completed_at || results.completedAt, message: `Agent execution completed successfully after waiting ${waitTime} seconds.`, timestamp: new Date().toISOString() }, null, 2) } ] }; } else { return { content: [ { type: 'text', text: JSON.stringify({ tool: 'wait_for_agent_completion', execution_id: executionId, status: results.status, agent_name: results.agent_name || results.agentName, error: results.error || `Agent execution ${results.status}`, wait_time_seconds: waitTime, message: `Agent execution ${results.status} after waiting ${waitTime} seconds.`, timestamp: new Date().toISOString() }, null, 2) } ] }; } } catch (error) { this.errorLog('[MCP] Wait for agent completion error:', error); if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Failed to wait for agent completion: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleForecastSales(request) { const args = request.params.arguments || {}; try { // Validate time_period format const timePeriod = args.time_period || '90d'; const validTimePeriodRegex = /^(30d|90d|quarter|quarterly|annual|Q[1-4]|Q[1-4] \d{4}|FY \d{4}|FY\d{2})$/; if (!validTimePeriodRegex.test(timePeriod)) { throw new McpError(ErrorCode.InvalidParams, `Invalid time_period format: "${timePeriod}". Supported formats: 30d, 90d, quarter, quarterly, annual, Q1-Q4, Q1 2024, FY 2024, FY25, etc.`); } const result = await this.apiService.getForecast({ time_period: timePeriod, include_scenarios: args.include_scenarios !== false, filters: args.filters || {} }, this.userToken); // Ensure we have a proper response structure const response = { tool: 'forecast_sales', time_period: timePeriod, success: result?.success ?? false, forecast_data: result?.data || null, message: result?.message || 'Forecast completed', error: result?.error || null, timestamp: new Date().toISOString() }; return { content: [ { type: 'text', text: JSON.stringify(response, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] Sales forecast error:', error); if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Sales forecast failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleListAgents(request) { const args = request.params.arguments || {}; try { const result = await this.apiService.getAvailableAgents(); let healthInfo = null; if (args.include_status !== false) { try { healthInfo = await this.apiService.checkHealth(); } catch (error) { this.debugLog('[MCP] Health check failed during list agents:', error); healthInfo = { backend: false, crewai: false, crmConnect: false }; } } return { content: [ { type: 'text', text: JSON.stringify({ tool: 'list_agents', success: result.success, agents: result.data, health_status: healthInfo, message: result.message, error: result.error, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] List agents error:', error); throw new McpError(ErrorCode.InternalError, `List agents failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleListWorkflows(request) { const args = request.params.arguments || {}; try { const result = await this.apiService.getAvailableWorkflows(); return { content: [ { type: 'text', text: JSON.stringify({ tool: 'list_workflows', success: result.success, workflows: result.data, include_details: args.include_details !== false, message: result.message, error: result.error, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] List workflows error:', error); throw new McpError(ErrorCode.InternalError, `List workflows failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async handleHealthCheck(request) { const args = request.params.arguments || {}; try { const healthStatus = await this.apiService.checkHealth(); const overallHealth = healthStatus.backend && healthStatus.crewai && healthStatus.crmConnect; return { content: [ { type: 'text', text: JSON.stringify({ tool: 'health_check', overall_status: overallHealth ? 'healthy' : 'degraded', services: { backend: { status: healthStatus.backend ? 'healthy' : 'unhealthy', description: 'IRISeller Backend API', url: 'http://localhost:3001' }, crewai: { status: healthStatus.crewai ? 'healthy' : 'unhealthy', description: 'CrewAI Agent Service', url: 'http://localhost:8001' }, crm_connect: { status: healthStatus.crmConnect ? 'healthy' : 'unhealthy', description: 'CRM Connect Gateway', url: 'http://localhost:3001/api/crm-connect' } }, detailed: args.detailed === true, timestamp: new Date().toISOString() }, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] Health check error:', error); // Return partial health info even if check fails return { content: [ { type: 'text', text: JSON.stringify({ tool: 'health_check', overall_status: 'error', services: { backend: { status: 'unknown', description: 'IRISeller Backend API' }, crewai: { status: 'unknown', description: 'CrewAI Agent Service' }, crm_connect: { status: 'unknown', description: 'CRM Connect Gateway' } }, error: error instanceof Error ? error.message : 'Health check failed', timestamp: new Date().toISOString() }, null, 2) } ] }; } } async handleWebSearch(request) { try { const args = request.params.arguments || {}; // Validate required parameters if (!args.query || typeof args.query !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'query parameter is required and must be a string'); } // Check if web search is available if (!this.webSearchService.isAvailable()) { throw new McpError(ErrorCode.InternalError, 'Claude web search is not available. Please configure ANTHROPIC_API_KEY environment variable.'); } // Prepare search request for Claude's web search const searchRequest = { query: args.query, max_uses: typeof args.max_uses === 'number' ? Math.min(Math.max(args.max_uses, 1), 5) : 3, allowed_domains: Array.isArray(args.allowed_domains) ? args.allowed_domains : undefined, blocked_domains: Array.isArray(args.blocked_domains) ? args.blocked_domains : undefined, user_location: args.user_location && typeof args.user_location === 'object' && 'type' in args.user_location && args.user_location.type === 'approximate' ? args.user_location : undefined }; // Execute Claude web search const searchResult = await this.webSearchService.search(searchRequest); // Format results for MCP response const formattedResults = { tool: 'web_search', query: searchResult.query, provider: searchResult.provider, answer: searchResult.answer, results: searchResult.results, citations: searchResult.citations, timestamp: searchResult.timestamp, configuration: this.webSearchService.getConfiguration() }; return { content: [ { type: 'text', text: JSON.stringify(formattedResults, null, 2) } ] }; } catch (error) { this.errorLog('[MCP] Web search error:', error); if (error instanceof McpError) { throw error; } // Return error response with configuration information return { content: [ { type: 'text', text: JSON.stringify({ tool: 'web_search', error: error instanceof Error ? error.message : 'Web search failed', configuration: this.webSearchService.getConfiguration(), timestamp: new Date().toISOString() }, null, 2) } ] }; } } async handleGetStockData(request) { try { const args = request.params.arguments || {}; // Validate required parameters if (!args.symbol || typeof args.symbol !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'symbol parameter is required and must be a string'); } // Check if stock data service is available if (!this.stockDataService.isAvailable()) { throw new McpError(ErrorCode.InternalError, 'Stock data service is not available. Please configure ANTHROPIC_API_KEY environment variable.'); } // Prepare stock data request const stockRequest = { symbol: args.symbol, company_name: typeof args.company_name === 'string' ? args.company_name : undefined, include_history: typeof args.include_history === 'boolean' ? args.include_history : true, history_period: typeof args.history_period === 'string' && ['1d', '5d', '1m', '3m', '6m', '1y'].includes(args.history_period) ? args.history_period : '3m' }; // Execute stock data search const stockResult = await this.stockDataService.getStockData(stockRequest); // Format results for MCP response const formattedResults = { tool: 'get_stock_data', request: stockRequest, stock_data: stockResult, timestamp: new Date().toISOString(), configuration: this.stockDataService.getConfiguration() }; return { content: [ { type: 'text', text: JSON.stringify(formattedResults, null, 2) } ] }; } catch (error) { this.errorLog('Stock data handler error:', error); // Return error response with configuration information return { content: [ { type: 'text', text: JSON.stringify({ tool: 'get_stock_data', error: error instanceof Error ? error.message : 'Stock data fetch failed', configuration: this.stockDataService.getConfiguration(), timestamp: new Date().toISOString() }, null, 2) } ] }; } } async handleMeetingPrep(request) { try { const args = request.params.arguments || {}; // Validate required parameters if (!args.person_name || typeof args.person_name !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'person_name parameter is required and must be a string'); } const personName = args.person_name.trim(); const companyName = typeof args.company_name === 'string' ? args.company_name.trim() : undefined; const meetingContext = typeof args.meeting_context === 'string' ? args.meeting_context : 'business meeting'; const researchDepth = typeof args.research_depth === 'string' && ['basic', 'comprehensive', 'deep'].includes(args.research_depth) ? args.research_depth : 'comprehensive'; const includeCompanyAnalysis = typeof args.include_company_analysis === 'boolean' ? args.include_company_analysis : true; const focusAreas = Array.isArray(args.focus_areas) ? args.focus_areas : []; this.debugLog(`[MCP] Preparing meeting notes for: ${personName}${companyName ? ` at ${companyName}` : ''}`); // Step 1: Use personalization agent to get enriched CRM data let crmData = null; try { this.debugLog('[MCP] Step 1: Using personalization agent for comprehensive CRM lookup...'); const agentResponse = await this.apiService.executeAgent({ agent_name: 'personalization', input_data: { person_name: personName, company_name: companyName, meeting_context: meetingContext }, options: { timeout: 30, priority: 'high', include_research: true } }); if (agentResponse?.results?.research_results?.contact_info) { crmData = agentResponse.results.research_results.contact_info; this.debugLog(`[MCP] Found enriched CRM data for ${personName}: ${crmData.email}, ${crmData.phone}`); } else { this.debugLog(`[MCP] No CRM data found via personalization agent for ${personName}`); this.debugLog(`[MCP] Agent response structure:`, JSON.stringify(agentResponse, null, 2).substring(0, 500)); } } catch (error) { this.debugLog('[MCP] CRM lookup via personalization agent failed:', error instanceof Error ? error.message : 'Unknown error'); } // Step 2: Web Research (if comprehensive or deep) let webResearchData = null; if (researchDepth === 'comprehensive' || researchDepth === 'deep') { try { this.debugLog('[MCP] Step 2: Conducting web research...'); const searchQueries = [ `"${personName}"${companyName ? ` "${companyName}"` : ''} professional background`, ...(companyName ? [`"${companyName}" recent news developments 2024`] : []), ...(includeCompanyAnalysis && companyName ? [`"${companyName}" industry challenges opportunities`] : []), ...(focusAreas.length > 0 ? focusAreas.map(area => `"${companyName || personName}" ${area}`) : []) ]; const webResults = await Promise.all(searchQueries.slice(0, researchDepth === 'deep' ? 4 : 2).map(async (query) => { try { const searchRequest = { params: { name: 'web_search', arguments: { query, max_uses: 2, allowed_domains: ['linkedin.com', 'crunchbase.com', 'bloomberg.com', 'techcrunch.com', 'forbes.com'] } }, method: 'tools/call' }; return await this.handleWebSearch(searchRequest); } catch (error) { this.debugLog(`[MCP] Web search failed for query: ${query}`, error); return null; } })); webResearchData = webResults .filter(result => result && result.content?.[0]?.text) .map(result => JSON.parse(result.content[0].text)); this.debugLog(`[MCP] Completed ${webResearchData.length} web searches`); } catch (error) { this.debugLog('[MCP] Web research failed:', error instanceof Error ? error.message : 'Unknown error'); } } // Step 3: Advanced AI Synthesis using Agent Execution let meetingNotes = null; try { this.debugLog('[MCP] Step 3: Generating AI-powered meeting notes...'); // Prepare comprehensive context for AI analysis const aiContext = { person_name: personName, company_name: companyName, meeting_context: meetingContext, crm_data: crmData, web_research: webResearchData, focus_areas: focusAreas }; // Use the agent execution system for advanced AI synthesis const agentRequest = { params: { name: 'execute_agent', arguments: { agent_name: 'rosa_sdr', input_data: { contact_name: personName, company_name: crmData?.leads?.[0]?.company || crmData?.leads?.[0]?.Company || crmData?.contacts?.[0]?.company || companyName || 'Unknown Compa