ms365-mcp-server 1.1.15 → 1.1.16

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
@@ -67,7 +67,7 @@ function parseArgs() {
67
67
  }
68
68
  const server = new Server({
69
69
  name: "ms365-mcp-server",
70
- version: "1.1.15"
70
+ version: "1.1.16"
71
71
  }, {
72
72
  capabilities: {
73
73
  resources: {
@@ -88,7 +88,7 @@ const server = new Server({
88
88
  }
89
89
  }
90
90
  });
91
- logger.log('Server started with version 1.1.14');
91
+ logger.log('Server started with version 1.1.16');
92
92
  // Set up the resource listing request handler
93
93
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
94
94
  logger.log('Received list resources request');
@@ -1007,83 +1007,42 @@ ${originalBodyContent}
1007
1007
  const effectiveMaxResults = maxResults > 0 ? maxResults : safetyLimit;
1008
1008
  let allMessages = [];
1009
1009
  let searchAttempts = 0;
1010
- const maxAttempts = 6; // Try multiple strategies
1011
- // Add timeout mechanism (5 minutes max)
1012
- const searchTimeout = 5 * 60 * 1000; // 5 minutes
1010
+ const maxAttempts = 3; // Reduced from 6 to 3 for faster performance
1011
+ // Add timeout mechanism (2 minutes max - much faster)
1012
+ const searchTimeout = 2 * 60 * 1000; // 2 minutes
1013
1013
  const startTime = Date.now();
1014
- logger.log(`🔍 Starting persistent search with criteria: ${JSON.stringify(criteria)}`);
1014
+ logger.log(`🔍 Starting FAST persistent search with criteria: ${JSON.stringify(criteria)}`);
1015
1015
  logger.log(`🔍 Max results requested: ${maxResults === 0 ? 'ALL (limited to ' + safetyLimit + ' for safety)' : maxResults}`);
1016
1016
  logger.log(`🔍 Search timeout: ${searchTimeout / 1000} seconds`);
1017
1017
  try {
1018
- // Strategy 1: Use reliable OData filter search first (has proper IDs) - Get ALL results
1018
+ // Strategy 1: Use FAST OData filter search (most reliable and fastest)
1019
1019
  if (searchAttempts < maxAttempts && (Date.now() - startTime) < searchTimeout) {
1020
1020
  searchAttempts++;
1021
- logger.log(`🔍 Attempt ${searchAttempts}: Using reliable OData filter search to get ALL results`);
1022
- const filterResults = await this.performFilteredSearchAll(criteria);
1021
+ logger.log(`🔍 Attempt ${searchAttempts}: Using FAST OData filter search`);
1022
+ const filterResults = await this.performFilteredSearchFast(criteria);
1023
1023
  allMessages.push(...filterResults);
1024
1024
  if (allMessages.length > 0) {
1025
- logger.log(`✅ Found ${allMessages.length} results with OData filter search`);
1025
+ logger.log(`✅ Found ${allMessages.length} results with FAST OData filter search`);
1026
1026
  }
1027
1027
  }
1028
- // Strategy 2: Use Microsoft Graph Search API for text-based queries (backup) - Get ALL results
1029
- if (allMessages.length === 0 && criteria.query && searchAttempts < maxAttempts && (Date.now() - startTime) < searchTimeout) {
1030
- searchAttempts++;
1031
- logger.log(`🔍 Attempt ${searchAttempts}: Using Graph Search API for query: "${criteria.query}" to get ALL results`);
1032
- const searchResults = await this.performGraphSearchAll(criteria.query);
1033
- allMessages.push(...searchResults);
1034
- if (allMessages.length > 0) {
1035
- logger.log(`✅ Found ${allMessages.length} results with Graph Search API`);
1036
- }
1037
- }
1038
- // Strategy 3: Use KQL (Keyword Query Language) for advanced searches - Get ALL results
1039
- if (allMessages.length === 0 && searchAttempts < maxAttempts && (Date.now() - startTime) < searchTimeout) {
1040
- const kqlQuery = this.buildKQLQuery(criteria);
1041
- if (kqlQuery) {
1042
- searchAttempts++;
1043
- logger.log(`🔍 Attempt ${searchAttempts}: Using KQL search: "${kqlQuery}" to get ALL results`);
1044
- const kqlResults = await this.performKQLSearchAll(kqlQuery);
1045
- allMessages.push(...kqlResults);
1046
- if (allMessages.length > 0) {
1047
- logger.log(`✅ Found ${allMessages.length} results with KQL search`);
1048
- }
1049
- }
1050
- }
1051
- // Strategy 4: Try relaxed KQL search (remove some constraints) - Get ALL results
1052
- if (allMessages.length === 0 && searchAttempts < maxAttempts && (Date.now() - startTime) < searchTimeout) {
1053
- const relaxedKQL = this.buildRelaxedKQLQuery(criteria);
1054
- if (relaxedKQL && relaxedKQL !== this.buildKQLQuery(criteria)) {
1055
- searchAttempts++;
1056
- logger.log(`🔍 Attempt ${searchAttempts}: Using relaxed KQL search: "${relaxedKQL}" to get ALL results`);
1057
- const relaxedResults = await this.performKQLSearchAll(relaxedKQL);
1058
- allMessages.push(...relaxedResults);
1059
- if (allMessages.length > 0) {
1060
- logger.log(`✅ Found ${allMessages.length} results with relaxed KQL search`);
1061
- }
1062
- }
1063
- }
1064
- // Strategy 5: If we found results but they have UNKNOWN IDs, try to resolve them
1065
- if (allMessages.length > 0 && allMessages.some(msg => msg.id === 'UNKNOWN') && (Date.now() - startTime) < searchTimeout) {
1066
- logger.log(`🔍 Attempting to resolve UNKNOWN message IDs using direct message queries`);
1067
- allMessages = await this.resolveUnknownMessageIds(allMessages, criteria);
1068
- }
1069
- // Strategy 6: Partial text search across ALL recent emails (expanded scope)
1028
+ // Strategy 2: Use FAST basic search fallback
1070
1029
  if (allMessages.length === 0 && searchAttempts < maxAttempts && (Date.now() - startTime) < searchTimeout) {
1071
1030
  searchAttempts++;
1072
- logger.log(`🔍 Attempt ${searchAttempts}: Using expanded partial text search across ALL emails`);
1073
- const partialResults = await this.performPartialTextSearchAll(criteria);
1074
- allMessages.push(...partialResults);
1031
+ logger.log(`🔍 Attempt ${searchAttempts}: Using FAST basic search fallback`);
1032
+ const basicResult = await this.performBasicSearchFast(criteria);
1033
+ allMessages.push(...basicResult.messages);
1075
1034
  if (allMessages.length > 0) {
1076
- logger.log(`✅ Found ${allMessages.length} results with partial text search`);
1035
+ logger.log(`✅ Found ${allMessages.length} results with FAST basic search`);
1077
1036
  }
1078
1037
  }
1079
- // Strategy 7: Fallback to basic search with ALL results
1038
+ // Strategy 3: Quick partial search if still no results
1080
1039
  if (allMessages.length === 0 && searchAttempts < maxAttempts && (Date.now() - startTime) < searchTimeout) {
1081
1040
  searchAttempts++;
1082
- logger.log(`🔍 Attempt ${searchAttempts}: Using fallback basic search to get ALL results`);
1083
- const basicResult = await this.performBasicSearchAll(criteria);
1084
- allMessages.push(...basicResult.messages);
1041
+ logger.log(`🔍 Attempt ${searchAttempts}: Using quick partial search`);
1042
+ const partialResults = await this.performPartialTextSearch(criteria, criteria.maxResults || 50);
1043
+ allMessages.push(...partialResults);
1085
1044
  if (allMessages.length > 0) {
1086
- logger.log(`✅ Found ${allMessages.length} results with basic search fallback`);
1045
+ logger.log(`✅ Found ${allMessages.length} results with quick partial search`);
1087
1046
  }
1088
1047
  }
1089
1048
  // Check if we hit the timeout
@@ -2945,31 +2904,176 @@ ${originalBodyContent}
2945
2904
  } while (nextLink);
2946
2905
  return allMessages;
2947
2906
  }
2907
+ /**
2908
+ * Fast OData filter search - optimized for speed
2909
+ */
2910
+ async performFilteredSearchFast(criteria) {
2911
+ try {
2912
+ const graphClient = await this.getGraphClient();
2913
+ const filters = [];
2914
+ // Build filters quickly
2915
+ if (criteria.from) {
2916
+ const escapedFrom = this.escapeODataValue(criteria.from);
2917
+ filters.push(`contains(from/emailAddress/address,'${escapedFrom}') or contains(from/emailAddress/name,'${escapedFrom}')`);
2918
+ }
2919
+ if (criteria.to) {
2920
+ const escapedTo = this.escapeODataValue(criteria.to);
2921
+ filters.push(`toRecipients/any(r:contains(r/emailAddress/address,'${escapedTo}'))`);
2922
+ }
2923
+ if (criteria.cc) {
2924
+ const escapedCc = this.escapeODataValue(criteria.cc);
2925
+ filters.push(`ccRecipients/any(r:contains(r/emailAddress/address,'${escapedCc}'))`);
2926
+ }
2927
+ if (criteria.subject) {
2928
+ const escapedSubject = this.escapeODataValue(criteria.subject);
2929
+ filters.push(`contains(subject,'${escapedSubject}')`);
2930
+ }
2931
+ if (criteria.after) {
2932
+ const afterDate = this.formatDateForOData(criteria.after);
2933
+ filters.push(`receivedDateTime ge '${afterDate}'`);
2934
+ }
2935
+ if (criteria.before) {
2936
+ const beforeDate = this.formatDateForOData(criteria.before);
2937
+ filters.push(`receivedDateTime le '${beforeDate}'`);
2938
+ }
2939
+ if (criteria.isUnread !== undefined) {
2940
+ filters.push(`isRead eq ${!criteria.isUnread}`);
2941
+ }
2942
+ if (criteria.importance) {
2943
+ filters.push(`importance eq '${criteria.importance}'`);
2944
+ }
2945
+ if (criteria.hasAttachment !== undefined) {
2946
+ filters.push(`hasAttachments eq ${criteria.hasAttachment}`);
2947
+ }
2948
+ // Build API call
2949
+ let apiCall = graphClient
2950
+ .api('/me/messages')
2951
+ .select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink')
2952
+ .orderby('receivedDateTime desc')
2953
+ .top(criteria.maxResults || 50);
2954
+ // Apply filters
2955
+ if (filters.length > 0) {
2956
+ const filterString = filters.join(' and ');
2957
+ logger.log(`🔍 Fast OData filter: ${filterString}`);
2958
+ apiCall = apiCall.filter(filterString);
2959
+ }
2960
+ // Apply folder filter if specified
2961
+ if (criteria.folder) {
2962
+ const folders = await this.findFolderByName(criteria.folder);
2963
+ if (folders.length > 0) {
2964
+ logger.log(`🔍 Fast folder search: ${folders[0].displayName}`);
2965
+ apiCall = graphClient
2966
+ .api(`/me/mailFolders/${folders[0].id}/messages`)
2967
+ .select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink')
2968
+ .orderby('receivedDateTime desc')
2969
+ .top(criteria.maxResults || 50);
2970
+ if (filters.length > 0) {
2971
+ const filterString = filters.join(' and ');
2972
+ apiCall = apiCall.filter(filterString);
2973
+ }
2974
+ }
2975
+ }
2976
+ const result = await apiCall.get();
2977
+ const messages = result.value?.map((email) => this.mapEmailResult(email)) || [];
2978
+ logger.log(`📧 Fast OData search returned ${messages.length} results`);
2979
+ return messages;
2980
+ }
2981
+ catch (error) {
2982
+ logger.log(`❌ Fast OData search failed: ${error}`);
2983
+ return [];
2984
+ }
2985
+ }
2986
+ /**
2987
+ * Fast basic search - optimized for speed
2988
+ */
2989
+ async performBasicSearchFast(criteria) {
2990
+ logger.log('🔄 Using fast basic search');
2991
+ const graphClient = await this.getGraphClient();
2992
+ const result = await graphClient
2993
+ .api('/me/messages')
2994
+ .select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink')
2995
+ .orderby('receivedDateTime desc')
2996
+ .top(criteria.maxResults || 50)
2997
+ .get();
2998
+ let messages = result.value?.map((email) => this.mapEmailResult(email)) || [];
2999
+ // Apply minimal filtering for speed
3000
+ if (criteria.from || criteria.to || criteria.subject) {
3001
+ messages = this.applyBasicFiltering(messages, criteria);
3002
+ }
3003
+ return {
3004
+ messages,
3005
+ hasMore: !!result['@odata.nextLink']
3006
+ };
3007
+ }
3008
+ /**
3009
+ * Basic filtering for fast search
3010
+ */
3011
+ applyBasicFiltering(messages, criteria) {
3012
+ return messages.filter(message => {
3013
+ // Simple text matching
3014
+ if (criteria.from) {
3015
+ const fromText = `${message.from.name} ${message.from.address}`.toLowerCase();
3016
+ if (!fromText.includes(criteria.from.toLowerCase()))
3017
+ return false;
3018
+ }
3019
+ if (criteria.to) {
3020
+ const toText = message.toRecipients.map(r => `${r.name} ${r.address}`).join(' ').toLowerCase();
3021
+ if (!toText.includes(criteria.to.toLowerCase()))
3022
+ return false;
3023
+ }
3024
+ if (criteria.subject) {
3025
+ if (!message.subject.toLowerCase().includes(criteria.subject.toLowerCase()))
3026
+ return false;
3027
+ }
3028
+ return true;
3029
+ });
3030
+ }
2948
3031
  /**
2949
3032
  * Search emails in batches of 50, looping up to 5 times to get more results efficiently
2950
3033
  */
2951
3034
  async searchEmailsBatched(criteria = {}, batchSize = 50, maxBatches = 5) {
2952
3035
  return await this.executeWithAuth(async () => {
2953
3036
  const graphClient = await this.getGraphClient();
2954
- logger.log(`🔍 Starting batched email search: ${batchSize} per batch, max ${maxBatches} batches`);
3037
+ logger.log(`🔍 Starting FAST batched email search: ${batchSize} per batch, max ${maxBatches} batches`);
2955
3038
  logger.log(`🔍 Search criteria:`, JSON.stringify(criteria, null, 2));
2956
3039
  const allMessages = [];
2957
3040
  let currentBatch = 0;
2958
3041
  let hasMoreResults = true;
2959
3042
  const startTime = Date.now();
2960
- const batchTimeout = 2 * 60 * 1000; // 2 minutes per batch
3043
+ const batchTimeout = 30 * 1000; // 30 seconds per batch (much faster)
2961
3044
  try {
2962
3045
  while (currentBatch < maxBatches && hasMoreResults && (Date.now() - startTime) < batchTimeout) {
2963
3046
  currentBatch++;
2964
3047
  logger.log(`🔍 Processing batch ${currentBatch}/${maxBatches}`);
2965
- // Create criteria for this batch
3048
+ // Create criteria for this batch with FAST search strategy
2966
3049
  const batchCriteria = {
2967
3050
  ...criteria,
2968
3051
  maxResults: batchSize
2969
3052
  };
2970
- // Search for this batch
2971
- const batchResult = await this.searchEmails(batchCriteria);
2972
- const batchMessages = batchResult.messages;
3053
+ // Use FAST search - only try the most reliable method first
3054
+ let batchMessages = [];
3055
+ try {
3056
+ // Strategy 1: Fast OData filter search (most reliable and fastest)
3057
+ logger.log(`🔍 Batch ${currentBatch}: Using fast OData filter search`);
3058
+ batchMessages = await this.performFilteredSearchFast(batchCriteria);
3059
+ if (batchMessages.length === 0) {
3060
+ // Strategy 2: Quick basic search fallback
3061
+ logger.log(`🔍 Batch ${currentBatch}: Using quick basic search fallback`);
3062
+ const basicResult = await this.performBasicSearchFast(batchCriteria);
3063
+ batchMessages = basicResult.messages;
3064
+ }
3065
+ }
3066
+ catch (error) {
3067
+ logger.log(`⚠️ Batch ${currentBatch} search failed, trying basic search: ${error}`);
3068
+ try {
3069
+ const basicResult = await this.performBasicSearchFast(batchCriteria);
3070
+ batchMessages = basicResult.messages;
3071
+ }
3072
+ catch (basicError) {
3073
+ logger.log(`❌ Batch ${currentBatch} basic search also failed: ${basicError}`);
3074
+ break;
3075
+ }
3076
+ }
2973
3077
  logger.log(`📧 Batch ${currentBatch}: Found ${batchMessages.length} results`);
2974
3078
  if (batchMessages.length > 0) {
2975
3079
  // Add new messages (avoid duplicates)
@@ -2977,20 +3081,20 @@ ${originalBodyContent}
2977
3081
  allMessages.push(...newMessages);
2978
3082
  logger.log(`📧 Batch ${currentBatch}: Added ${newMessages.length} new unique messages (total: ${allMessages.length})`);
2979
3083
  // Check if we have more results
2980
- hasMoreResults = batchResult.hasMore && batchMessages.length === batchSize;
3084
+ hasMoreResults = batchMessages.length === batchSize;
2981
3085
  }
2982
3086
  else {
2983
3087
  // No more results found
2984
3088
  hasMoreResults = false;
2985
3089
  logger.log(`📧 Batch ${currentBatch}: No more results found`);
2986
3090
  }
2987
- // Small delay between batches to avoid rate limiting
3091
+ // Shorter delay between batches for faster performance
2988
3092
  if (hasMoreResults && currentBatch < maxBatches) {
2989
- await new Promise(resolve => setTimeout(resolve, 1000));
3093
+ await new Promise(resolve => setTimeout(resolve, 500)); // Reduced to 500ms
2990
3094
  }
2991
3095
  }
2992
3096
  const totalTime = (Date.now() - startTime) / 1000;
2993
- logger.log(`🔍 Batched search completed: ${allMessages.length} total results from ${currentBatch} batches in ${totalTime} seconds`);
3097
+ logger.log(`🔍 FAST batched search completed: ${allMessages.length} total results from ${currentBatch} batches in ${totalTime} seconds`);
2994
3098
  return {
2995
3099
  messages: allMessages,
2996
3100
  hasMore: hasMoreResults,
@@ -2998,7 +3102,7 @@ ${originalBodyContent}
2998
3102
  };
2999
3103
  }
3000
3104
  catch (error) {
3001
- logger.error('Error in batched email search:', error);
3105
+ logger.error('Error in fast batched email search:', error);
3002
3106
  return {
3003
3107
  messages: allMessages,
3004
3108
  hasMore: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ms365-mcp-server",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "Microsoft 365 MCP Server for managing Microsoft 365 email through natural language interactions with full OAuth2 authentication support",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",