ms365-mcp-server 1.1.16 → 1.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15,8 +15,24 @@ import { logger } from './utils/api.js';
15
15
  import { MS365Operations } from './utils/ms365-operations.js';
16
16
  import { multiUserMS365Auth } from './utils/multi-user-auth.js';
17
17
  import { enhancedMS365Auth } from './utils/ms365-auth-enhanced.js';
18
+ import { IntelligenceEngine } from './utils/intelligence-engine.js';
19
+ import { ProactiveIntelligence } from './utils/proactive-intelligence.js';
20
+ import { DocumentWorkflow } from './utils/document-workflow.js';
21
+ import { ContextAwareSearch } from './utils/context-aware-search.js';
22
+ import { LargeMailboxSearch } from './utils/large-mailbox-search.js';
23
+ import { BatchTestRunner } from './utils/batch-test-scenarios.js';
24
+ import { performanceMonitor } from './utils/batch-performance-monitor.js';
25
+ import { SearchBatchPipeline } from './utils/search-batch-pipeline.js';
18
26
  // Create singleton MS365Operations instance
19
27
  const ms365Ops = new MS365Operations();
28
+ // Create singleton intelligence services
29
+ const intelligenceEngine = new IntelligenceEngine(ms365Ops);
30
+ const proactiveIntelligence = new ProactiveIntelligence(ms365Ops);
31
+ const documentWorkflow = new DocumentWorkflow();
32
+ const contextAwareSearch = new ContextAwareSearch(ms365Ops);
33
+ const largeMailboxSearch = new LargeMailboxSearch(ms365Ops, contextAwareSearch, intelligenceEngine, proactiveIntelligence);
34
+ const batchTestRunner = new BatchTestRunner(ms365Ops);
35
+ const searchBatchPipeline = new SearchBatchPipeline(ms365Ops);
20
36
  let ms365Config = {
21
37
  setupAuth: false,
22
38
  resetAuth: false,
@@ -67,7 +83,7 @@ function parseArgs() {
67
83
  }
68
84
  const server = new Server({
69
85
  name: "ms365-mcp-server",
70
- version: "1.1.16"
86
+ version: "1.1.18"
71
87
  }, {
72
88
  capabilities: {
73
89
  resources: {
@@ -210,7 +226,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
210
226
  },
211
227
  {
212
228
  name: "manage_email",
213
- description: "UNIFIED EMAIL MANAGEMENT: Read, search, list, mark, move, or delete emails. Combines all email operations in one powerful tool. Supports intelligent partial name matching, folder management, and advanced search criteria. IMPORTANT: Use 'reply_draft' or 'forward_draft' actions (not 'draft') to create threaded drafts that appear in email conversations.",
229
+ description: "🚀 GRAPH API COMPLIANT EMAIL MANAGEMENT: Advanced email operations with Microsoft Graph API compliance. FIXED: Separated $filter and $search operations to respect Graph API limitations. OPTIMIZED: Dynamic timeouts (30-60s), intelligent strategy selection, and proper field restrictions. 📧 Actions: search, search_to_me, list, mark, move, delete, draft operations, threading support",
214
230
  inputSchema: {
215
231
  type: "object",
216
232
  properties: {
@@ -467,6 +483,351 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
467
483
  }
468
484
  }
469
485
  }
486
+ },
487
+ {
488
+ name: "batch_operations",
489
+ description: "🚀 NATIVE GRAPH BATCHING: Leverage Microsoft Graph's native JSON batching API to perform up to 20 operations in a single HTTP request. Dramatically improves performance for multiple email operations. Based on official Microsoft Graph batching documentation.",
490
+ inputSchema: {
491
+ type: "object",
492
+ properties: {
493
+ userId: {
494
+ type: "string",
495
+ description: "User ID for multi-user authentication (required if using multi-user mode)"
496
+ },
497
+ action: {
498
+ type: "string",
499
+ enum: ["batch_get_emails", "batch_email_operations", "batch_folder_operations", "get_folder_with_emails"],
500
+ description: "Batch action: batch_get_emails (get multiple emails efficiently), batch_email_operations (mark/move/delete multiple emails), batch_folder_operations (multiple folder operations), get_folder_with_emails (get folder info and recent emails together)"
501
+ },
502
+ messageIds: {
503
+ type: "array",
504
+ items: { type: "string" },
505
+ description: "List of message IDs for batch_get_emails and batch_email_operations actions"
506
+ },
507
+ includeAttachments: {
508
+ type: "boolean",
509
+ description: "Include attachment info when getting emails (batch_get_emails action)",
510
+ default: false
511
+ },
512
+ operations: {
513
+ type: "array",
514
+ items: {
515
+ type: "object",
516
+ properties: {
517
+ id: {
518
+ type: "string",
519
+ description: "Unique ID for this operation (for tracking results)"
520
+ },
521
+ operation: {
522
+ type: "string",
523
+ enum: ["mark", "move", "delete"],
524
+ description: "Operation type: mark (read/unread), move (to folder), delete (permanently)"
525
+ },
526
+ messageId: {
527
+ type: "string",
528
+ description: "Message ID to operate on"
529
+ },
530
+ params: {
531
+ type: "object",
532
+ properties: {
533
+ isRead: {
534
+ type: "boolean",
535
+ description: "Mark as read (true) or unread (false) for mark operation"
536
+ },
537
+ destinationFolderId: {
538
+ type: "string",
539
+ description: "Destination folder ID for move operation"
540
+ }
541
+ },
542
+ description: "Parameters for the operation"
543
+ }
544
+ },
545
+ required: ["id", "operation", "messageId"]
546
+ },
547
+ description: "List of email operations to perform in batch (max 20 operations)"
548
+ },
549
+ folderId: {
550
+ type: "string",
551
+ description: "Folder ID for get_folder_with_emails action"
552
+ },
553
+ emailCount: {
554
+ type: "number",
555
+ description: "Number of recent emails to get with folder info (default: 10, max: 50)",
556
+ minimum: 1,
557
+ maximum: 50,
558
+ default: 10
559
+ }
560
+ },
561
+ required: ["action"]
562
+ }
563
+ },
564
+ {
565
+ name: "ai_email_assistant",
566
+ description: "🤖 AI EMAIL ASSISTANT: GRAPH API COMPLIANT intelligent email companion with OPTIMIZED LARGE MAILBOX SUPPORT. ✅ FIXED: Proper $filter/$search separation, field restrictions, and timeout handling. Features: Dynamic timeouts (up to 3 minutes), progressive search tiers, Graph API compliance, smart fallbacks. 🚀 BEST FOR: Large mailboxes (20k+ emails), complex searches, timeout-sensitive operations.",
567
+ inputSchema: {
568
+ type: "object",
569
+ properties: {
570
+ userId: {
571
+ type: "string",
572
+ description: "User ID for multi-user authentication (required if using multi-user mode)"
573
+ },
574
+ action: {
575
+ type: "string",
576
+ enum: ["search", "classify", "process_attachments"],
577
+ description: "Action: 'search' (intelligent search with AI), 'classify' (smart email classification), 'process_attachments' (intelligent attachment processing)"
578
+ },
579
+ query: {
580
+ type: "string",
581
+ description: "Natural language search query (e.g., 'tax notice from few weeks', 'government emails urgent', 'related to project alpha') - required for search action"
582
+ },
583
+ // Search options
584
+ enableCrossReference: {
585
+ type: "boolean",
586
+ description: "Enable cross-reference detection to find related emails (search action)",
587
+ default: true
588
+ },
589
+ enableFuzzySearch: {
590
+ type: "boolean",
591
+ description: "Enable fuzzy matching for better search results (search action)",
592
+ default: true
593
+ },
594
+ enableThreadReconstruction: {
595
+ type: "boolean",
596
+ description: "Enable thread reconstruction for forwarded email chains (search action)",
597
+ default: true
598
+ },
599
+ enableContextAware: {
600
+ type: "boolean",
601
+ description: "Enable context-aware search with time/sender understanding (search action)",
602
+ default: true
603
+ },
604
+ folder: {
605
+ type: "string",
606
+ description: "Search within specific folder (search action, default: all folders)"
607
+ },
608
+ corpusSize: {
609
+ type: "number",
610
+ description: "Number of recent emails to search through (search action, default: 200, max: 1000)",
611
+ minimum: 10,
612
+ maximum: 1000,
613
+ default: 200
614
+ },
615
+ useLargeMailboxStrategy: {
616
+ type: "boolean",
617
+ description: "Enable intelligent multi-tier search for large mailboxes (20k+ emails) - automatically enabled for large mailboxes",
618
+ default: false
619
+ },
620
+ timeWindowDays: {
621
+ type: "number",
622
+ description: "Time window in days for large mailbox search (default: 90 for optimal performance, max: 365 for comprehensive search)",
623
+ minimum: 30,
624
+ maximum: 1095,
625
+ default: 90
626
+ },
627
+ // Classification options
628
+ classifyAction: {
629
+ type: "string",
630
+ enum: ["classify_recent", "classify_folder", "classify_batch", "get_summary"],
631
+ description: "Classification action: classify_recent (last 50 emails), classify_folder (specific folder), classify_batch (specific emails), get_summary (classification summary)"
632
+ },
633
+ folderId: {
634
+ type: "string",
635
+ description: "Folder ID for classify_folder action (e.g., 'inbox', 'sent')"
636
+ },
637
+ messageIds: {
638
+ type: "array",
639
+ items: { type: "string" },
640
+ description: "List of message IDs for classify_batch action or specific email for processing"
641
+ },
642
+ category: {
643
+ type: "string",
644
+ enum: ["government", "tax", "legal", "financial", "healthcare", "insurance", "deadline", "invoice", "security"],
645
+ description: "Filter by specific category (classify action)"
646
+ },
647
+ priority: {
648
+ type: "string",
649
+ enum: ["critical", "high", "medium", "low"],
650
+ description: "Filter by priority level (classify action)"
651
+ },
652
+ // Attachment processing options
653
+ messageId: {
654
+ type: "string",
655
+ description: "Email message ID containing attachments (process_attachments action)"
656
+ },
657
+ attachmentIds: {
658
+ type: "array",
659
+ items: { type: "string" },
660
+ description: "Specific attachment IDs to process (process_attachments action, optional)"
661
+ },
662
+ autoDecrypt: {
663
+ type: "boolean",
664
+ description: "Attempt to automatically decrypt password-protected files (process_attachments action)",
665
+ default: true
666
+ },
667
+ extractText: {
668
+ type: "boolean",
669
+ description: "Extract text content from documents (process_attachments action)",
670
+ default: true
671
+ },
672
+ customPasswords: {
673
+ type: "array",
674
+ items: { type: "string" },
675
+ description: "Additional passwords to try for decryption (process_attachments action)"
676
+ },
677
+ // General options
678
+ maxResults: {
679
+ type: "number",
680
+ description: "Maximum number of results to return",
681
+ minimum: 1,
682
+ maximum: 200,
683
+ default: 50
684
+ }
685
+ },
686
+ required: ["action"]
687
+ }
688
+ },
689
+ {
690
+ name: "performance_testing",
691
+ description: "🔬 PERFORMANCE TESTING & BENCHMARKING: Comprehensive testing suite to benchmark traditional vs native batched operations. Compare performance, monitor improvements, and generate detailed reports showing HTTP call reduction and speed improvements.",
692
+ inputSchema: {
693
+ type: "object",
694
+ properties: {
695
+ userId: {
696
+ type: "string",
697
+ description: "User ID for multi-user authentication (required if using multi-user mode)"
698
+ },
699
+ action: {
700
+ type: "string",
701
+ enum: ["run_tests", "benchmark_bulk_retrieval", "benchmark_bulk_operations", "benchmark_folder_operations", "benchmark_mixed_operations", "generate_report", "clear_metrics"],
702
+ description: "Testing action: run_tests (comprehensive test suite), benchmark_* (specific performance tests), generate_report (create performance report), clear_metrics (reset all metrics)"
703
+ },
704
+ testEmailCount: {
705
+ type: "number",
706
+ description: "Number of emails to use for testing (default: 10, max: 20 for safety)",
707
+ minimum: 5,
708
+ maximum: 20,
709
+ default: 10
710
+ },
711
+ includeAttachments: {
712
+ type: "boolean",
713
+ description: "Include attachment operations in bulk retrieval tests",
714
+ default: true
715
+ },
716
+ folderId: {
717
+ type: "string",
718
+ description: "Folder ID to test (default: inbox)",
719
+ default: "inbox"
720
+ },
721
+ operationType: {
722
+ type: "string",
723
+ enum: ["mark", "move"],
724
+ description: "Type of operation for bulk operations test (default: mark)",
725
+ default: "mark"
726
+ },
727
+ generateDetailedLog: {
728
+ type: "boolean",
729
+ description: "Generate detailed performance logs",
730
+ default: true
731
+ }
732
+ },
733
+ required: ["action"]
734
+ }
735
+ },
736
+ {
737
+ name: "search_batch_pipeline",
738
+ description: "🔄 SEARCH-TO-BATCH PIPELINE: Seamlessly combine search and batch operations for maximum efficiency. Find emails with search, then efficiently process them with native batching. Perfect for bulk operations on search results.",
739
+ inputSchema: {
740
+ type: "object",
741
+ properties: {
742
+ userId: {
743
+ type: "string",
744
+ description: "User ID for multi-user authentication (required if using multi-user mode)"
745
+ },
746
+ action: {
747
+ type: "string",
748
+ enum: ["search_and_retrieve", "search_and_mark_read", "search_and_mark_unread", "search_and_move", "search_and_delete", "quick_workflows"],
749
+ description: "Pipeline action: search_and_retrieve (search + get full details), search_and_mark_read (search + mark as read), search_and_mark_unread (search + mark as unread), search_and_move (search + move to folder), search_and_delete (search + delete), quick_workflows (pre-built workflows)"
750
+ },
751
+ // Search criteria
752
+ query: {
753
+ type: "string",
754
+ description: "Search query for finding emails"
755
+ },
756
+ from: {
757
+ type: "string",
758
+ description: "Search emails from specific sender"
759
+ },
760
+ to: {
761
+ type: "string",
762
+ description: "Search emails to specific recipient"
763
+ },
764
+ subject: {
765
+ type: "string",
766
+ description: "Search emails with specific subject"
767
+ },
768
+ folder: {
769
+ type: "string",
770
+ description: "Search within specific folder"
771
+ },
772
+ after: {
773
+ type: "string",
774
+ description: "Search emails after date (YYYY-MM-DD)"
775
+ },
776
+ before: {
777
+ type: "string",
778
+ description: "Search emails before date (YYYY-MM-DD)"
779
+ },
780
+ hasAttachment: {
781
+ type: "boolean",
782
+ description: "Filter emails with attachments"
783
+ },
784
+ isUnread: {
785
+ type: "boolean",
786
+ description: "Filter for unread emails"
787
+ },
788
+ importance: {
789
+ type: "string",
790
+ enum: ["low", "normal", "high"],
791
+ description: "Filter by importance level"
792
+ },
793
+ maxResults: {
794
+ type: "number",
795
+ description: "Maximum results to find and process (default: 50, max: 100)",
796
+ minimum: 1,
797
+ maximum: 100,
798
+ default: 50
799
+ },
800
+ // Batch operation parameters
801
+ includeAttachments: {
802
+ type: "boolean",
803
+ description: "Include attachments in search_and_retrieve action",
804
+ default: false
805
+ },
806
+ destinationFolderId: {
807
+ type: "string",
808
+ description: "Destination folder ID for search_and_move action"
809
+ },
810
+ // Quick workflow parameters
811
+ workflowType: {
812
+ type: "string",
813
+ enum: ["mark_sender_read", "move_by_subject", "delete_old", "get_attachments"],
814
+ description: "Pre-built workflow type for quick_workflows action"
815
+ },
816
+ senderEmail: {
817
+ type: "string",
818
+ description: "Sender email for mark_sender_read workflow"
819
+ },
820
+ subjectText: {
821
+ type: "string",
822
+ description: "Subject text for move_by_subject workflow"
823
+ },
824
+ beforeDate: {
825
+ type: "string",
826
+ description: "Date for delete_old workflow (YYYY-MM-DD)"
827
+ }
828
+ },
829
+ required: ["action"]
830
+ }
470
831
  }
471
832
  ];
472
833
  // Add multi-user specific tools
@@ -538,6 +899,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
538
899
  */
539
900
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
540
901
  const { name, arguments: args } = request.params;
902
+ // Helper function to validate and normalize bodyType
903
+ const normalizeBodyType = (bodyType) => {
904
+ if (!bodyType)
905
+ return 'text';
906
+ const normalized = bodyType.toString().toLowerCase().trim();
907
+ if (normalized === 'html')
908
+ return 'html';
909
+ if (normalized === 'text')
910
+ return 'text';
911
+ // Invalid value provided - log warning and default to text
912
+ logger.log(`⚠️ Invalid bodyType '${bodyType}' provided. Valid values are 'text' or 'html'. Defaulting to 'text'.`);
913
+ return 'text';
914
+ };
541
915
  try {
542
916
  switch (name) {
543
917
  // ============ UNIFIED AUTHENTICATION TOOL ============
@@ -720,7 +1094,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
720
1094
  ]
721
1095
  };
722
1096
  case "search":
723
- const searchResults = await ms365Ops.searchEmails(args);
1097
+ // Enhanced basic search with dynamic timeout based on search complexity
1098
+ const hasComplexFilters = !!(args?.query || args?.from || args?.subject || args?.after || args?.before);
1099
+ const baseTimeout = hasComplexFilters ? 60000 : 45000; // 60s for complex, 45s for simple
1100
+ const maxResults = args?.maxResults || 50;
1101
+ const adjustedTimeout = maxResults > 100 ? baseTimeout * 1.5 : baseTimeout; // Increase timeout for large result sets
1102
+ logger.log(`🔍 Search timeout set to ${adjustedTimeout / 1000}s (complex: ${hasComplexFilters}, maxResults: ${maxResults})`);
1103
+ const searchPromise = ms365Ops.searchEmails(args);
1104
+ const timeoutPromise = new Promise((_, reject) => {
1105
+ setTimeout(() => {
1106
+ reject(new Error(`Search timed out after ${adjustedTimeout / 1000} seconds. For large mailboxes or complex searches, try: (1) ai_email_assistant with useLargeMailboxStrategy: true, (2) More specific search terms, (3) Smaller maxResults (current: ${maxResults}), or (4) Narrower date ranges.`));
1107
+ }, adjustedTimeout);
1108
+ });
1109
+ const searchResults = await Promise.race([searchPromise, timeoutPromise]);
724
1110
  logger.log(`DEBUG: Search results count: ${searchResults.messages.length}`);
725
1111
  logger.log(`DEBUG: First result:`, JSON.stringify(searchResults.messages[0], null, 2));
726
1112
  // Enhanced feedback for search results
@@ -762,7 +1148,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
762
1148
  const batchSize = args?.batchSize || 50;
763
1149
  const maxBatches = args?.maxBatches || 5;
764
1150
  logger.log(`DEBUG: Starting batched search with batchSize: ${batchSize}, maxBatches: ${maxBatches}`);
765
- const batchedSearchResults = await ms365Ops.searchEmailsBatched(args, batchSize, maxBatches);
1151
+ // Add timeout protection for batched searches
1152
+ const batchTimeout = 45000; // 45 seconds for batched searches
1153
+ const batchPromise = ms365Ops.searchEmailsBatched(args, batchSize, maxBatches);
1154
+ const batchTimeoutPromise = new Promise((_, reject) => {
1155
+ setTimeout(() => {
1156
+ reject(new Error('Batched search timed out - try using ai_email_assistant with useLargeMailboxStrategy: true for better performance with large mailboxes'));
1157
+ }, batchTimeout);
1158
+ });
1159
+ const batchedSearchResults = await Promise.race([batchPromise, batchTimeoutPromise]);
766
1160
  logger.log(`DEBUG: Batched search results count: ${batchedSearchResults.messages.length} from ${batchedSearchResults.totalBatches} batches`);
767
1161
  // Enhanced feedback for batched search results
768
1162
  let batchedResponseText = `🔍 Batched Email Search Results (${batchedSearchResults.messages.length} found from ${batchedSearchResults.totalBatches} batches)`;
@@ -954,7 +1348,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
954
1348
  bcc: normalizeDraftEmailArray(args.draftBcc),
955
1349
  subject: args.draftSubject,
956
1350
  body: args.draftBody,
957
- bodyType: args.draftBodyType || 'text',
1351
+ bodyType: normalizeBodyType(args.draftBodyType),
958
1352
  importance: args.draftImportance || 'normal',
959
1353
  attachments: args.draftAttachments,
960
1354
  conversationId: args.conversationId
@@ -998,7 +1392,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
998
1392
  if (args.draftBody)
999
1393
  updates.body = args.draftBody;
1000
1394
  if (args.draftBodyType)
1001
- updates.bodyType = args.draftBodyType;
1395
+ updates.bodyType = normalizeBodyType(args.draftBodyType);
1002
1396
  if (args.draftImportance)
1003
1397
  updates.importance = args.draftImportance;
1004
1398
  if (args.draftAttachments)
@@ -1039,7 +1433,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1039
1433
  if (!args?.originalMessageId) {
1040
1434
  throw new Error("originalMessageId is required for reply_draft action");
1041
1435
  }
1042
- const replyDraftResult = await ms365Ops.createReplyDraft(args.originalMessageId, args.draftBody, args.replyToAll || false, args.draftBodyType || 'text');
1436
+ const replyDraftResult = await ms365Ops.createReplyDraft(args.originalMessageId, args.draftBody, args.replyToAll || false, normalizeBodyType(args.draftBodyType));
1043
1437
  return {
1044
1438
  content: [
1045
1439
  {
@@ -1052,7 +1446,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1052
1446
  if (!args?.originalMessageId) {
1053
1447
  throw new Error("originalMessageId is required for forward_draft action");
1054
1448
  }
1055
- const forwardDraftResult = await ms365Ops.createForwardDraft(args.originalMessageId, args.draftBody, args.draftBodyType || 'text');
1449
+ const forwardDraftResult = await ms365Ops.createForwardDraft(args.originalMessageId, args.draftBody, normalizeBodyType(args.draftBodyType));
1056
1450
  return {
1057
1451
  content: [
1058
1452
  {
@@ -1107,6 +1501,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1107
1501
  throw new Error(`Unknown contact action: ${contactAction}`);
1108
1502
  }
1109
1503
  // ============ REMAINING ORIGINAL TOOLS ============
1504
+ // ============ REMAINING ORIGINAL TOOLS ============
1110
1505
  case "send_email":
1111
1506
  if (ms365Config.multiUser) {
1112
1507
  const userId = args?.userId;
@@ -1144,7 +1539,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1144
1539
  bcc: normalizeEmailArray(args.bcc),
1145
1540
  subject: args.subject,
1146
1541
  body: args.body || '',
1147
- bodyType: args.bodyType || 'text',
1542
+ bodyType: normalizeBodyType(args.bodyType),
1148
1543
  replyTo: args.replyTo,
1149
1544
  importance: args.importance || 'normal',
1150
1545
  attachments: args.attachments || []
@@ -1311,6 +1706,724 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1311
1706
  }
1312
1707
  ]
1313
1708
  };
1709
+ // ============ NATIVE JSON BATCHING TOOL ============
1710
+ case "batch_operations":
1711
+ if (ms365Config.multiUser) {
1712
+ const userId = args?.userId;
1713
+ if (!userId) {
1714
+ throw new Error("User ID is required in multi-user mode");
1715
+ }
1716
+ const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
1717
+ ms365Ops.setGraphClient(graphClient);
1718
+ }
1719
+ else {
1720
+ const graphClient = await enhancedMS365Auth.getGraphClient();
1721
+ ms365Ops.setGraphClient(graphClient);
1722
+ }
1723
+ const batchAction = args?.action;
1724
+ if (!batchAction) {
1725
+ throw new Error("Action is required for batch operations");
1726
+ }
1727
+ switch (batchAction) {
1728
+ case 'batch_get_emails':
1729
+ if (!args?.messageIds || !Array.isArray(args.messageIds)) {
1730
+ throw new Error("messageIds array is required for batch_get_emails action");
1731
+ }
1732
+ const batchEmails = await ms365Ops.getEmailsBatch(args.messageIds, args?.includeAttachments || false);
1733
+ return {
1734
+ content: [
1735
+ {
1736
+ type: "text",
1737
+ text: `🚀 Native Batch Email Results (${batchEmails.length} emails retrieved)\n\n` +
1738
+ `📊 Performance: Single HTTP request for ${args.messageIds.length} emails\n\n` +
1739
+ batchEmails.map((email, index) => `${index + 1}. 📧 ${email.subject}\n` +
1740
+ ` 👤 From: ${email.from.name} <${email.from.address}>\n` +
1741
+ ` 📅 ${new Date(email.receivedDateTime).toLocaleString()}\n` +
1742
+ ` ${email.isRead ? '📖 Read' : '📩 Unread'}\n` +
1743
+ ` 🆔 ID: ${email.id}\n` +
1744
+ (email.attachments?.length ? ` 📎 Attachments: ${email.attachments.length}\n` : '')).join('\n') +
1745
+ `\n💡 Traditional approach would require ${args.messageIds.length} separate API calls!`
1746
+ }
1747
+ ]
1748
+ };
1749
+ case 'batch_email_operations':
1750
+ if (!args?.operations || !Array.isArray(args.operations)) {
1751
+ throw new Error("operations array is required for batch_email_operations action");
1752
+ }
1753
+ if (args.operations.length > 20) {
1754
+ throw new Error("Maximum 20 operations allowed per batch (Microsoft Graph limit)");
1755
+ }
1756
+ const operationResults = await ms365Ops.batchEmailOperations(args.operations);
1757
+ let successCount = 0;
1758
+ let failureCount = 0;
1759
+ const resultText = Array.from(operationResults.entries()).map(([id, result]) => {
1760
+ if (result.success) {
1761
+ successCount++;
1762
+ return `✅ ${id}: Success`;
1763
+ }
1764
+ else {
1765
+ failureCount++;
1766
+ return `❌ ${id}: ${result.error}`;
1767
+ }
1768
+ }).join('\n');
1769
+ return {
1770
+ content: [
1771
+ {
1772
+ type: "text",
1773
+ text: `🚀 Native Batch Operations Results\n\n` +
1774
+ `📊 Performance: ${args.operations.length} operations in single HTTP request\n` +
1775
+ `✅ Successful: ${successCount}\n` +
1776
+ `❌ Failed: ${failureCount}\n\n` +
1777
+ `📋 Detailed Results:\n${resultText}\n\n` +
1778
+ `💡 Traditional approach would require ${args.operations.length} separate API calls!`
1779
+ }
1780
+ ]
1781
+ };
1782
+ case 'get_folder_with_emails':
1783
+ if (!args?.folderId) {
1784
+ throw new Error("folderId is required for get_folder_with_emails action");
1785
+ }
1786
+ const emailCount = Math.min(args?.emailCount || 10, 50);
1787
+ const folderWithEmails = await ms365Ops.getFolderWithRecentEmails(args.folderId, emailCount);
1788
+ return {
1789
+ content: [
1790
+ {
1791
+ type: "text",
1792
+ text: `🚀 Native Batch Folder + Emails Results\n\n` +
1793
+ `📊 Performance: Folder info + ${emailCount} emails in single HTTP request\n\n` +
1794
+ `📁 Folder: ${folderWithEmails.folder?.displayName || 'Unknown'}\n` +
1795
+ ` 🆔 ID: ${folderWithEmails.folder?.id}\n` +
1796
+ ` 📧 Total Items: ${folderWithEmails.folder?.totalItemCount || 0}\n` +
1797
+ ` 📩 Unread: ${folderWithEmails.folder?.unreadItemCount || 0}\n\n` +
1798
+ `📧 Recent Emails (${folderWithEmails.emails.length}):\n` +
1799
+ folderWithEmails.emails.map((email, index) => `${index + 1}. ${email.subject}\n` +
1800
+ ` 👤 From: ${email.from.name}\n` +
1801
+ ` 📅 ${new Date(email.receivedDateTime).toLocaleDateString()}\n` +
1802
+ ` ${email.isRead ? '📖' : '📩'} ${email.isRead ? 'Read' : 'Unread'}`).join('\n') +
1803
+ `\n\n💡 Traditional approach would require 2 separate API calls!`
1804
+ }
1805
+ ]
1806
+ };
1807
+ default:
1808
+ throw new Error(`Unknown batch action: ${batchAction}`);
1809
+ }
1810
+ // ============ AI EMAIL ASSISTANT (MERGED TOOL) ============
1811
+ case "ai_email_assistant":
1812
+ if (ms365Config.multiUser) {
1813
+ const userId = args?.userId;
1814
+ if (!userId) {
1815
+ throw new Error("User ID is required in multi-user mode");
1816
+ }
1817
+ const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
1818
+ ms365Ops.setGraphClient(graphClient);
1819
+ }
1820
+ else {
1821
+ const graphClient = await enhancedMS365Auth.getGraphClient();
1822
+ ms365Ops.setGraphClient(graphClient);
1823
+ }
1824
+ const aiAction = args?.action;
1825
+ if (!aiAction) {
1826
+ throw new Error("Action is required for AI email assistant");
1827
+ }
1828
+ switch (aiAction) {
1829
+ case 'search':
1830
+ if (!args?.query) {
1831
+ throw new Error("Query is required for search action");
1832
+ }
1833
+ const searchOptions = {
1834
+ enableCrossReference: args?.enableCrossReference !== false,
1835
+ enableFuzzySearch: args?.enableFuzzySearch !== false,
1836
+ enableThreadReconstruction: args?.enableThreadReconstruction !== false,
1837
+ maxResults: args?.maxResults || 50
1838
+ };
1839
+ const searchFolder = args?.folder || 'inbox';
1840
+ const useLargeMailboxStrategy = args?.useLargeMailboxStrategy === true;
1841
+ const timeWindowDays = args?.timeWindowDays || 365;
1842
+ // Check if we should use large mailbox strategy
1843
+ if (useLargeMailboxStrategy) {
1844
+ // Use advanced multi-tier search for large mailboxes
1845
+ const largeMailboxResult = await largeMailboxSearch.search(args.query, {
1846
+ maxTotalResults: searchOptions.maxResults,
1847
+ timeWindowDays: timeWindowDays,
1848
+ batchSize: 300,
1849
+ prioritizeRecent: true
1850
+ });
1851
+ return {
1852
+ content: [
1853
+ {
1854
+ type: "text",
1855
+ text: `🤖 AI Email Assistant - Large Mailbox Search Results\n\n` +
1856
+ `🔍 Query: "${largeMailboxResult.query}"\n` +
1857
+ `📊 Strategy: ${largeMailboxResult.searchStrategy}\n` +
1858
+ `📧 Total Mailbox Size: ~${largeMailboxResult.totalEmailsInMailbox.toLocaleString()} emails\n` +
1859
+ `🎯 Confidence: ${largeMailboxResult.confidence.toFixed(2)}\n` +
1860
+ `⏱️ Search Time: ${largeMailboxResult.searchTime}ms\n\n` +
1861
+ `🔍 Search Tiers Executed:\n${largeMailboxResult.tierResults.map(tier => `• ${tier.tier}: ${tier.emailsSearched} searched → ${tier.resultsFound} found (${tier.processingTime}ms)`).join('\n')}\n\n` +
1862
+ `📧 Found ${largeMailboxResult.finalResults.length} relevant emails:\n\n` +
1863
+ largeMailboxResult.finalResults.map((email, index) => `${index + 1}. ${email.subject}\n` +
1864
+ ` 📨 From: ${email.from.name} <${email.from.address}>\n` +
1865
+ ` 📅 ${new Date(email.receivedDateTime).toLocaleString()}\n` +
1866
+ ` 🏷️ Categories: ${email.categories?.join(', ') || 'None'}\n` +
1867
+ ` ⚡ Priority: ${email.importance || 'normal'}\n`).join('\n') +
1868
+ `\n💡 Recommendations:\n${largeMailboxResult.recommendations.join('\n')}`
1869
+ }
1870
+ ]
1871
+ };
1872
+ }
1873
+ else {
1874
+ // Use standard search for smaller mailboxes
1875
+ const corpusSize = args?.corpusSize || 200;
1876
+ const emailSearchResult = await ms365Ops.searchEmails({
1877
+ query: '*',
1878
+ maxResults: Math.min(corpusSize, 1000), // Cap at 1000 for performance
1879
+ folder: searchFolder
1880
+ });
1881
+ if (args?.enableContextAware !== false) {
1882
+ // Use context-aware search
1883
+ const contextResults = await contextAwareSearch.search(args.query, emailSearchResult.messages);
1884
+ logger.log(`🔍 Searched through ${emailSearchResult.messages.length} emails from corpus of ${corpusSize}`);
1885
+ const limitedResults = contextResults.emails.slice(0, searchOptions.maxResults);
1886
+ return {
1887
+ content: [
1888
+ {
1889
+ type: "text",
1890
+ text: `🤖 AI Email Assistant - Smart Search Results\n\n` +
1891
+ `🔍 Query: "${contextResults.originalQuery}"\n` +
1892
+ `📊 Strategy: ${contextResults.searchStrategy}\n` +
1893
+ `📧 Searched: ${emailSearchResult.messages.length} emails\n` +
1894
+ `🎯 Confidence: ${contextResults.confidence.toFixed(2)}\n\n` +
1895
+ `📧 Found ${limitedResults.length} relevant emails:\n\n` +
1896
+ limitedResults.map((email, index) => `${index + 1}. ${email.subject}\n` +
1897
+ ` 📨 From: ${email.from.name} <${email.from.address}>\n` +
1898
+ ` 📅 ${new Date(email.receivedDateTime).toLocaleString()}\n` +
1899
+ ` 🏷️ Categories: ${email.categories?.join(', ') || 'None'}\n` +
1900
+ ` ⚡ Priority: ${email.importance || 'normal'}\n`).join('\n') +
1901
+ `\n💡 Explanation:\n${contextResults.explanation}\n\n` +
1902
+ `🎯 Suggestions:\n${contextResults.suggestions.join('\n')}`
1903
+ }
1904
+ ]
1905
+ };
1906
+ }
1907
+ else {
1908
+ // Use intelligent search
1909
+ const intelligentResults = await intelligenceEngine.intelligentSearch(args.query, emailSearchResult.messages, searchOptions);
1910
+ logger.log(`🧠 Analyzed ${emailSearchResult.messages.length} emails from corpus of ${corpusSize}`);
1911
+ return {
1912
+ content: [
1913
+ {
1914
+ type: "text",
1915
+ text: `🤖 AI Email Assistant - Intelligent Search Results\n\n` +
1916
+ `🔍 Query: "${args.query}"\n` +
1917
+ `📧 Searched: ${emailSearchResult.messages.length} emails\n` +
1918
+ `📊 Found ${intelligentResults.insights.totalEmails} results with ${intelligentResults.insights.averageConfidence.toFixed(2)} confidence\n` +
1919
+ `⏱️ Processing Time: ${intelligentResults.insights.processingTime}ms\n\n` +
1920
+ `📧 Results:\n${intelligentResults.results.slice(0, searchOptions.maxResults).map((email, index) => `${index + 1}. ${email.subject}\n 📨 From: ${email.from.name} <${email.from.address}>\n 📅 ${new Date(email.receivedDateTime).toLocaleString()}\n 🔗 Cross-refs: ${intelligentResults.crossReferences.get(email.id)?.length || 0}\n 📊 Threads: ${intelligentResults.threads.has(email.id) ? 'Yes' : 'No'}\n`).join('\n')}\n\n` +
1921
+ `💡 Insights:\n` +
1922
+ `• Total Emails: ${intelligentResults.insights.totalEmails}\n` +
1923
+ `• High Priority: ${intelligentResults.insights.highPriorityEmails}\n` +
1924
+ `• Thread Count: ${intelligentResults.insights.threadCount}\n` +
1925
+ `• Average Confidence: ${intelligentResults.insights.averageConfidence.toFixed(2)}`
1926
+ }
1927
+ ]
1928
+ };
1929
+ }
1930
+ }
1931
+ case 'classify':
1932
+ const classifyAction = args?.classifyAction || 'classify_recent';
1933
+ const maxResults = args?.maxResults || 50;
1934
+ switch (classifyAction) {
1935
+ case 'classify_recent':
1936
+ const recentEmailsResult = await ms365Ops.searchEmails({
1937
+ query: '*',
1938
+ maxResults: maxResults,
1939
+ folder: 'inbox'
1940
+ });
1941
+ const recentClassifications = await proactiveIntelligence.batchClassifyEmails(recentEmailsResult.messages);
1942
+ const recentSummary = proactiveIntelligence.generateClassificationSummary(recentClassifications);
1943
+ return {
1944
+ content: [
1945
+ {
1946
+ type: "text",
1947
+ text: `🤖 AI Email Assistant - Smart Classification\n\n` +
1948
+ `🏷️ Recent ${maxResults} Emails Analysis:\n\n` +
1949
+ `📊 Summary:\n` +
1950
+ `• Total emails: ${recentSummary.totalEmails}\n` +
1951
+ `• Critical: ${recentSummary.byPriority.critical || 0}\n` +
1952
+ `• High: ${recentSummary.byPriority.high || 0}\n` +
1953
+ `• Medium: ${recentSummary.byPriority.medium || 0}\n` +
1954
+ `• Low: ${recentSummary.byPriority.low || 0}\n\n` +
1955
+ `📈 Top Categories:\n${Object.entries(recentSummary.byCategory).map(([cat, count]) => `• ${cat}: ${count}`).join('\n')}\n\n` +
1956
+ `🎯 AI Recommendations:\n${recentSummary.recommendedActions.join('\n')}`
1957
+ }
1958
+ ]
1959
+ };
1960
+ case 'get_summary':
1961
+ const summaryEmailsResult = await ms365Ops.searchEmails({
1962
+ query: '*',
1963
+ maxResults: 100,
1964
+ folder: 'inbox'
1965
+ });
1966
+ const summaryClassifications = await proactiveIntelligence.batchClassifyEmails(summaryEmailsResult.messages);
1967
+ const summary = proactiveIntelligence.generateClassificationSummary(summaryClassifications);
1968
+ return {
1969
+ content: [
1970
+ {
1971
+ type: "text",
1972
+ text: `🤖 AI Email Assistant - Intelligence Summary\n\n` +
1973
+ `📊 Total Emails Analyzed: ${summary.totalEmails}\n\n` +
1974
+ `⚡ Priority Distribution:\n` +
1975
+ `• Critical: ${summary.byPriority.critical || 0}\n` +
1976
+ `• High: ${summary.byPriority.high || 0}\n` +
1977
+ `• Medium: ${summary.byPriority.medium || 0}\n` +
1978
+ `• Low: ${summary.byPriority.low || 0}\n\n` +
1979
+ `📈 Category Distribution:\n${Object.entries(summary.byCategory).map(([cat, count]) => `• ${cat}: ${count}`).join('\n')}\n\n` +
1980
+ `🎯 Top AI Recommendations:\n${summary.recommendedActions.slice(0, 5).join('\n')}`
1981
+ }
1982
+ ]
1983
+ };
1984
+ default:
1985
+ throw new Error(`Unknown classification action: ${classifyAction}`);
1986
+ }
1987
+ case 'process_attachments':
1988
+ if (!args?.messageId) {
1989
+ throw new Error("Message ID is required for attachment processing");
1990
+ }
1991
+ // Get the email and its attachments
1992
+ const email = await ms365Ops.getEmail(args.messageId, true);
1993
+ if (!email.hasAttachments || !email.attachments) {
1994
+ throw new Error("This email has no attachments to process");
1995
+ }
1996
+ // Filter specific attachments if requested
1997
+ let attachmentsToProcess = email.attachments;
1998
+ if (args?.attachmentIds && Array.isArray(args.attachmentIds)) {
1999
+ attachmentsToProcess = email.attachments.filter(att => args.attachmentIds.includes(att.id));
2000
+ }
2001
+ // Download attachments and prepare for processing
2002
+ const attachmentData = await Promise.all(attachmentsToProcess.map(async (att) => {
2003
+ const attachment = await ms365Ops.getAttachment(args.messageId, att.id);
2004
+ return {
2005
+ id: att.id,
2006
+ name: att.name,
2007
+ size: att.size,
2008
+ contentType: att.contentType,
2009
+ contentBytes: attachment.contentBytes
2010
+ };
2011
+ }));
2012
+ // Process attachments with document workflow
2013
+ const processingResults = await documentWorkflow.processEmailAttachments(email, attachmentData);
2014
+ // Get processing statistics
2015
+ const stats = documentWorkflow.getProcessingStats(processingResults);
2016
+ return {
2017
+ content: [
2018
+ {
2019
+ type: "text",
2020
+ text: `🤖 AI Email Assistant - Smart Attachment Processing\n\n` +
2021
+ `📊 Statistics:\n` +
2022
+ `• Total: ${stats.total}\n` +
2023
+ `• Successful: ${stats.successful}\n` +
2024
+ `• Password Protected: ${stats.passwordProtected}\n` +
2025
+ `• Failed: ${stats.failed}\n` +
2026
+ `• Text Extracted: ${stats.textExtracted}\n` +
2027
+ `• Avg Processing Time: ${stats.averageProcessingTime.toFixed(2)}ms\n\n` +
2028
+ `📋 Results:\n` +
2029
+ processingResults.map((result, index) => `${index + 1}. ${result.attachment.name}\n` +
2030
+ ` 📊 Status: ${result.status}\n` +
2031
+ ` 💾 Size: ${result.metadata.size} bytes\n` +
2032
+ ` 🔒 Password Protected: ${result.metadata.isPasswordProtected ? 'Yes' : 'No'}\n` +
2033
+ ` 📝 Text Extracted: ${result.metadata.extractedText ? 'Yes' : 'No'}\n` +
2034
+ ` ⏱️ Processing Time: ${result.metadata.processingTime}ms\n` +
2035
+ `${result.textContent ? ` 📄 Content Preview: ${result.textContent.substring(0, 200)}...\n` : ''}` +
2036
+ `${result.errors ? ` ❌ Errors: ${result.errors.join(', ')}\n` : ''}`).join('\n')
2037
+ }
2038
+ ]
2039
+ };
2040
+ default:
2041
+ throw new Error(`Unknown AI assistant action: ${aiAction}`);
2042
+ }
2043
+ // ============ PERFORMANCE TESTING TOOL ============
2044
+ case "performance_testing":
2045
+ if (ms365Config.multiUser) {
2046
+ const userId = args?.userId;
2047
+ if (!userId) {
2048
+ throw new Error("User ID is required in multi-user mode");
2049
+ }
2050
+ const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
2051
+ ms365Ops.setGraphClient(graphClient);
2052
+ }
2053
+ else {
2054
+ const graphClient = await enhancedMS365Auth.getGraphClient();
2055
+ ms365Ops.setGraphClient(graphClient);
2056
+ }
2057
+ const testAction = args?.action;
2058
+ if (!testAction) {
2059
+ throw new Error("Action is required for performance testing");
2060
+ }
2061
+ switch (testAction) {
2062
+ case 'run_tests':
2063
+ // Get recent emails for testing
2064
+ const testEmailCount = Math.min(args?.testEmailCount || 10, 20);
2065
+ const recentEmails = await ms365Ops.searchEmails({
2066
+ maxResults: testEmailCount,
2067
+ folder: 'inbox'
2068
+ });
2069
+ if (recentEmails.messages.length < 5) {
2070
+ return {
2071
+ content: [
2072
+ {
2073
+ type: "text",
2074
+ text: `❌ Insufficient emails for testing. Found ${recentEmails.messages.length} emails, need at least 5.\n\n💡 Try sending yourself some test emails or use a different folder.`
2075
+ }
2076
+ ]
2077
+ };
2078
+ }
2079
+ const testMessageIds = recentEmails.messages.map(email => email.id);
2080
+ const testReport = await batchTestRunner.runAllTests(testMessageIds);
2081
+ return {
2082
+ content: [
2083
+ {
2084
+ type: "text",
2085
+ text: `🔬 COMPREHENSIVE PERFORMANCE TEST RESULTS\n\n${testReport}\n\n💡 These tests demonstrate the significant performance improvements achieved with native Microsoft Graph JSON batching!`
2086
+ }
2087
+ ]
2088
+ };
2089
+ case 'benchmark_bulk_retrieval':
2090
+ const retrievalTestCount = Math.min(args?.testEmailCount || 10, 20);
2091
+ const retrievalEmails = await ms365Ops.searchEmails({
2092
+ maxResults: retrievalTestCount,
2093
+ folder: args?.folderId || 'inbox'
2094
+ });
2095
+ if (retrievalEmails.messages.length < 5) {
2096
+ throw new Error(`Insufficient emails for testing. Found ${retrievalEmails.messages.length}, need at least 5.`);
2097
+ }
2098
+ const retrievalTestIds = retrievalEmails.messages.slice(0, Math.min(10, retrievalEmails.messages.length)).map(e => e.id);
2099
+ const retrievalResult = await batchTestRunner.testBulkEmailRetrieval(retrievalTestIds);
2100
+ return {
2101
+ content: [
2102
+ {
2103
+ type: "text",
2104
+ text: `📧 BULK EMAIL RETRIEVAL BENCHMARK\n\n` +
2105
+ `🔍 Tested: ${retrievalTestIds.length} emails${args?.includeAttachments ? ' with attachments' : ''}\n\n` +
2106
+ `📊 Results:\n` +
2107
+ `• Traditional: ${retrievalResult.traditional.metrics.duration}ms (${retrievalResult.traditional.metrics.httpCalls} HTTP calls)\n` +
2108
+ `• Batched: ${retrievalResult.batched.metrics.duration}ms (${retrievalResult.batched.metrics.httpCalls} HTTP calls)\n\n` +
2109
+ `🏆 Performance Improvement:\n` +
2110
+ `• ${retrievalResult.comparison?.improvement.durationReduction.toFixed(1)}% faster execution\n` +
2111
+ `• ${retrievalResult.comparison?.improvement.httpCallReduction.toFixed(1)}% fewer HTTP calls\n` +
2112
+ `• ${retrievalResult.comparison?.improvement.efficiencyGain.toFixed(1)}% better efficiency per request`
2113
+ }
2114
+ ]
2115
+ };
2116
+ case 'benchmark_bulk_operations':
2117
+ const operationsTestCount = Math.min(args?.testEmailCount || 10, 20);
2118
+ const operationsEmails = await ms365Ops.searchEmails({
2119
+ maxResults: operationsTestCount,
2120
+ folder: args?.folderId || 'inbox'
2121
+ });
2122
+ if (operationsEmails.messages.length < 5) {
2123
+ throw new Error(`Insufficient emails for testing. Found ${operationsEmails.messages.length}, need at least 5.`);
2124
+ }
2125
+ const operationsTestIds = operationsEmails.messages.slice(0, Math.min(15, operationsEmails.messages.length)).map(e => e.id);
2126
+ const operationsResult = await batchTestRunner.testBulkEmailOperations(operationsTestIds);
2127
+ return {
2128
+ content: [
2129
+ {
2130
+ type: "text",
2131
+ text: `⚡ BULK EMAIL OPERATIONS BENCHMARK\n\n` +
2132
+ `🔍 Tested: ${operationsTestIds.length} ${args?.operationType || 'mark'} operations\n\n` +
2133
+ `📊 Results:\n` +
2134
+ `• Traditional: ${operationsResult.traditional.metrics.duration}ms (${operationsResult.traditional.metrics.httpCalls} HTTP calls)\n` +
2135
+ `• Batched: ${operationsResult.batched.metrics.duration}ms (${operationsResult.batched.metrics.httpCalls} HTTP calls)\n\n` +
2136
+ `🏆 Performance Improvement:\n` +
2137
+ `• ${operationsResult.comparison?.improvement.durationReduction.toFixed(1)}% faster execution\n` +
2138
+ `• ${operationsResult.comparison?.improvement.httpCallReduction.toFixed(1)}% fewer HTTP calls\n` +
2139
+ `• ${operationsResult.comparison?.improvement.efficiencyGain.toFixed(1)}% better efficiency per request`
2140
+ }
2141
+ ]
2142
+ };
2143
+ case 'benchmark_folder_operations':
2144
+ const folderResult = await batchTestRunner.testFolderWithEmails(args?.folderId || 'inbox', Math.min(args?.testEmailCount || 10, 15));
2145
+ return {
2146
+ content: [
2147
+ {
2148
+ type: "text",
2149
+ text: `📁 FOLDER + EMAILS BENCHMARK\n\n` +
2150
+ `🔍 Tested: Folder info + ${args?.testEmailCount || 10} emails\n\n` +
2151
+ `📊 Results:\n` +
2152
+ `• Traditional: ${folderResult.traditional.metrics.duration}ms (${folderResult.traditional.metrics.httpCalls} HTTP calls)\n` +
2153
+ `• Batched: ${folderResult.batched.metrics.duration}ms (${folderResult.batched.metrics.httpCalls} HTTP calls)\n\n` +
2154
+ `🏆 Performance Improvement:\n` +
2155
+ `• ${folderResult.comparison?.improvement.durationReduction.toFixed(1)}% faster execution\n` +
2156
+ `• ${folderResult.comparison?.improvement.httpCallReduction.toFixed(1)}% fewer HTTP calls\n` +
2157
+ `• ${folderResult.comparison?.improvement.efficiencyGain.toFixed(1)}% better efficiency per request`
2158
+ }
2159
+ ]
2160
+ };
2161
+ case 'benchmark_mixed_operations':
2162
+ const mixedTestCount = Math.min(args?.testEmailCount || 12, 20);
2163
+ const mixedEmails = await ms365Ops.searchEmails({
2164
+ maxResults: mixedTestCount,
2165
+ folder: args?.folderId || 'inbox'
2166
+ });
2167
+ if (mixedEmails.messages.length < 9) {
2168
+ throw new Error(`Insufficient emails for mixed operations testing. Found ${mixedEmails.messages.length}, need at least 9.`);
2169
+ }
2170
+ const mixedTestIds = mixedEmails.messages.slice(0, Math.min(12, mixedEmails.messages.length)).map(e => e.id);
2171
+ const mixedResult = await batchTestRunner.testMixedOperations(mixedTestIds);
2172
+ return {
2173
+ content: [
2174
+ {
2175
+ type: "text",
2176
+ text: `🔄 MIXED OPERATIONS WORKFLOW BENCHMARK\n\n` +
2177
+ `🔍 Tested: ${mixedTestIds.length} mixed operations (mark, move, delete simulation)\n\n` +
2178
+ `📊 Results:\n` +
2179
+ `• Traditional: ${mixedResult.traditional.metrics.duration}ms (${mixedResult.traditional.metrics.httpCalls} HTTP calls)\n` +
2180
+ `• Batched: ${mixedResult.batched.metrics.duration}ms (${mixedResult.batched.metrics.httpCalls} HTTP calls)\n\n` +
2181
+ `🏆 Performance Improvement:\n` +
2182
+ `• ${mixedResult.comparison?.improvement.durationReduction.toFixed(1)}% faster execution\n` +
2183
+ `• ${mixedResult.comparison?.improvement.httpCallReduction.toFixed(1)}% fewer HTTP calls\n` +
2184
+ `• ${mixedResult.comparison?.improvement.efficiencyGain.toFixed(1)}% better efficiency per request`
2185
+ }
2186
+ ]
2187
+ };
2188
+ case 'generate_report':
2189
+ const performanceReport = performanceMonitor.generateReport();
2190
+ return {
2191
+ content: [
2192
+ {
2193
+ type: "text",
2194
+ text: `📊 COMPREHENSIVE PERFORMANCE REPORT\n\n${performanceReport}\n\n💡 This report shows cumulative performance data from all batch operations executed during this session.`
2195
+ }
2196
+ ]
2197
+ };
2198
+ case 'clear_metrics':
2199
+ performanceMonitor.clear();
2200
+ return {
2201
+ content: [
2202
+ {
2203
+ type: "text",
2204
+ text: `✅ Performance metrics cleared. Ready for new testing session.`
2205
+ }
2206
+ ]
2207
+ };
2208
+ default:
2209
+ throw new Error(`Unknown performance testing action: ${testAction}`);
2210
+ }
2211
+ // ============ SEARCH-BATCH PIPELINE TOOL ============
2212
+ case "search_batch_pipeline":
2213
+ if (ms365Config.multiUser) {
2214
+ const userId = args?.userId;
2215
+ if (!userId) {
2216
+ throw new Error("User ID is required in multi-user mode");
2217
+ }
2218
+ const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
2219
+ ms365Ops.setGraphClient(graphClient);
2220
+ }
2221
+ else {
2222
+ const graphClient = await enhancedMS365Auth.getGraphClient();
2223
+ ms365Ops.setGraphClient(graphClient);
2224
+ }
2225
+ const pipelineAction = args?.action;
2226
+ if (!pipelineAction) {
2227
+ throw new Error("Action is required for search-batch pipeline");
2228
+ }
2229
+ switch (pipelineAction) {
2230
+ case 'search_and_retrieve':
2231
+ const searchCriteria = {
2232
+ query: args?.query,
2233
+ from: args?.from,
2234
+ to: args?.to,
2235
+ subject: args?.subject,
2236
+ folder: args?.folder,
2237
+ after: args?.after,
2238
+ before: args?.before,
2239
+ hasAttachment: args?.hasAttachment,
2240
+ isUnread: args?.isUnread,
2241
+ importance: args?.importance,
2242
+ maxResults: args?.maxResults || 50
2243
+ };
2244
+ const retrieveResult = await searchBatchPipeline.searchAndRetrieveFull(searchCriteria, args?.includeAttachments || false);
2245
+ return {
2246
+ content: [
2247
+ {
2248
+ type: "text",
2249
+ text: `🔄 SEARCH-TO-BATCH PIPELINE: Retrieve Full Details\n\n` +
2250
+ `📊 ${retrieveResult.searchSummary}\n\n` +
2251
+ `🚀 Performance Improvement:\n` +
2252
+ `• Traditional: ${retrieveResult.performance.traditionalCalls} HTTP calls\n` +
2253
+ `• Batched: ${retrieveResult.performance.batchedCalls} HTTP calls\n` +
2254
+ `• Reduction: ${retrieveResult.performance.callReduction.toFixed(1)}%\n` +
2255
+ `• Time: ${retrieveResult.performance.timeEstimate}\n\n` +
2256
+ `📧 Retrieved Emails:\n` +
2257
+ retrieveResult.emails.map((email, index) => `${index + 1}. ${email.subject}\n` +
2258
+ ` 👤 From: ${email.from.name} <${email.from.address}>\n` +
2259
+ ` 📅 ${new Date(email.receivedDateTime).toLocaleString()}\n` +
2260
+ ` ${email.hasAttachments ? '📎 Has attachments' : ''}\n`).join('\n')
2261
+ }
2262
+ ]
2263
+ };
2264
+ case 'search_and_mark_read':
2265
+ case 'search_and_mark_unread':
2266
+ const markCriteria = {
2267
+ query: args?.query,
2268
+ from: args?.from,
2269
+ to: args?.to,
2270
+ subject: args?.subject,
2271
+ folder: args?.folder,
2272
+ after: args?.after,
2273
+ before: args?.before,
2274
+ hasAttachment: args?.hasAttachment,
2275
+ isUnread: args?.isUnread,
2276
+ importance: args?.importance
2277
+ };
2278
+ const markAction = pipelineAction === 'search_and_mark_read' ? 'mark_read' : 'mark_unread';
2279
+ const markResult = await searchBatchPipeline.executeSearchBatchPipeline({
2280
+ searchCriteria: markCriteria,
2281
+ batchAction: markAction,
2282
+ maxResults: args?.maxResults || 50
2283
+ });
2284
+ return {
2285
+ content: [
2286
+ {
2287
+ type: "text",
2288
+ text: `🔄 SEARCH-TO-BATCH PIPELINE: Mark as ${markAction === 'mark_read' ? 'Read' : 'Unread'}\n\n` +
2289
+ `📊 Search Results: ${markResult.searchResults.foundEmails} emails found (${markResult.searchResults.searchTime}ms)\n` +
2290
+ `📧 Batch Results: ${markResult.batchResults.successCount} marked successfully, ${markResult.batchResults.errorCount} errors (${markResult.batchResults.batchTime}ms)\n\n` +
2291
+ `🚀 Performance Improvement:\n` +
2292
+ `• Traditional: ${markResult.performanceImprovement.traditionalCalls} HTTP calls\n` +
2293
+ `• Batched: ${markResult.performanceImprovement.batchedCalls} HTTP calls\n` +
2294
+ `• Reduction: ${markResult.performanceImprovement.callReduction.toFixed(1)}%\n` +
2295
+ `• Time: ${markResult.performanceImprovement.timeEstimate}\n\n` +
2296
+ `✅ Total Time: ${markResult.totalTime}ms`
2297
+ }
2298
+ ]
2299
+ };
2300
+ case 'search_and_move':
2301
+ if (!args?.destinationFolderId) {
2302
+ throw new Error("destinationFolderId is required for search_and_move action");
2303
+ }
2304
+ const moveCriteria = {
2305
+ query: args?.query,
2306
+ from: args?.from,
2307
+ to: args?.to,
2308
+ subject: args?.subject,
2309
+ folder: args?.folder,
2310
+ after: args?.after,
2311
+ before: args?.before,
2312
+ hasAttachment: args?.hasAttachment,
2313
+ isUnread: args?.isUnread,
2314
+ importance: args?.importance
2315
+ };
2316
+ const moveResult = await searchBatchPipeline.executeSearchBatchPipeline({
2317
+ searchCriteria: moveCriteria,
2318
+ batchAction: 'move',
2319
+ batchParams: { destinationFolderId: args.destinationFolderId },
2320
+ maxResults: args?.maxResults || 50
2321
+ });
2322
+ return {
2323
+ content: [
2324
+ {
2325
+ type: "text",
2326
+ text: `🔄 SEARCH-TO-BATCH PIPELINE: Move to Folder\n\n` +
2327
+ `📊 Search Results: ${moveResult.searchResults.foundEmails} emails found (${moveResult.searchResults.searchTime}ms)\n` +
2328
+ `📧 Batch Results: ${moveResult.batchResults.successCount} moved successfully, ${moveResult.batchResults.errorCount} errors (${moveResult.batchResults.batchTime}ms)\n` +
2329
+ `📁 Destination: ${args.destinationFolderId}\n\n` +
2330
+ `🚀 Performance Improvement:\n` +
2331
+ `• Traditional: ${moveResult.performanceImprovement.traditionalCalls} HTTP calls\n` +
2332
+ `• Batched: ${moveResult.performanceImprovement.batchedCalls} HTTP calls\n` +
2333
+ `• Reduction: ${moveResult.performanceImprovement.callReduction.toFixed(1)}%\n` +
2334
+ `• Time: ${moveResult.performanceImprovement.timeEstimate}\n\n` +
2335
+ `✅ Total Time: ${moveResult.totalTime}ms`
2336
+ }
2337
+ ]
2338
+ };
2339
+ case 'search_and_delete':
2340
+ const deleteCriteria = {
2341
+ query: args?.query,
2342
+ from: args?.from,
2343
+ to: args?.to,
2344
+ subject: args?.subject,
2345
+ folder: args?.folder,
2346
+ after: args?.after,
2347
+ before: args?.before,
2348
+ hasAttachment: args?.hasAttachment,
2349
+ isUnread: args?.isUnread,
2350
+ importance: args?.importance
2351
+ };
2352
+ const deleteResult = await searchBatchPipeline.executeSearchBatchPipeline({
2353
+ searchCriteria: deleteCriteria,
2354
+ batchAction: 'delete',
2355
+ maxResults: args?.maxResults || 50
2356
+ });
2357
+ return {
2358
+ content: [
2359
+ {
2360
+ type: "text",
2361
+ text: `🔄 SEARCH-TO-BATCH PIPELINE: Delete Emails\n\n` +
2362
+ `📊 Search Results: ${deleteResult.searchResults.foundEmails} emails found (${deleteResult.searchResults.searchTime}ms)\n` +
2363
+ `📧 Batch Results: ${deleteResult.batchResults.successCount} deleted successfully, ${deleteResult.batchResults.errorCount} errors (${deleteResult.batchResults.batchTime}ms)\n\n` +
2364
+ `🚀 Performance Improvement:\n` +
2365
+ `• Traditional: ${deleteResult.performanceImprovement.traditionalCalls} HTTP calls\n` +
2366
+ `• Batched: ${deleteResult.performanceImprovement.batchedCalls} HTTP calls\n` +
2367
+ `• Reduction: ${deleteResult.performanceImprovement.callReduction.toFixed(1)}%\n` +
2368
+ `• Time: ${deleteResult.performanceImprovement.timeEstimate}\n\n` +
2369
+ `✅ Total Time: ${deleteResult.totalTime}ms`
2370
+ }
2371
+ ]
2372
+ };
2373
+ case 'quick_workflows':
2374
+ const workflowType = args?.workflowType;
2375
+ if (!workflowType) {
2376
+ throw new Error("workflowType is required for quick_workflows action");
2377
+ }
2378
+ const workflows = await searchBatchPipeline.quickWorkflows();
2379
+ let workflowResult;
2380
+ switch (workflowType) {
2381
+ case 'mark_sender_read':
2382
+ if (!args?.senderEmail) {
2383
+ throw new Error("senderEmail is required for mark_sender_read workflow");
2384
+ }
2385
+ workflowResult = await workflows.markSenderEmailsRead(args.senderEmail);
2386
+ break;
2387
+ case 'move_by_subject':
2388
+ if (!args?.subjectText || !args?.destinationFolderId) {
2389
+ throw new Error("subjectText and destinationFolderId are required for move_by_subject workflow");
2390
+ }
2391
+ workflowResult = await workflows.moveEmailsBySubject(args.subjectText, args.destinationFolderId);
2392
+ break;
2393
+ case 'delete_old':
2394
+ if (!args?.beforeDate) {
2395
+ throw new Error("beforeDate is required for delete_old workflow");
2396
+ }
2397
+ workflowResult = await workflows.deleteOldEmails(args.beforeDate);
2398
+ break;
2399
+ case 'get_attachments':
2400
+ if (!args?.senderEmail) {
2401
+ throw new Error("senderEmail is required for get_attachments workflow");
2402
+ }
2403
+ workflowResult = await workflows.getEmailsWithAttachments(args.senderEmail);
2404
+ break;
2405
+ default:
2406
+ throw new Error(`Unknown workflow type: ${workflowType}`);
2407
+ }
2408
+ return {
2409
+ content: [
2410
+ {
2411
+ type: "text",
2412
+ text: `🔄 QUICK WORKFLOW: ${workflowType}\n\n` +
2413
+ `📊 Search Results: ${workflowResult.searchResults?.foundEmails || 'N/A'} emails found\n` +
2414
+ `📧 Batch Results: ${workflowResult.batchResults?.successCount || 'N/A'} processed successfully\n\n` +
2415
+ `🚀 Performance Improvement:\n` +
2416
+ `• Traditional: ${workflowResult.performanceImprovement?.traditionalCalls || 'N/A'} HTTP calls\n` +
2417
+ `• Batched: ${workflowResult.performanceImprovement?.batchedCalls || 'N/A'} HTTP calls\n` +
2418
+ `• Reduction: ${workflowResult.performanceImprovement?.callReduction?.toFixed(1) || 'N/A'}%\n` +
2419
+ `• Time: ${workflowResult.performanceImprovement?.timeEstimate || 'N/A'}\n\n` +
2420
+ `✅ Workflow completed in ${workflowResult.totalTime || 'N/A'}ms`
2421
+ }
2422
+ ]
2423
+ };
2424
+ default:
2425
+ throw new Error(`Unknown pipeline action: ${pipelineAction}`);
2426
+ }
1314
2427
  default:
1315
2428
  throw new Error(`Unknown tool: ${name}`);
1316
2429
  }