ms365-mcp-server 1.1.17 → 1.1.19
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/README.md +6 -0
- package/dist/index.js +1 -1
- package/dist/utils/ms365-operations.js +126 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,12 @@ A powerful **Model Context Protocol (MCP) server** that enables seamless Microso
|
|
|
12
12
|
- **📁 Smart Contact Management** - Search and retrieve your Microsoft 365 contacts
|
|
13
13
|
- **🌐 Cross-Platform** - Works on macOS, Linux, and Windows
|
|
14
14
|
|
|
15
|
+
## 📚 Documentation
|
|
16
|
+
|
|
17
|
+
For detailed technical documentation, enhancement reports, and guides, see the **[docs/](./docs/)** directory:
|
|
18
|
+
- **[Enhancement Reports](./docs/MS365-MCP-Server-Enhancement-Report.md)** - Recent fixes and improvements
|
|
19
|
+
- **[Technical Guides](./docs/)** - Batch operations and Graph API implementation guides
|
|
20
|
+
|
|
15
21
|
## 🛠️ Available Tools (6 Total)
|
|
16
22
|
|
|
17
23
|
### **📧 Email Management**
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export class MS365Operations {
|
|
|
7
7
|
constructor() {
|
|
8
8
|
this.graphClient = null;
|
|
9
9
|
this.searchCache = new Map();
|
|
10
|
-
this.CACHE_DURATION =
|
|
10
|
+
this.CACHE_DURATION = 300 * 1000; // 5 minute cache for better performance
|
|
11
11
|
this.MAX_RETRIES = 3;
|
|
12
12
|
this.BASE_DELAY = 1000; // 1 second
|
|
13
13
|
}
|
|
@@ -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}`);
|
|
@@ -1622,7 +1624,18 @@ ${originalBodyContent}
|
|
|
1622
1624
|
// Other filters remain the same but are more robust
|
|
1623
1625
|
if (criteria.to && !message.toRecipients.some(r => r.address.toLowerCase().includes(criteria.to.toLowerCase())))
|
|
1624
1626
|
return false;
|
|
1625
|
-
if (criteria.cc && (!message.ccRecipients || !message.ccRecipients.some(r =>
|
|
1627
|
+
if (criteria.cc && (!message.ccRecipients || !message.ccRecipients.some(r => {
|
|
1628
|
+
const searchTerm = criteria.cc.toLowerCase().trim();
|
|
1629
|
+
const recipientName = r.name.toLowerCase();
|
|
1630
|
+
const recipientAddress = r.address.toLowerCase();
|
|
1631
|
+
// Multiple matching strategies for robust CC filtering
|
|
1632
|
+
return (recipientAddress === searchTerm ||
|
|
1633
|
+
recipientAddress.includes(searchTerm) ||
|
|
1634
|
+
recipientName === searchTerm ||
|
|
1635
|
+
recipientName.includes(searchTerm) ||
|
|
1636
|
+
searchTerm.split(/\s+/).every(part => recipientName.includes(part)) ||
|
|
1637
|
+
new RegExp(`\\b${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'i').test(recipientName));
|
|
1638
|
+
})))
|
|
1626
1639
|
return false;
|
|
1627
1640
|
if (criteria.subject && !message.subject.toLowerCase().includes(criteria.subject.toLowerCase()))
|
|
1628
1641
|
return false;
|
|
@@ -2080,11 +2093,11 @@ ${originalBodyContent}
|
|
|
2080
2093
|
const filters = [];
|
|
2081
2094
|
if (additionalCriteria.after) {
|
|
2082
2095
|
const afterDate = this.formatDateForOData(additionalCriteria.after);
|
|
2083
|
-
filters.push(`receivedDateTime ge
|
|
2096
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
2084
2097
|
}
|
|
2085
2098
|
if (additionalCriteria.before) {
|
|
2086
2099
|
const beforeDate = this.formatDateForOData(additionalCriteria.before);
|
|
2087
|
-
filters.push(`receivedDateTime le
|
|
2100
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
2088
2101
|
}
|
|
2089
2102
|
if (additionalCriteria.isUnread !== undefined) {
|
|
2090
2103
|
filters.push(`isRead eq ${!additionalCriteria.isUnread}`);
|
|
@@ -2377,13 +2390,49 @@ ${originalBodyContent}
|
|
|
2377
2390
|
return false;
|
|
2378
2391
|
}
|
|
2379
2392
|
if (criteria.to) {
|
|
2380
|
-
const
|
|
2393
|
+
const searchTerm = criteria.to.toLowerCase().trim();
|
|
2394
|
+
const toMatch = message.toRecipients.some(recipient => {
|
|
2395
|
+
const recipientName = recipient.name.toLowerCase();
|
|
2396
|
+
const recipientAddress = recipient.address.toLowerCase();
|
|
2397
|
+
// Multiple matching strategies for robust TO filtering
|
|
2398
|
+
return (
|
|
2399
|
+
// Exact email match
|
|
2400
|
+
recipientAddress === searchTerm ||
|
|
2401
|
+
// Email contains search term
|
|
2402
|
+
recipientAddress.includes(searchTerm) ||
|
|
2403
|
+
// Full name match
|
|
2404
|
+
recipientName === searchTerm ||
|
|
2405
|
+
// Name contains search term
|
|
2406
|
+
recipientName.includes(searchTerm) ||
|
|
2407
|
+
// Split name matching (for "first last" searches)
|
|
2408
|
+
searchTerm.split(/\s+/).every(part => recipientName.includes(part)) ||
|
|
2409
|
+
// Word boundary matching
|
|
2410
|
+
new RegExp(`\\b${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'i').test(recipientName));
|
|
2411
|
+
});
|
|
2381
2412
|
if (!toMatch)
|
|
2382
2413
|
return false;
|
|
2383
2414
|
}
|
|
2384
2415
|
if (criteria.cc) {
|
|
2416
|
+
const searchTerm = criteria.cc.toLowerCase().trim();
|
|
2385
2417
|
const ccMatch = message.ccRecipients && message.ccRecipients.length > 0 &&
|
|
2386
|
-
message.ccRecipients.some(recipient =>
|
|
2418
|
+
message.ccRecipients.some(recipient => {
|
|
2419
|
+
const recipientName = recipient.name.toLowerCase();
|
|
2420
|
+
const recipientAddress = recipient.address.toLowerCase();
|
|
2421
|
+
// Multiple matching strategies for robust CC filtering (same as TO)
|
|
2422
|
+
return (
|
|
2423
|
+
// Exact email match
|
|
2424
|
+
recipientAddress === searchTerm ||
|
|
2425
|
+
// Email contains search term
|
|
2426
|
+
recipientAddress.includes(searchTerm) ||
|
|
2427
|
+
// Full name match
|
|
2428
|
+
recipientName === searchTerm ||
|
|
2429
|
+
// Name contains search term
|
|
2430
|
+
recipientName.includes(searchTerm) ||
|
|
2431
|
+
// Split name matching (for "first last" searches)
|
|
2432
|
+
searchTerm.split(/\s+/).every(part => recipientName.includes(part)) ||
|
|
2433
|
+
// Word boundary matching
|
|
2434
|
+
new RegExp(`\\b${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'i').test(recipientName));
|
|
2435
|
+
});
|
|
2387
2436
|
if (!ccMatch)
|
|
2388
2437
|
return false;
|
|
2389
2438
|
}
|
|
@@ -2646,14 +2695,14 @@ ${originalBodyContent}
|
|
|
2646
2695
|
filters.push(`contains(subject,'${escapedSubject}')`);
|
|
2647
2696
|
}
|
|
2648
2697
|
if (criteria.after) {
|
|
2649
|
-
// Fix date formatting - use proper
|
|
2698
|
+
// Fix date formatting - use proper DateTimeOffset format without quotes
|
|
2650
2699
|
const afterDate = this.formatDateForOData(criteria.after);
|
|
2651
|
-
filters.push(`receivedDateTime ge
|
|
2700
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
2652
2701
|
}
|
|
2653
2702
|
if (criteria.before) {
|
|
2654
|
-
// Fix date formatting - use proper
|
|
2703
|
+
// Fix date formatting - use proper DateTimeOffset format without quotes
|
|
2655
2704
|
const beforeDate = this.formatDateForOData(criteria.before);
|
|
2656
|
-
filters.push(`receivedDateTime le
|
|
2705
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
2657
2706
|
}
|
|
2658
2707
|
if (criteria.isUnread !== undefined) {
|
|
2659
2708
|
filters.push(`isRead eq ${!criteria.isUnread}`);
|
|
@@ -3000,10 +3049,8 @@ ${originalBodyContent}
|
|
|
3000
3049
|
const escapedFrom = this.escapeODataValue(criteria.from);
|
|
3001
3050
|
filters.push(`contains(from/emailAddress/address,'${escapedFrom}') or contains(from/emailAddress/name,'${escapedFrom}')`);
|
|
3002
3051
|
}
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
filters.push(`toRecipients/any(r:contains(r/emailAddress/address,'${escapedTo}'))`);
|
|
3006
|
-
}
|
|
3052
|
+
// NOTE: toRecipients filtering not supported by Microsoft Graph OData API
|
|
3053
|
+
// Manual filtering will be applied after retrieval
|
|
3007
3054
|
if (criteria.cc) {
|
|
3008
3055
|
const escapedCc = this.escapeODataValue(criteria.cc);
|
|
3009
3056
|
filters.push(`ccRecipients/any(r:contains(r/emailAddress/address,'${escapedCc}'))`);
|
|
@@ -3014,11 +3061,11 @@ ${originalBodyContent}
|
|
|
3014
3061
|
}
|
|
3015
3062
|
if (criteria.after) {
|
|
3016
3063
|
const afterDate = this.formatDateForOData(criteria.after);
|
|
3017
|
-
filters.push(`receivedDateTime ge
|
|
3064
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
3018
3065
|
}
|
|
3019
3066
|
if (criteria.before) {
|
|
3020
3067
|
const beforeDate = this.formatDateForOData(criteria.before);
|
|
3021
|
-
filters.push(`receivedDateTime le
|
|
3068
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
3022
3069
|
}
|
|
3023
3070
|
if (criteria.isUnread !== undefined) {
|
|
3024
3071
|
filters.push(`isRead eq ${!criteria.isUnread}`);
|
|
@@ -3418,9 +3465,9 @@ ${originalBodyContent}
|
|
|
3418
3465
|
.api(`/me/mailFolders/${folder.id}/messages`)
|
|
3419
3466
|
.select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink')
|
|
3420
3467
|
.orderby('receivedDateTime desc')
|
|
3421
|
-
.top(Math.min(maxResults *
|
|
3468
|
+
.top(Math.min(maxResults * 10, 1000)); // Increased limit for TO searches to find more recipients
|
|
3422
3469
|
// Build filters for better performance
|
|
3423
|
-
const filters = [`receivedDateTime ge
|
|
3470
|
+
const filters = [`receivedDateTime ge ${recentDateStr}T00:00:00Z`];
|
|
3424
3471
|
if (criteria.from) {
|
|
3425
3472
|
const escapedFrom = this.escapeODataValue(criteria.from);
|
|
3426
3473
|
if (criteria.from.includes('@')) {
|
|
@@ -3434,6 +3481,8 @@ ${originalBodyContent}
|
|
|
3434
3481
|
const escapedSubject = this.escapeODataValue(criteria.subject);
|
|
3435
3482
|
filters.push(`contains(subject,'${escapedSubject}')`);
|
|
3436
3483
|
}
|
|
3484
|
+
// NOTE: toRecipients filtering is not supported by Microsoft Graph OData API
|
|
3485
|
+
// Manual filtering will be applied after retrieving results
|
|
3437
3486
|
if (criteria.isUnread !== undefined) {
|
|
3438
3487
|
filters.push(`isRead eq ${!criteria.isUnread}`);
|
|
3439
3488
|
}
|
|
@@ -3453,6 +3502,14 @@ ${originalBodyContent}
|
|
|
3453
3502
|
return messageText.includes(searchText);
|
|
3454
3503
|
});
|
|
3455
3504
|
}
|
|
3505
|
+
// Apply manual TO filtering for names that might not be caught by OData filter
|
|
3506
|
+
if (criteria.to && !criteria.to.includes('@')) {
|
|
3507
|
+
messages = messages.filter(message => {
|
|
3508
|
+
const toSearchTerm = criteria.to.toLowerCase();
|
|
3509
|
+
return message.toRecipients.some(recipient => recipient.name.toLowerCase().includes(toSearchTerm) ||
|
|
3510
|
+
recipient.address.toLowerCase().includes(toSearchTerm));
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3456
3513
|
logger.log(`📧 Large folder search Phase 1 result: ${messages.length} emails found`);
|
|
3457
3514
|
// If we have enough results or found good matches, return
|
|
3458
3515
|
if (messages.length >= maxResults || messages.length > 5) {
|
|
@@ -3470,8 +3527,8 @@ ${originalBodyContent}
|
|
|
3470
3527
|
.top(300);
|
|
3471
3528
|
// More restrictive filters for older search
|
|
3472
3529
|
const olderFilters = [
|
|
3473
|
-
`receivedDateTime ge
|
|
3474
|
-
`receivedDateTime lt
|
|
3530
|
+
`receivedDateTime ge ${olderDateStr}T00:00:00Z`,
|
|
3531
|
+
`receivedDateTime lt ${recentDateStr}T00:00:00Z`
|
|
3475
3532
|
];
|
|
3476
3533
|
// Must have at least one specific criteria for older search
|
|
3477
3534
|
if (criteria.from) {
|
|
@@ -3483,13 +3540,17 @@ ${originalBodyContent}
|
|
|
3483
3540
|
olderFilters.push(`contains(from/emailAddress/name,'${escapedFrom}')`);
|
|
3484
3541
|
}
|
|
3485
3542
|
}
|
|
3543
|
+
else if (criteria.to) {
|
|
3544
|
+
// NOTE: toRecipients filtering is not supported by Microsoft Graph OData API
|
|
3545
|
+
// Skip OData filtering, manual filtering will be applied later
|
|
3546
|
+
}
|
|
3486
3547
|
else if (criteria.subject) {
|
|
3487
3548
|
const escapedSubject = this.escapeODataValue(criteria.subject);
|
|
3488
3549
|
olderFilters.push(`contains(subject,'${escapedSubject}')`);
|
|
3489
3550
|
}
|
|
3490
3551
|
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.`);
|
|
3552
|
+
// If no specific sender/subject/to, skip older search to avoid timeout
|
|
3553
|
+
logger.log(`⚠️ No specific sender/subject/to for older search. Returning recent results only.`);
|
|
3493
3554
|
return messages.slice(0, maxResults);
|
|
3494
3555
|
}
|
|
3495
3556
|
if (criteria.importance) {
|
|
@@ -3509,6 +3570,14 @@ ${originalBodyContent}
|
|
|
3509
3570
|
return messageText.includes(searchText);
|
|
3510
3571
|
});
|
|
3511
3572
|
}
|
|
3573
|
+
// Apply manual TO filtering for names to older results
|
|
3574
|
+
if (criteria.to && !criteria.to.includes('@')) {
|
|
3575
|
+
filteredOlderMessages = filteredOlderMessages.filter(message => {
|
|
3576
|
+
const toSearchTerm = criteria.to.toLowerCase();
|
|
3577
|
+
return message.toRecipients.some(recipient => recipient.name.toLowerCase().includes(toSearchTerm) ||
|
|
3578
|
+
recipient.address.toLowerCase().includes(toSearchTerm));
|
|
3579
|
+
});
|
|
3580
|
+
}
|
|
3512
3581
|
logger.log(`📧 Large folder search Phase 2 result: ${filteredOlderMessages.length} additional emails found`);
|
|
3513
3582
|
// Combine results and avoid duplicates
|
|
3514
3583
|
const allMessages = [...messages];
|
|
@@ -3528,7 +3597,7 @@ ${originalBodyContent}
|
|
|
3528
3597
|
.api(`/me/mailFolders/${folder.id}/messages`)
|
|
3529
3598
|
.select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink')
|
|
3530
3599
|
.orderby('receivedDateTime desc')
|
|
3531
|
-
.top(Math.min(maxResults *
|
|
3600
|
+
.top(Math.min(maxResults * 10, 999)); // Increased limit for TO searches to find more recipients
|
|
3532
3601
|
// Build filters
|
|
3533
3602
|
const filters = [];
|
|
3534
3603
|
if (criteria.from) {
|
|
@@ -3540,13 +3609,11 @@ ${originalBodyContent}
|
|
|
3540
3609
|
filters.push(`contains(from/emailAddress/name,'${escapedFrom}')`);
|
|
3541
3610
|
}
|
|
3542
3611
|
}
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
filters.push(`toRecipients/any(r: r/emailAddress/address eq '${escapedTo}')`);
|
|
3546
|
-
}
|
|
3612
|
+
// NOTE: toRecipients filtering not supported by Microsoft Graph OData API
|
|
3613
|
+
// Manual filtering will be applied after retrieval
|
|
3547
3614
|
if (criteria.cc && criteria.cc.includes('@')) {
|
|
3548
3615
|
const escapedCc = this.escapeODataValue(criteria.cc);
|
|
3549
|
-
filters.push(`ccRecipients/any(
|
|
3616
|
+
filters.push(`ccRecipients/any(c: c/emailAddress/address eq '${escapedCc}')`);
|
|
3550
3617
|
}
|
|
3551
3618
|
if (criteria.subject) {
|
|
3552
3619
|
const escapedSubject = this.escapeODataValue(criteria.subject);
|
|
@@ -3554,11 +3621,11 @@ ${originalBodyContent}
|
|
|
3554
3621
|
}
|
|
3555
3622
|
if (criteria.after) {
|
|
3556
3623
|
const afterDate = this.formatDateForOData(criteria.after);
|
|
3557
|
-
filters.push(`receivedDateTime ge
|
|
3624
|
+
filters.push(`receivedDateTime ge ${afterDate}`);
|
|
3558
3625
|
}
|
|
3559
3626
|
if (criteria.before) {
|
|
3560
3627
|
const beforeDate = this.formatDateForOData(criteria.before);
|
|
3561
|
-
filters.push(`receivedDateTime le
|
|
3628
|
+
filters.push(`receivedDateTime le ${beforeDate}`);
|
|
3562
3629
|
}
|
|
3563
3630
|
if (criteria.isUnread !== undefined) {
|
|
3564
3631
|
filters.push(`isRead eq ${!criteria.isUnread}`);
|
|
@@ -3584,6 +3651,14 @@ ${originalBodyContent}
|
|
|
3584
3651
|
return messageText.includes(searchText);
|
|
3585
3652
|
});
|
|
3586
3653
|
}
|
|
3654
|
+
// Apply manual TO filtering for names that might not be caught by OData filter
|
|
3655
|
+
if (criteria.to && !criteria.to.includes('@')) {
|
|
3656
|
+
messages = messages.filter(message => {
|
|
3657
|
+
const toSearchTerm = criteria.to.toLowerCase();
|
|
3658
|
+
return message.toRecipients.some(recipient => recipient.name.toLowerCase().includes(toSearchTerm) ||
|
|
3659
|
+
recipient.address.toLowerCase().includes(toSearchTerm));
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3587
3662
|
logger.log(`📧 Standard folder search result: ${messages.length} emails found`);
|
|
3588
3663
|
return messages.slice(0, maxResults);
|
|
3589
3664
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ms365-mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.19",
|
|
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",
|