UNPKG

ms365-mcp-server

Version:

Microsoft 365 MCP Server for managing Microsoft 365 email through natural language interactions with full OAuth2 authentication support

223 lines (222 loc) 9.59 kB
import { logger } from './api.js'; export class SearchBatchPipeline { constructor(ms365Ops) { this.ms365Ops = ms365Ops; } /** * Execute a complete search-to-batch pipeline * Combines search and batch operations for maximum efficiency */ async executeSearchBatchPipeline(options) { const pipelineStartTime = Date.now(); logger.log(`🔄 SEARCH-BATCH PIPELINE: Starting ${options.batchAction} pipeline`); logger.log(`🔍 Search criteria:`, JSON.stringify(options.searchCriteria, null, 2)); // Step 1: Perform optimized search const searchStartTime = Date.now(); const searchResults = await this.ms365Ops.searchEmails({ ...options.searchCriteria, maxResults: options.maxResults || 50 }); const searchTime = Date.now() - searchStartTime; logger.log(`🔍 Search completed: ${searchResults.messages.length} emails found in ${searchTime}ms`); if (searchResults.messages.length === 0) { return this.createEmptyPipelineResult(pipelineStartTime, searchTime); } // Step 2: Execute batch operation const batchStartTime = Date.now(); const messageIds = searchResults.messages.map(email => email.id); let batchResults; let batchHttpCalls = 0; let successCount = 0; let errorCount = 0; switch (options.batchAction) { case 'retrieve': batchResults = await this.ms365Ops.getEmailsBatch(messageIds, options.includeAttachments || false); batchHttpCalls = Math.ceil(messageIds.length / 20); // 20 per batch successCount = batchResults.length; break; case 'mark_read': case 'mark_unread': const markOperations = messageIds.map((id, index) => ({ id: `mark_${index}`, operation: 'mark', messageId: id, params: { isRead: options.batchAction === 'mark_read' } })); const markResults = await this.ms365Ops.batchEmailOperations(markOperations); batchResults = Array.from(markResults.entries()); batchHttpCalls = Math.ceil(markOperations.length / 20); for (const [id, result] of markResults) { if (result.success) successCount++; else errorCount++; } break; case 'move': if (!options.batchParams?.destinationFolderId) { throw new Error('destinationFolderId is required for move operations'); } const moveOperations = messageIds.map((id, index) => ({ id: `move_${index}`, operation: 'move', messageId: id, params: { destinationFolderId: options.batchParams.destinationFolderId } })); const moveResults = await this.ms365Ops.batchEmailOperations(moveOperations); batchResults = Array.from(moveResults.entries()); batchHttpCalls = Math.ceil(moveOperations.length / 20); for (const [id, result] of moveResults) { if (result.success) successCount++; else errorCount++; } break; case 'delete': const deleteOperations = messageIds.map((id, index) => ({ id: `delete_${index}`, operation: 'delete', messageId: id, params: {} })); const deleteResults = await this.ms365Ops.batchEmailOperations(deleteOperations); batchResults = Array.from(deleteResults.entries()); batchHttpCalls = Math.ceil(deleteOperations.length / 20); for (const [id, result] of deleteResults) { if (result.success) successCount++; else errorCount++; } break; default: throw new Error(`Unknown batch action: ${options.batchAction}`); } const batchTime = Date.now() - batchStartTime; const totalTime = Date.now() - pipelineStartTime; // Calculate performance improvements const traditionalCalls = messageIds.length + 1; // search + individual operations const batchedCalls = 1 + batchHttpCalls; // search + batch operations const callReduction = ((traditionalCalls - batchedCalls) / traditionalCalls) * 100; const estimatedTraditionalTime = traditionalCalls * 150; // Estimate 150ms per call const result = { searchResults: { foundEmails: searchResults.messages.length, searchTime }, batchResults: { processedEmails: messageIds.length, batchTime, httpCalls: batchHttpCalls, successCount, errorCount }, totalTime, performanceImprovement: { traditionalCalls, batchedCalls, callReduction, timeEstimate: `${estimatedTraditionalTime}ms traditional vs ${totalTime}ms batched` }, results: batchResults }; logger.log(`🚀 PIPELINE COMPLETED: ${successCount} successful, ${errorCount} errors in ${totalTime}ms`); logger.log(`📊 Performance: ${callReduction.toFixed(1)}% fewer HTTP calls`); return result; } /** * Smart search-to-batch workflow for large folders * Automatically optimizes for folders with many emails */ async smartSearchAndProcess(searchCriteria, batchAction, batchParams) { // Check if searching in a large folder if (searchCriteria.folder) { const folders = await this.ms365Ops.findFolderByName(searchCriteria.folder); if (folders.length > 0 && folders[0].totalItemCount > 10000) { logger.log(`🏠 Large folder detected (${folders[0].totalItemCount} emails). Using optimized pipeline.`); // For large folders, use progressive search with smaller batches return await this.executeSearchBatchPipeline({ searchCriteria: { ...searchCriteria, maxResults: 25 // Smaller batches for large folders }, batchAction, batchParams, maxResults: 25 }); } } // Standard pipeline for normal folders return await this.executeSearchBatchPipeline({ searchCriteria, batchAction, batchParams, maxResults: 50 }); } /** * Batch retrieve with search integration * Optimized for getting full email details after search */ async searchAndRetrieveFull(searchCriteria, includeAttachments = false) { const result = await this.executeSearchBatchPipeline({ searchCriteria, batchAction: 'retrieve', includeAttachments, maxResults: 50 }); return { searchSummary: `Found ${result.searchResults.foundEmails} emails, retrieved full details for ${result.batchResults.successCount}`, emails: result.results, performance: result.performanceImprovement }; } /** * Quick email processing workflows */ async quickWorkflows() { return { // Mark all unread emails from specific sender as read markSenderEmailsRead: async (senderEmail) => { return await this.smartSearchAndProcess({ from: senderEmail, isUnread: true }, 'mark_read'); }, // Move all emails with specific subject to folder moveEmailsBySubject: async (subject, destinationFolderId) => { return await this.smartSearchAndProcess({ subject }, 'move', { destinationFolderId }); }, // Delete old emails (older than date) deleteOldEmails: async (beforeDate) => { return await this.smartSearchAndProcess({ before: beforeDate }, 'delete'); }, // Get all emails with attachments from specific sender getEmailsWithAttachments: async (senderEmail) => { return await this.searchAndRetrieveFull({ from: senderEmail, hasAttachment: true }, true); } }; } createEmptyPipelineResult(pipelineStartTime, searchTime) { const totalTime = Date.now() - pipelineStartTime; return { searchResults: { foundEmails: 0, searchTime }, batchResults: { processedEmails: 0, batchTime: 0, httpCalls: 0, successCount: 0, errorCount: 0 }, totalTime, performanceImprovement: { traditionalCalls: 1, batchedCalls: 1, callReduction: 0, timeEstimate: 'No emails found to process' }, results: [] }; } }