ms365-mcp-server 1.1.13 → 1.1.15

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 CHANGED
@@ -10,7 +10,7 @@ import path from 'path';
10
10
  import { fileURLToPath } from 'url';
11
11
  import fs from 'fs/promises';
12
12
  import crypto from 'crypto';
13
- import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
13
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
14
14
  import { logger } from './utils/api.js';
15
15
  import { MS365Operations } from './utils/ms365-operations.js';
16
16
  import { multiUserMS365Auth } from './utils/multi-user-auth.js';
@@ -67,23 +67,28 @@ function parseArgs() {
67
67
  }
68
68
  const server = new Server({
69
69
  name: "ms365-mcp-server",
70
- version: "1.1.13"
70
+ version: "1.1.15"
71
71
  }, {
72
72
  capabilities: {
73
73
  resources: {
74
74
  read: true,
75
- list: true
75
+ list: true,
76
+ templates: true
76
77
  },
77
78
  tools: {
78
79
  list: true,
79
80
  call: true
80
81
  },
81
82
  prompts: {
83
+ list: true,
84
+ get: true
85
+ },
86
+ resourceTemplates: {
82
87
  list: true
83
88
  }
84
89
  }
85
90
  });
86
- logger.log('Server started with version 1.1.13');
91
+ logger.log('Server started with version 1.1.14');
87
92
  // Set up the resource listing request handler
88
93
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
89
94
  logger.log('Received list resources request');
@@ -103,6 +108,20 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
103
108
  logger.log('Received list prompts request');
104
109
  return { prompts: [] };
105
110
  });
111
+ /**
112
+ * Handler for getting a specific prompt.
113
+ */
114
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
115
+ logger.log('Received get prompt request: ' + JSON.stringify(request));
116
+ throw new Error("Prompt getting not implemented");
117
+ });
118
+ /**
119
+ * Handler for listing available resource templates.
120
+ */
121
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
122
+ logger.log('Received list resource templates request');
123
+ return { resourceTemplates: [] };
124
+ });
106
125
  /**
107
126
  * List available tools for interacting with Microsoft 365.
108
127
  */
@@ -119,19 +138,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
119
138
  description: "User ID for multi-user authentication (required if using multi-user mode)"
120
139
  },
121
140
  to: {
122
- type: "array",
123
- items: { type: "string" },
124
- description: "List of recipient email addresses"
141
+ oneOf: [
142
+ { type: "string" },
143
+ { type: "array", items: { type: "string" } }
144
+ ],
145
+ description: "Recipient email address (string) or list of recipient email addresses (array)"
125
146
  },
126
147
  cc: {
127
- type: "array",
128
- items: { type: "string" },
129
- description: "List of CC recipient email addresses (optional)"
148
+ oneOf: [
149
+ { type: "string" },
150
+ { type: "array", items: { type: "string" } }
151
+ ],
152
+ description: "CC recipient email address (string) or list of CC recipient email addresses (array) - optional"
130
153
  },
131
154
  bcc: {
132
- type: "array",
133
- items: { type: "string" },
134
- description: "List of BCC recipient email addresses (optional)"
155
+ oneOf: [
156
+ { type: "string" },
157
+ { type: "array", items: { type: "string" } }
158
+ ],
159
+ description: "BCC recipient email address (string) or list of BCC recipient email addresses (array) - optional"
135
160
  },
136
161
  subject: {
137
162
  type: "string",
@@ -180,8 +205,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
180
205
  description: "List of email attachments (optional)"
181
206
  }
182
207
  },
183
- required: ["to", "subject"],
184
- additionalProperties: false
208
+ required: ["to", "subject"]
185
209
  }
186
210
  },
187
211
  {
@@ -196,8 +220,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
196
220
  },
197
221
  action: {
198
222
  type: "string",
199
- enum: ["read", "search", "list", "mark", "move", "delete", "search_to_me", "draft", "update_draft", "send_draft", "list_drafts", "reply_draft", "forward_draft"],
200
- description: "Action to perform: read (get email by ID), search (find emails), list (folder contents), mark (read/unread), move (to folder), delete (permanently), search_to_me (emails addressed to you), draft (create standalone draft - NOTE: Use reply_draft/forward_draft for threaded drafts), update_draft (modify existing draft), send_draft (send saved draft), list_drafts (list draft emails), reply_draft (create threaded reply draft that appears in conversation), forward_draft (create threaded forward draft that appears in conversation)"
223
+ enum: ["read", "search", "search_batched", "list", "mark", "move", "delete", "search_to_me", "draft", "update_draft", "send_draft", "list_drafts", "reply_draft", "forward_draft"],
224
+ description: "Action to perform: read (get email by ID), search (find emails), search_batched (find emails in batches of 50, up to 5 batches for better performance), list (folder contents), mark (read/unread), move (to folder), delete (permanently), search_to_me (emails addressed to you), draft (create standalone draft - NOTE: Use reply_draft/forward_draft for threaded drafts), update_draft (modify existing draft), send_draft (send saved draft), list_drafts (list draft emails), reply_draft (create threaded reply draft that appears in conversation), forward_draft (create threaded forward draft that appears in conversation)"
201
225
  },
202
226
  messageId: {
203
227
  type: "string",
@@ -268,26 +292,45 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
268
292
  },
269
293
  maxResults: {
270
294
  type: "number",
271
- description: "Maximum number of results to return (default: 50, max: 200)",
272
- minimum: 1,
273
- maximum: 200,
295
+ description: "Maximum number of results to return. Set to 0 or omit to get ALL matching results (safety limited to 1000). For large result sets, use specific maxResults values to avoid timeouts. Default: 50",
296
+ minimum: 0,
297
+ default: 50
298
+ },
299
+ batchSize: {
300
+ type: "number",
301
+ description: "Number of results per batch for search_batched action (default: 50)",
302
+ minimum: 10,
303
+ maximum: 100,
274
304
  default: 50
275
305
  },
306
+ maxBatches: {
307
+ type: "number",
308
+ description: "Maximum number of batches for search_batched action (default: 5)",
309
+ minimum: 1,
310
+ maximum: 10,
311
+ default: 5
312
+ },
276
313
  // Draft email parameters
277
314
  draftTo: {
278
- type: "array",
279
- items: { type: "string" },
280
- description: "List of recipient email addresses (required for draft action)"
315
+ oneOf: [
316
+ { type: "string" },
317
+ { type: "array", items: { type: "string" } }
318
+ ],
319
+ description: "Recipient email address (string) or list of recipient email addresses (array) - required for draft action"
281
320
  },
282
321
  draftCc: {
283
- type: "array",
284
- items: { type: "string" },
285
- description: "List of CC recipient email addresses (optional for draft action)"
322
+ oneOf: [
323
+ { type: "string" },
324
+ { type: "array", items: { type: "string" } }
325
+ ],
326
+ description: "CC recipient email address (string) or list of CC recipient email addresses (array) - optional for draft action"
286
327
  },
287
328
  draftBcc: {
288
- type: "array",
289
- items: { type: "string" },
290
- description: "List of BCC recipient email addresses (optional for draft action)"
329
+ oneOf: [
330
+ { type: "string" },
331
+ { type: "array", items: { type: "string" } }
332
+ ],
333
+ description: "BCC recipient email address (string) or list of BCC recipient email addresses (array) - optional for draft action"
291
334
  },
292
335
  draftSubject: {
293
336
  type: "string",
@@ -350,8 +393,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
350
393
  description: "Conversation ID for threading drafts (optional)"
351
394
  }
352
395
  },
353
- required: ["action"],
354
- additionalProperties: false
396
+ required: ["action"]
355
397
  }
356
398
  },
357
399
  {
@@ -373,8 +415,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
373
415
  description: "Attachment ID to download"
374
416
  }
375
417
  },
376
- required: ["messageId", "attachmentId"],
377
- additionalProperties: false
418
+ required: ["messageId", "attachmentId"]
378
419
  }
379
420
  },
380
421
  {
@@ -395,8 +436,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
395
436
  type: "string",
396
437
  description: "Optional: Search for folders by name (case-insensitive partial match)"
397
438
  }
398
- },
399
- additionalProperties: false
439
+ }
400
440
  }
401
441
  },
402
442
  {
@@ -425,8 +465,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
425
465
  minimum: 1,
426
466
  maximum: 500
427
467
  }
428
- },
429
- additionalProperties: false
468
+ }
430
469
  }
431
470
  }
432
471
  ];
@@ -442,8 +481,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
442
481
  type: "string",
443
482
  description: "User's email address (optional, for identification)"
444
483
  }
445
- },
446
- additionalProperties: false
484
+ }
447
485
  }
448
486
  },
449
487
  {
@@ -457,8 +495,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
457
495
  description: "Your User ID to remove"
458
496
  }
459
497
  },
460
- required: ["userId"],
461
- additionalProperties: false
498
+ required: ["userId"]
462
499
  }
463
500
  }
464
501
  ];
@@ -486,8 +523,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
486
523
  description: "Account key for logout action (default: current user)",
487
524
  default: "default-user"
488
525
  }
489
- },
490
- additionalProperties: false
526
+ }
491
527
  }
492
528
  }
493
529
  ];
@@ -722,6 +758,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
722
758
  }
723
759
  ]
724
760
  };
761
+ case "search_batched":
762
+ const batchSize = args?.batchSize || 50;
763
+ const maxBatches = args?.maxBatches || 5;
764
+ logger.log(`DEBUG: Starting batched search with batchSize: ${batchSize}, maxBatches: ${maxBatches}`);
765
+ const batchedSearchResults = await ms365Ops.searchEmailsBatched(args, batchSize, maxBatches);
766
+ logger.log(`DEBUG: Batched search results count: ${batchedSearchResults.messages.length} from ${batchedSearchResults.totalBatches} batches`);
767
+ // Enhanced feedback for batched search results
768
+ let batchedResponseText = `šŸ” Batched Email Search Results (${batchedSearchResults.messages.length} found from ${batchedSearchResults.totalBatches} batches)`;
769
+ if (batchedSearchResults.messages.length === 0) {
770
+ batchedResponseText = `šŸ” No emails found matching your criteria.\n\nšŸ’” Search Tips:\n`;
771
+ if (args?.from) {
772
+ batchedResponseText += `• Try partial names: "${args.from.split(' ')[0]}" or "${args.from.split(' ').pop()}"\n`;
773
+ batchedResponseText += `• Check spelling of sender name\n`;
774
+ }
775
+ if (args?.subject) {
776
+ batchedResponseText += `• Try broader subject terms\n`;
777
+ }
778
+ if (args?.after || args?.before) {
779
+ batchedResponseText += `• Try expanding date range\n`;
780
+ }
781
+ batchedResponseText += `• Remove some search criteria to get broader results`;
782
+ }
783
+ else {
784
+ batchedResponseText += `\n\n${batchedSearchResults.messages.map((email, index) => {
785
+ // Handle missing IDs more gracefully
786
+ const emailId = email.id || 'ID_MISSING';
787
+ const fromDisplay = email.from?.name ? `${email.from.name} <${email.from.address}>` : email.from?.address || 'Unknown sender';
788
+ return `${index + 1}. šŸ“§ ${email.subject || 'No subject'}\n šŸ‘¤ From: ${fromDisplay}\n šŸ“… ${email.receivedDateTime ? new Date(email.receivedDateTime).toLocaleDateString() : 'Unknown date'}\n ${email.isRead ? 'šŸ“– Read' : 'šŸ“© Unread'}\n šŸ†” ID: ${emailId}\n`;
789
+ }).join('\n')}`;
790
+ if (batchedSearchResults.hasMore) {
791
+ batchedResponseText += `\nšŸ’” There are more results available. Increase maxBatches parameter to get more emails.`;
792
+ }
793
+ batchedResponseText += `\n\nšŸ“Š Search Summary:\n`;
794
+ batchedResponseText += `• Total results: ${batchedSearchResults.messages.length}\n`;
795
+ batchedResponseText += `• Batches processed: ${batchedSearchResults.totalBatches}/${maxBatches}\n`;
796
+ batchedResponseText += `• Batch size: ${batchSize}\n`;
797
+ batchedResponseText += `• More results available: ${batchedSearchResults.hasMore ? 'Yes' : 'No'}`;
798
+ }
799
+ return {
800
+ content: [
801
+ {
802
+ type: "text",
803
+ text: batchedResponseText
804
+ }
805
+ ]
806
+ };
725
807
  case "search_to_me":
726
808
  const searchToMeResults = await ms365Ops.searchEmailsToMe(args);
727
809
  // Process attachments for each email
@@ -851,15 +933,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
851
933
  if (!args?.draftTo || !args?.draftSubject || !args?.draftBody) {
852
934
  throw new Error("draftTo, draftSubject, and draftBody are required for draft action");
853
935
  }
936
+ // Helper function to normalize email arrays for drafts
937
+ const normalizeDraftEmailArray = (field) => {
938
+ if (!field)
939
+ return [];
940
+ if (typeof field === 'string')
941
+ return [field];
942
+ if (Array.isArray(field))
943
+ return field;
944
+ throw new Error(`Invalid email field format. Expected string or array of strings.`);
945
+ };
854
946
  // Check if this looks like a reply based on subject
855
947
  const isLikelyReply = args.draftSubject?.toString().toLowerCase().startsWith('re:');
856
948
  if (isLikelyReply) {
857
949
  logger.log('šŸ”” SUGGESTION: Consider using "reply_draft" action instead of "draft" for threaded replies that appear in conversations');
858
950
  }
859
951
  const draftResult = await ms365Ops.saveDraftEmail({
860
- to: args.draftTo,
861
- cc: args.draftCc,
862
- bcc: args.draftBcc,
952
+ to: normalizeDraftEmailArray(args.draftTo),
953
+ cc: normalizeDraftEmailArray(args.draftCc),
954
+ bcc: normalizeDraftEmailArray(args.draftBcc),
863
955
  subject: args.draftSubject,
864
956
  body: args.draftBody,
865
957
  bodyType: args.draftBodyType || 'text',
@@ -884,13 +976,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
884
976
  if (!args?.draftId) {
885
977
  throw new Error("draftId is required for update_draft action");
886
978
  }
979
+ // Helper function to normalize email arrays for draft updates
980
+ const normalizeUpdateEmailArray = (field) => {
981
+ if (!field)
982
+ return [];
983
+ if (typeof field === 'string')
984
+ return [field];
985
+ if (Array.isArray(field))
986
+ return field;
987
+ throw new Error(`Invalid email field format. Expected string or array of strings.`);
988
+ };
887
989
  const updates = {};
888
990
  if (args.draftTo)
889
- updates.to = args.draftTo;
991
+ updates.to = normalizeUpdateEmailArray(args.draftTo);
890
992
  if (args.draftCc)
891
- updates.cc = args.draftCc;
993
+ updates.cc = normalizeUpdateEmailArray(args.draftCc);
892
994
  if (args.draftBcc)
893
- updates.bcc = args.draftBcc;
995
+ updates.bcc = normalizeUpdateEmailArray(args.draftBcc);
894
996
  if (args.draftSubject)
895
997
  updates.subject = args.draftSubject;
896
998
  if (args.draftBody)
@@ -1018,7 +1120,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1018
1120
  const graphClient = await enhancedMS365Auth.getGraphClient();
1019
1121
  ms365Ops.setGraphClient(graphClient);
1020
1122
  }
1021
- const emailResult = await ms365Ops.sendEmail(args);
1123
+ // Validate and normalize email input
1124
+ if (!args?.to) {
1125
+ throw new Error("'to' field is required for sending email");
1126
+ }
1127
+ if (!args?.subject) {
1128
+ throw new Error("'subject' field is required for sending email");
1129
+ }
1130
+ // Helper function to normalize email arrays
1131
+ const normalizeEmailArray = (field) => {
1132
+ if (!field)
1133
+ return [];
1134
+ if (typeof field === 'string')
1135
+ return [field];
1136
+ if (Array.isArray(field))
1137
+ return field;
1138
+ throw new Error(`Invalid email field format. Expected string or array of strings.`);
1139
+ };
1140
+ // Normalize the email message
1141
+ const emailMessage = {
1142
+ to: normalizeEmailArray(args.to),
1143
+ cc: normalizeEmailArray(args.cc),
1144
+ bcc: normalizeEmailArray(args.bcc),
1145
+ subject: args.subject,
1146
+ body: args.body || '',
1147
+ bodyType: args.bodyType || 'text',
1148
+ replyTo: args.replyTo,
1149
+ importance: args.importance || 'normal',
1150
+ attachments: args.attachments || []
1151
+ };
1152
+ const emailResult = await ms365Ops.sendEmail(emailMessage);
1022
1153
  return {
1023
1154
  content: [
1024
1155
  {