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 +2 -2
- package/dist/utils/ms365-operations.js +174 -70
- package/package.json +1 -1
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.
|
|
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.
|
|
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 =
|
|
1011
|
-
// Add timeout mechanism (
|
|
1012
|
-
const searchTimeout =
|
|
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
|
|
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
|
|
1022
|
-
const filterResults = await this.
|
|
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
|
|
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
|
|
1073
|
-
const
|
|
1074
|
-
allMessages.push(...
|
|
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
|
|
1035
|
+
logger.log(`✅ Found ${allMessages.length} results with FAST basic search`);
|
|
1077
1036
|
}
|
|
1078
1037
|
}
|
|
1079
|
-
// Strategy
|
|
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
|
|
1083
|
-
const
|
|
1084
|
-
allMessages.push(...
|
|
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
|
|
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 =
|
|
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
|
-
//
|
|
2971
|
-
|
|
2972
|
-
|
|
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 =
|
|
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
|
-
//
|
|
3091
|
+
// Shorter delay between batches for faster performance
|
|
2988
3092
|
if (hasMoreResults && currentBatch < maxBatches) {
|
|
2989
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
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(`🔍
|
|
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.
|
|
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",
|