ms365-mcp-server 1.1.17 → 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 +1 -1
- package/dist/utils/ms365-operations.js +94 -48
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -101,6 +101,7 @@ export class MS365Operations {
|
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* Utility method to validate and format date for OData filters
|
|
104
|
+
* Microsoft Graph expects DateTimeOffset without quotes in OData filters
|
|
104
105
|
*/
|
|
105
106
|
formatDateForOData(dateString) {
|
|
106
107
|
try {
|
|
@@ -108,7 +109,8 @@ export class MS365Operations {
|
|
|
108
109
|
if (isNaN(date.getTime())) {
|
|
109
110
|
throw new Error(`Invalid date: ${dateString}`);
|
|
110
111
|
}
|
|
111
|
-
|
|
112
|
+
// Remove milliseconds and format for OData DateTimeOffset (no quotes needed)
|
|
113
|
+
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
112
114
|
}
|
|
113
115
|
catch (error) {
|
|
114
116
|
logger.error(`Error formatting date ${dateString}:`, error);
|
|
@@ -127,23 +129,20 @@ export class MS365Operations {
|
|
|
127
129
|
const escapedFrom = this.escapeODataValue(criteria.from);
|
|
128
130
|
filters.push(`from/emailAddress/address eq '${escapedFrom}'`);
|
|
129
131
|
}
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
const escapedTo = this.escapeODataValue(criteria.to);
|
|
133
|
-
filters.push(`toRecipients/any(r: r/emailAddress/address eq '${escapedTo}')`);
|
|
134
|
-
}
|
|
132
|
+
// NOTE: toRecipients filtering is not supported by Microsoft Graph OData API
|
|
133
|
+
// Manual filtering will be applied after retrieving results
|
|
135
134
|
if (criteria.cc && criteria.cc.includes('@')) {
|
|
136
135
|
const escapedCc = this.escapeODataValue(criteria.cc);
|
|
137
|
-
filters.push(`ccRecipients/any(
|
|
136
|
+
filters.push(`ccRecipients/any(c: c/emailAddress/address eq '${escapedCc}')`);
|
|
138
137
|
}
|
|
139
|
-
// ✅ SAFE: Date filters with proper
|
|
138
|
+
// ✅ SAFE: Date filters with proper DateTimeOffset format (no quotes)
|
|
140
139
|
if (criteria.after) {
|
|
141
140
|
const afterDate = this.formatDateForOData(criteria.after);
|
|
142
|
-
filters.push(`receivedDateTime ge
|
|
141
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
143
142
|
}
|
|
144
143
|
if (criteria.before) {
|
|
145
144
|
const beforeDate = this.formatDateForOData(criteria.before);
|
|
146
|
-
filters.push(`receivedDateTime le
|
|
145
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
147
146
|
}
|
|
148
147
|
// ✅ SAFE: Boolean filters
|
|
149
148
|
if (criteria.hasAttachment !== undefined) {
|
|
@@ -1007,8 +1006,13 @@ ${originalBodyContent}
|
|
|
1007
1006
|
(criteria.from && !criteria.from.includes('@')) // name searches
|
|
1008
1007
|
);
|
|
1009
1008
|
try {
|
|
1009
|
+
// STRATEGY 0: FOLDER SEARCH - Handle folder searches with optimized methods
|
|
1010
|
+
if (criteria.folder) {
|
|
1011
|
+
logger.log('🔍 Using OPTIMIZED FOLDER SEARCH strategy');
|
|
1012
|
+
allMessages = await this.performOptimizedFolderSearch(criteria, maxResults);
|
|
1013
|
+
}
|
|
1010
1014
|
// STRATEGY A: Pure Filter Strategy (when no search fields present)
|
|
1011
|
-
if (hasFilterableFields && !hasSearchableFields) {
|
|
1015
|
+
else if (hasFilterableFields && !hasSearchableFields) {
|
|
1012
1016
|
logger.log('🔍 Using PURE FILTER strategy (structured queries only)');
|
|
1013
1017
|
allMessages = await this.performPureFilterSearch(criteria, maxResults);
|
|
1014
1018
|
}
|
|
@@ -1029,7 +1033,8 @@ ${originalBodyContent}
|
|
|
1029
1033
|
allMessages = basicResult.messages;
|
|
1030
1034
|
}
|
|
1031
1035
|
// Apply manual filtering for unsupported fields (to/cc names, complex logic)
|
|
1032
|
-
|
|
1036
|
+
// Note: Skip manual filtering for folder searches as they handle it internally
|
|
1037
|
+
const filteredMessages = criteria.folder ? allMessages : this.applyManualFiltering(allMessages, criteria);
|
|
1033
1038
|
// Sort by relevance and date
|
|
1034
1039
|
const sortedMessages = this.sortSearchResults(filteredMessages, criteria);
|
|
1035
1040
|
// Apply maxResults limit
|
|
@@ -1420,11 +1425,8 @@ ${originalBodyContent}
|
|
|
1420
1425
|
filters.push(`contains(from/emailAddress/name,'${escapedFrom}')`);
|
|
1421
1426
|
}
|
|
1422
1427
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
const escapedTo = this.escapeODataValue(criteria.to);
|
|
1426
|
-
filters.push(`toRecipients/any(r: r/emailAddress/address eq '${escapedTo}')`);
|
|
1427
|
-
}
|
|
1428
|
+
// NOTE: toRecipients filtering not supported by Microsoft Graph OData API
|
|
1429
|
+
// Manual filtering will be applied later
|
|
1428
1430
|
if (criteria.cc && criteria.cc.includes('@')) {
|
|
1429
1431
|
// Properly escape single quotes in email addresses
|
|
1430
1432
|
const escapedCc = this.escapeODataValue(criteria.cc);
|
|
@@ -1436,14 +1438,14 @@ ${originalBodyContent}
|
|
|
1436
1438
|
filters.push(`contains(subject,'${escapedSubject}')`);
|
|
1437
1439
|
}
|
|
1438
1440
|
if (criteria.after) {
|
|
1439
|
-
// Fix date formatting - use proper
|
|
1441
|
+
// Fix date formatting - use proper DateTimeOffset format without quotes
|
|
1440
1442
|
const afterDate = this.formatDateForOData(criteria.after);
|
|
1441
|
-
filters.push(`receivedDateTime ge
|
|
1443
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
1442
1444
|
}
|
|
1443
1445
|
if (criteria.before) {
|
|
1444
|
-
// Fix date formatting - use proper
|
|
1446
|
+
// Fix date formatting - use proper DateTimeOffset format without quotes
|
|
1445
1447
|
const beforeDate = this.formatDateForOData(criteria.before);
|
|
1446
|
-
filters.push(`receivedDateTime le
|
|
1448
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
1447
1449
|
}
|
|
1448
1450
|
if (criteria.isUnread !== undefined) {
|
|
1449
1451
|
filters.push(`isRead eq ${!criteria.isUnread}`);
|
|
@@ -2080,11 +2082,11 @@ ${originalBodyContent}
|
|
|
2080
2082
|
const filters = [];
|
|
2081
2083
|
if (additionalCriteria.after) {
|
|
2082
2084
|
const afterDate = this.formatDateForOData(additionalCriteria.after);
|
|
2083
|
-
filters.push(`receivedDateTime ge
|
|
2085
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
2084
2086
|
}
|
|
2085
2087
|
if (additionalCriteria.before) {
|
|
2086
2088
|
const beforeDate = this.formatDateForOData(additionalCriteria.before);
|
|
2087
|
-
filters.push(`receivedDateTime le
|
|
2089
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
2088
2090
|
}
|
|
2089
2091
|
if (additionalCriteria.isUnread !== undefined) {
|
|
2090
2092
|
filters.push(`isRead eq ${!additionalCriteria.isUnread}`);
|
|
@@ -2377,7 +2379,25 @@ ${originalBodyContent}
|
|
|
2377
2379
|
return false;
|
|
2378
2380
|
}
|
|
2379
2381
|
if (criteria.to) {
|
|
2380
|
-
const
|
|
2382
|
+
const searchTerm = criteria.to.toLowerCase().trim();
|
|
2383
|
+
const toMatch = message.toRecipients.some(recipient => {
|
|
2384
|
+
const recipientName = recipient.name.toLowerCase();
|
|
2385
|
+
const recipientAddress = recipient.address.toLowerCase();
|
|
2386
|
+
// Multiple matching strategies for robust TO filtering
|
|
2387
|
+
return (
|
|
2388
|
+
// Exact email match
|
|
2389
|
+
recipientAddress === searchTerm ||
|
|
2390
|
+
// Email contains search term
|
|
2391
|
+
recipientAddress.includes(searchTerm) ||
|
|
2392
|
+
// Full name match
|
|
2393
|
+
recipientName === searchTerm ||
|
|
2394
|
+
// Name contains search term
|
|
2395
|
+
recipientName.includes(searchTerm) ||
|
|
2396
|
+
// Split name matching (for "first last" searches)
|
|
2397
|
+
searchTerm.split(/\s+/).every(part => recipientName.includes(part)) ||
|
|
2398
|
+
// Word boundary matching
|
|
2399
|
+
new RegExp(`\\b${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'i').test(recipientName));
|
|
2400
|
+
});
|
|
2381
2401
|
if (!toMatch)
|
|
2382
2402
|
return false;
|
|
2383
2403
|
}
|
|
@@ -2646,14 +2666,14 @@ ${originalBodyContent}
|
|
|
2646
2666
|
filters.push(`contains(subject,'${escapedSubject}')`);
|
|
2647
2667
|
}
|
|
2648
2668
|
if (criteria.after) {
|
|
2649
|
-
// Fix date formatting - use proper
|
|
2669
|
+
// Fix date formatting - use proper DateTimeOffset format without quotes
|
|
2650
2670
|
const afterDate = this.formatDateForOData(criteria.after);
|
|
2651
|
-
filters.push(`receivedDateTime ge
|
|
2671
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
2652
2672
|
}
|
|
2653
2673
|
if (criteria.before) {
|
|
2654
|
-
// Fix date formatting - use proper
|
|
2674
|
+
// Fix date formatting - use proper DateTimeOffset format without quotes
|
|
2655
2675
|
const beforeDate = this.formatDateForOData(criteria.before);
|
|
2656
|
-
filters.push(`receivedDateTime le
|
|
2676
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
2657
2677
|
}
|
|
2658
2678
|
if (criteria.isUnread !== undefined) {
|
|
2659
2679
|
filters.push(`isRead eq ${!criteria.isUnread}`);
|
|
@@ -3000,10 +3020,8 @@ ${originalBodyContent}
|
|
|
3000
3020
|
const escapedFrom = this.escapeODataValue(criteria.from);
|
|
3001
3021
|
filters.push(`contains(from/emailAddress/address,'${escapedFrom}') or contains(from/emailAddress/name,'${escapedFrom}')`);
|
|
3002
3022
|
}
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
filters.push(`toRecipients/any(r:contains(r/emailAddress/address,'${escapedTo}'))`);
|
|
3006
|
-
}
|
|
3023
|
+
// NOTE: toRecipients filtering not supported by Microsoft Graph OData API
|
|
3024
|
+
// Manual filtering will be applied after retrieval
|
|
3007
3025
|
if (criteria.cc) {
|
|
3008
3026
|
const escapedCc = this.escapeODataValue(criteria.cc);
|
|
3009
3027
|
filters.push(`ccRecipients/any(r:contains(r/emailAddress/address,'${escapedCc}'))`);
|
|
@@ -3014,11 +3032,11 @@ ${originalBodyContent}
|
|
|
3014
3032
|
}
|
|
3015
3033
|
if (criteria.after) {
|
|
3016
3034
|
const afterDate = this.formatDateForOData(criteria.after);
|
|
3017
|
-
filters.push(`receivedDateTime ge
|
|
3035
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
3018
3036
|
}
|
|
3019
3037
|
if (criteria.before) {
|
|
3020
3038
|
const beforeDate = this.formatDateForOData(criteria.before);
|
|
3021
|
-
filters.push(`receivedDateTime le
|
|
3039
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
3022
3040
|
}
|
|
3023
3041
|
if (criteria.isUnread !== undefined) {
|
|
3024
3042
|
filters.push(`isRead eq ${!criteria.isUnread}`);
|
|
@@ -3418,9 +3436,9 @@ ${originalBodyContent}
|
|
|
3418
3436
|
.api(`/me/mailFolders/${folder.id}/messages`)
|
|
3419
3437
|
.select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink')
|
|
3420
3438
|
.orderby('receivedDateTime desc')
|
|
3421
|
-
.top(Math.min(maxResults *
|
|
3439
|
+
.top(Math.min(maxResults * 10, 1000)); // Increased limit for TO searches to find more recipients
|
|
3422
3440
|
// Build filters for better performance
|
|
3423
|
-
const filters = [`receivedDateTime ge
|
|
3441
|
+
const filters = [`receivedDateTime ge ${recentDateStr}T00:00:00Z`];
|
|
3424
3442
|
if (criteria.from) {
|
|
3425
3443
|
const escapedFrom = this.escapeODataValue(criteria.from);
|
|
3426
3444
|
if (criteria.from.includes('@')) {
|
|
@@ -3434,6 +3452,8 @@ ${originalBodyContent}
|
|
|
3434
3452
|
const escapedSubject = this.escapeODataValue(criteria.subject);
|
|
3435
3453
|
filters.push(`contains(subject,'${escapedSubject}')`);
|
|
3436
3454
|
}
|
|
3455
|
+
// NOTE: toRecipients filtering is not supported by Microsoft Graph OData API
|
|
3456
|
+
// Manual filtering will be applied after retrieving results
|
|
3437
3457
|
if (criteria.isUnread !== undefined) {
|
|
3438
3458
|
filters.push(`isRead eq ${!criteria.isUnread}`);
|
|
3439
3459
|
}
|
|
@@ -3453,6 +3473,14 @@ ${originalBodyContent}
|
|
|
3453
3473
|
return messageText.includes(searchText);
|
|
3454
3474
|
});
|
|
3455
3475
|
}
|
|
3476
|
+
// Apply manual TO filtering for names that might not be caught by OData filter
|
|
3477
|
+
if (criteria.to && !criteria.to.includes('@')) {
|
|
3478
|
+
messages = messages.filter(message => {
|
|
3479
|
+
const toSearchTerm = criteria.to.toLowerCase();
|
|
3480
|
+
return message.toRecipients.some(recipient => recipient.name.toLowerCase().includes(toSearchTerm) ||
|
|
3481
|
+
recipient.address.toLowerCase().includes(toSearchTerm));
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3456
3484
|
logger.log(`📧 Large folder search Phase 1 result: ${messages.length} emails found`);
|
|
3457
3485
|
// If we have enough results or found good matches, return
|
|
3458
3486
|
if (messages.length >= maxResults || messages.length > 5) {
|
|
@@ -3470,8 +3498,8 @@ ${originalBodyContent}
|
|
|
3470
3498
|
.top(300);
|
|
3471
3499
|
// More restrictive filters for older search
|
|
3472
3500
|
const olderFilters = [
|
|
3473
|
-
`receivedDateTime ge
|
|
3474
|
-
`receivedDateTime lt
|
|
3501
|
+
`receivedDateTime ge ${olderDateStr}T00:00:00Z`,
|
|
3502
|
+
`receivedDateTime lt ${recentDateStr}T00:00:00Z`
|
|
3475
3503
|
];
|
|
3476
3504
|
// Must have at least one specific criteria for older search
|
|
3477
3505
|
if (criteria.from) {
|
|
@@ -3483,13 +3511,17 @@ ${originalBodyContent}
|
|
|
3483
3511
|
olderFilters.push(`contains(from/emailAddress/name,'${escapedFrom}')`);
|
|
3484
3512
|
}
|
|
3485
3513
|
}
|
|
3514
|
+
else if (criteria.to) {
|
|
3515
|
+
// NOTE: toRecipients filtering is not supported by Microsoft Graph OData API
|
|
3516
|
+
// Skip OData filtering, manual filtering will be applied later
|
|
3517
|
+
}
|
|
3486
3518
|
else if (criteria.subject) {
|
|
3487
3519
|
const escapedSubject = this.escapeODataValue(criteria.subject);
|
|
3488
3520
|
olderFilters.push(`contains(subject,'${escapedSubject}')`);
|
|
3489
3521
|
}
|
|
3490
3522
|
else {
|
|
3491
|
-
// If no specific sender/subject, skip older search to avoid timeout
|
|
3492
|
-
logger.log(`⚠️ No specific sender/subject for older search. Returning recent results only.`);
|
|
3523
|
+
// If no specific sender/subject/to, skip older search to avoid timeout
|
|
3524
|
+
logger.log(`⚠️ No specific sender/subject/to for older search. Returning recent results only.`);
|
|
3493
3525
|
return messages.slice(0, maxResults);
|
|
3494
3526
|
}
|
|
3495
3527
|
if (criteria.importance) {
|
|
@@ -3509,6 +3541,14 @@ ${originalBodyContent}
|
|
|
3509
3541
|
return messageText.includes(searchText);
|
|
3510
3542
|
});
|
|
3511
3543
|
}
|
|
3544
|
+
// Apply manual TO filtering for names to older results
|
|
3545
|
+
if (criteria.to && !criteria.to.includes('@')) {
|
|
3546
|
+
filteredOlderMessages = filteredOlderMessages.filter(message => {
|
|
3547
|
+
const toSearchTerm = criteria.to.toLowerCase();
|
|
3548
|
+
return message.toRecipients.some(recipient => recipient.name.toLowerCase().includes(toSearchTerm) ||
|
|
3549
|
+
recipient.address.toLowerCase().includes(toSearchTerm));
|
|
3550
|
+
});
|
|
3551
|
+
}
|
|
3512
3552
|
logger.log(`📧 Large folder search Phase 2 result: ${filteredOlderMessages.length} additional emails found`);
|
|
3513
3553
|
// Combine results and avoid duplicates
|
|
3514
3554
|
const allMessages = [...messages];
|
|
@@ -3528,7 +3568,7 @@ ${originalBodyContent}
|
|
|
3528
3568
|
.api(`/me/mailFolders/${folder.id}/messages`)
|
|
3529
3569
|
.select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink')
|
|
3530
3570
|
.orderby('receivedDateTime desc')
|
|
3531
|
-
.top(Math.min(maxResults *
|
|
3571
|
+
.top(Math.min(maxResults * 10, 999)); // Increased limit for TO searches to find more recipients
|
|
3532
3572
|
// Build filters
|
|
3533
3573
|
const filters = [];
|
|
3534
3574
|
if (criteria.from) {
|
|
@@ -3540,13 +3580,11 @@ ${originalBodyContent}
|
|
|
3540
3580
|
filters.push(`contains(from/emailAddress/name,'${escapedFrom}')`);
|
|
3541
3581
|
}
|
|
3542
3582
|
}
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
filters.push(`toRecipients/any(r: r/emailAddress/address eq '${escapedTo}')`);
|
|
3546
|
-
}
|
|
3583
|
+
// NOTE: toRecipients filtering not supported by Microsoft Graph OData API
|
|
3584
|
+
// Manual filtering will be applied after retrieval
|
|
3547
3585
|
if (criteria.cc && criteria.cc.includes('@')) {
|
|
3548
3586
|
const escapedCc = this.escapeODataValue(criteria.cc);
|
|
3549
|
-
filters.push(`ccRecipients/any(
|
|
3587
|
+
filters.push(`ccRecipients/any(c: c/emailAddress/address eq '${escapedCc}')`);
|
|
3550
3588
|
}
|
|
3551
3589
|
if (criteria.subject) {
|
|
3552
3590
|
const escapedSubject = this.escapeODataValue(criteria.subject);
|
|
@@ -3554,11 +3592,11 @@ ${originalBodyContent}
|
|
|
3554
3592
|
}
|
|
3555
3593
|
if (criteria.after) {
|
|
3556
3594
|
const afterDate = this.formatDateForOData(criteria.after);
|
|
3557
|
-
filters.push(`receivedDateTime ge
|
|
3595
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
3558
3596
|
}
|
|
3559
3597
|
if (criteria.before) {
|
|
3560
3598
|
const beforeDate = this.formatDateForOData(criteria.before);
|
|
3561
|
-
filters.push(`receivedDateTime le
|
|
3599
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
3562
3600
|
}
|
|
3563
3601
|
if (criteria.isUnread !== undefined) {
|
|
3564
3602
|
filters.push(`isRead eq ${!criteria.isUnread}`);
|
|
@@ -3584,6 +3622,14 @@ ${originalBodyContent}
|
|
|
3584
3622
|
return messageText.includes(searchText);
|
|
3585
3623
|
});
|
|
3586
3624
|
}
|
|
3625
|
+
// Apply manual TO filtering for names that might not be caught by OData filter
|
|
3626
|
+
if (criteria.to && !criteria.to.includes('@')) {
|
|
3627
|
+
messages = messages.filter(message => {
|
|
3628
|
+
const toSearchTerm = criteria.to.toLowerCase();
|
|
3629
|
+
return message.toRecipients.some(recipient => recipient.name.toLowerCase().includes(toSearchTerm) ||
|
|
3630
|
+
recipient.address.toLowerCase().includes(toSearchTerm));
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3587
3633
|
logger.log(`📧 Standard folder search result: ${messages.length} emails found`);
|
|
3588
3634
|
return messages.slice(0, maxResults);
|
|
3589
3635
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ms365-mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.18",
|
|
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",
|