@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
JavaScript
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