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
JavaScript
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: []
};
}
}