ms365-mcp-server 1.0.3 → 1.0.4
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 +7 -5
- package/dist/index.js +24 -24
- package/dist/utils/ms365-operations.js +90 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ A powerful **Model Context Protocol (MCP) server** that enables seamless Microso
|
|
|
18
18
|
### 📧 Core Email Operations (12 tools)
|
|
19
19
|
- **`send_email`** - Send emails with attachments, HTML/text content, CC/BCC
|
|
20
20
|
- **`read_email`** - Read full email content including headers and attachments
|
|
21
|
-
- **`search_emails`** - Advanced search with
|
|
21
|
+
- **`search_emails`** - Advanced search with intelligent partial name matching
|
|
22
22
|
- **`search_emails_to_me`** - Find emails addressed to YOU (both TO and CC fields)
|
|
23
23
|
- **`list_emails`** - List emails in inbox, sent, or custom folders
|
|
24
24
|
- **`get_attachment`** - Download and retrieve email attachments
|
|
@@ -139,10 +139,12 @@ ms365-mcp-server --setup-auth
|
|
|
139
139
|
|
|
140
140
|
### 🔍 Email Search Options
|
|
141
141
|
|
|
142
|
-
- **`search_emails`** -
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
142
|
+
- **`search_emails`** - Unified search with intelligent capabilities
|
|
143
|
+
- **Smart Name Matching**: Automatically detects names vs email addresses
|
|
144
|
+
- **Partial Name Support**: Search by "Madan", "Kumar", "M Kumar", etc.
|
|
145
|
+
- **Flexible Criteria**: Combine name search with date ranges, subjects, etc.
|
|
146
|
+
- **Email Address Matching**: Use exact email addresses when needed
|
|
147
|
+
- Example: `{"from": "Madan", "after": "2024-01-01"}` or `{"from": "boss@company.com"}`
|
|
146
148
|
|
|
147
149
|
- **`search_emails_to_me`** - Automatic search for emails addressed to YOU
|
|
148
150
|
- Automatically uses your email address for both TO and CC searches
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ function parseArgs() {
|
|
|
55
55
|
}
|
|
56
56
|
const server = new Server({
|
|
57
57
|
name: "ms365-mcp-server",
|
|
58
|
-
version: "1.0.
|
|
58
|
+
version: "1.0.4"
|
|
59
59
|
}, {
|
|
60
60
|
capabilities: {
|
|
61
61
|
resources: {
|
|
@@ -197,7 +197,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
name: "search_emails",
|
|
200
|
-
description: "Search emails with various criteria including subject, sender, TO/CC recipients, date range, and advanced filtering. Supports complex queries and filtering, including emails addressed to you directly or where you were CC'd.",
|
|
200
|
+
description: "Search emails with various criteria including subject, sender, TO/CC recipients, date range, and advanced filtering. Features intelligent partial name matching for senders - just use their first name, last name, or partial name in the 'from' field. Supports complex queries and filtering, including emails addressed to you directly or where you were CC'd.",
|
|
201
201
|
inputSchema: {
|
|
202
202
|
type: "object",
|
|
203
203
|
properties: {
|
|
@@ -911,28 +911,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
911
911
|
}
|
|
912
912
|
]
|
|
913
913
|
};
|
|
914
|
-
case "list_emails":
|
|
915
|
-
if (ms365Config.multiUser) {
|
|
916
|
-
const userId = args?.userId;
|
|
917
|
-
if (!userId) {
|
|
918
|
-
throw new Error("User ID is required in multi-user mode");
|
|
919
|
-
}
|
|
920
|
-
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
921
|
-
ms365Ops.setGraphClient(graphClient);
|
|
922
|
-
}
|
|
923
|
-
else {
|
|
924
|
-
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
925
|
-
ms365Ops.setGraphClient(graphClient);
|
|
926
|
-
}
|
|
927
|
-
const emailList = await ms365Ops.listEmails(args?.folderId, args?.maxResults);
|
|
928
|
-
return {
|
|
929
|
-
content: [
|
|
930
|
-
{
|
|
931
|
-
type: "text",
|
|
932
|
-
text: `📬 Email List (${emailList.messages.length} emails)\n\n${emailList.messages.map((email, index) => `${index + 1}. 📧 ${email.subject}\n 👤 From: ${email.from.name} <${email.from.address}>\n 📅 ${new Date(email.receivedDateTime).toLocaleDateString()}\n ${email.isRead ? '📖' : '📩'} ${email.isRead ? 'Read' : 'Unread'}\n 🆔 ID: ${email.id}\n`).join('\n')}`
|
|
933
|
-
}
|
|
934
|
-
]
|
|
935
|
-
};
|
|
936
914
|
case "mark_email":
|
|
937
915
|
if (ms365Config.multiUser) {
|
|
938
916
|
const userId = args?.userId;
|
|
@@ -1087,6 +1065,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1087
1065
|
}
|
|
1088
1066
|
]
|
|
1089
1067
|
};
|
|
1068
|
+
case "list_emails":
|
|
1069
|
+
if (ms365Config.multiUser) {
|
|
1070
|
+
const userId = args?.userId;
|
|
1071
|
+
if (!userId) {
|
|
1072
|
+
throw new Error("User ID is required in multi-user mode");
|
|
1073
|
+
}
|
|
1074
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1075
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1076
|
+
}
|
|
1077
|
+
else {
|
|
1078
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1079
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1080
|
+
}
|
|
1081
|
+
const emailList = await ms365Ops.listEmails(args?.folderId, args?.maxResults);
|
|
1082
|
+
return {
|
|
1083
|
+
content: [
|
|
1084
|
+
{
|
|
1085
|
+
type: "text",
|
|
1086
|
+
text: `📬 Email List (${emailList.messages.length} emails)\n\n${emailList.messages.map((email, index) => `${index + 1}. 📧 ${email.subject}\n 👤 From: ${email.from.name} <${email.from.address}>\n 📅 ${new Date(email.receivedDateTime).toLocaleDateString()}\n ${email.isRead ? '📖' : '📩'} ${email.isRead ? 'Read' : 'Unread'}\n 🆔 ID: ${email.id}\n`).join('\n')}`
|
|
1087
|
+
}
|
|
1088
|
+
]
|
|
1089
|
+
};
|
|
1090
1090
|
default:
|
|
1091
1091
|
throw new Error(`Unknown tool: ${name}`);
|
|
1092
1092
|
}
|
|
@@ -65,7 +65,20 @@ export class MS365Operations {
|
|
|
65
65
|
// Build email-specific search terms using proper Microsoft Graph syntax
|
|
66
66
|
const emailSearchTerms = [];
|
|
67
67
|
if (criteria.from) {
|
|
68
|
-
|
|
68
|
+
// Check if it looks like an email address for exact matching
|
|
69
|
+
if (criteria.from.includes('@')) {
|
|
70
|
+
emailSearchTerms.push(`from:${criteria.from}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// For names, use general search which is more flexible with partial matching
|
|
74
|
+
// This will trigger manual filtering which supports partial names better
|
|
75
|
+
if (criteria.query) {
|
|
76
|
+
searchTerms.push(`${criteria.query} AND ${criteria.from}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
searchTerms.push(criteria.from);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
69
82
|
}
|
|
70
83
|
if (criteria.to) {
|
|
71
84
|
emailSearchTerms.push(`to:${criteria.to}`);
|
|
@@ -224,7 +237,59 @@ export class MS365Operations {
|
|
|
224
237
|
async searchEmails(criteria = {}) {
|
|
225
238
|
try {
|
|
226
239
|
const graphClient = await this.getGraphClient();
|
|
227
|
-
//
|
|
240
|
+
// For name-based searches, use enhanced approach for better partial matching
|
|
241
|
+
if (criteria.from && !criteria.from.includes('@')) {
|
|
242
|
+
logger.log(`Using enhanced name matching for: "${criteria.from}"`);
|
|
243
|
+
// Get more emails and filter manually for better name matching
|
|
244
|
+
const maxResults = criteria.maxResults || 100;
|
|
245
|
+
try {
|
|
246
|
+
// Get broader set of emails for manual filtering
|
|
247
|
+
const apiCall = graphClient.api('/me/messages')
|
|
248
|
+
.select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink')
|
|
249
|
+
.orderby('receivedDateTime desc')
|
|
250
|
+
.top(Math.min(maxResults * 2, 200)); // Get more emails to filter from
|
|
251
|
+
const result = await apiCall.get();
|
|
252
|
+
let messages = result.value?.map((email) => ({
|
|
253
|
+
id: email.id,
|
|
254
|
+
subject: email.subject || '',
|
|
255
|
+
from: {
|
|
256
|
+
name: email.from?.emailAddress?.name || '',
|
|
257
|
+
address: email.from?.emailAddress?.address || ''
|
|
258
|
+
},
|
|
259
|
+
toRecipients: email.toRecipients?.map((recipient) => ({
|
|
260
|
+
name: recipient.emailAddress?.name || '',
|
|
261
|
+
address: recipient.emailAddress?.address || ''
|
|
262
|
+
})) || [],
|
|
263
|
+
ccRecipients: email.ccRecipients?.map((recipient) => ({
|
|
264
|
+
name: recipient.emailAddress?.name || '',
|
|
265
|
+
address: recipient.emailAddress?.address || ''
|
|
266
|
+
})) || [],
|
|
267
|
+
receivedDateTime: email.receivedDateTime,
|
|
268
|
+
sentDateTime: email.sentDateTime,
|
|
269
|
+
bodyPreview: email.bodyPreview || '',
|
|
270
|
+
isRead: email.isRead || false,
|
|
271
|
+
hasAttachments: email.hasAttachments || false,
|
|
272
|
+
importance: email.importance || 'normal',
|
|
273
|
+
conversationId: email.conversationId || '',
|
|
274
|
+
parentFolderId: email.parentFolderId || '',
|
|
275
|
+
webLink: email.webLink || ''
|
|
276
|
+
})) || [];
|
|
277
|
+
// Apply manual filtering with enhanced name matching
|
|
278
|
+
messages = this.applyManualFiltering(messages, criteria);
|
|
279
|
+
// Apply maxResults limit
|
|
280
|
+
const limitedMessages = messages.slice(0, maxResults);
|
|
281
|
+
logger.log(`Enhanced name search found ${limitedMessages.length} emails matching "${criteria.from}"`);
|
|
282
|
+
return {
|
|
283
|
+
messages: limitedMessages,
|
|
284
|
+
hasMore: messages.length > maxResults || !!result['@odata.nextLink']
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
logger.error('Error in enhanced name search, falling back to standard search:', error);
|
|
289
|
+
// Fall through to standard search logic
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Standard search logic (for email addresses and other criteria)
|
|
228
293
|
const searchQuery = this.buildSearchQuery(criteria);
|
|
229
294
|
const filterQuery = this.buildFilterQuery(criteria);
|
|
230
295
|
// Debug logging
|
|
@@ -395,9 +460,29 @@ export class MS365Operations {
|
|
|
395
460
|
return false;
|
|
396
461
|
}
|
|
397
462
|
if (criteria.from) {
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
463
|
+
const searchTerm = criteria.from.toLowerCase().trim();
|
|
464
|
+
const fromName = message.from.name.toLowerCase();
|
|
465
|
+
const fromAddress = message.from.address.toLowerCase();
|
|
466
|
+
// Multiple matching strategies for better partial name support
|
|
467
|
+
const matches = [
|
|
468
|
+
// Direct name or email match
|
|
469
|
+
fromName.includes(searchTerm),
|
|
470
|
+
fromAddress.includes(searchTerm),
|
|
471
|
+
// Split search term and check if all parts exist in name
|
|
472
|
+
searchTerm.split(/\s+/).every(part => fromName.includes(part)),
|
|
473
|
+
// Check if any word in the name starts with the search term
|
|
474
|
+
fromName.split(/\s+/).some(namePart => namePart.startsWith(searchTerm)),
|
|
475
|
+
// Check if search term matches any word in the name exactly
|
|
476
|
+
fromName.split(/\s+/).some(namePart => namePart === searchTerm),
|
|
477
|
+
// Handle "Last, First" format
|
|
478
|
+
fromName.replace(/,\s*/g, ' ').includes(searchTerm),
|
|
479
|
+
// Handle initials (e.g., "M Kumar" for "Madan Kumar")
|
|
480
|
+
searchTerm.split(/\s+/).length === 2 &&
|
|
481
|
+
fromName.split(/\s+/).length >= 2 &&
|
|
482
|
+
fromName.split(/\s+/)[0].startsWith(searchTerm.split(/\s+/)[0][0]) &&
|
|
483
|
+
fromName.includes(searchTerm.split(/\s+/)[1])
|
|
484
|
+
];
|
|
485
|
+
if (!matches.some(match => match))
|
|
401
486
|
return false;
|
|
402
487
|
}
|
|
403
488
|
if (criteria.to) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ms365-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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",
|