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 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 filtering and date ranges
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`** - General search with manual recipient specification
143
- - Requires you to specify email addresses in `to` or `cc` fields
144
- - More flexible for searching any recipient combinations
145
- - Example: `{"to": "someone@company.com", "from": "boss@company.com"}`
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.3"
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
- emailSearchTerms.push(`from:${criteria.from}`);
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
- // Build search and filter queries
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 fromMatch = message.from.address.toLowerCase().includes(criteria.from.toLowerCase()) ||
399
- message.from.name.toLowerCase().includes(criteria.from.toLowerCase());
400
- if (!fromMatch)
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",
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",