ms365-mcp-server 1.1.12 → 1.1.14

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
@@ -67,7 +67,7 @@ function parseArgs() {
67
67
  }
68
68
  const server = new Server({
69
69
  name: "ms365-mcp-server",
70
- version: "1.1.12"
70
+ version: "1.1.14"
71
71
  }, {
72
72
  capabilities: {
73
73
  resources: {
@@ -83,7 +83,7 @@ const server = new Server({
83
83
  }
84
84
  }
85
85
  });
86
- logger.log('Server started with version 1.1.12');
86
+ logger.log('Server started with version 1.1.14');
87
87
  // Set up the resource listing request handler
88
88
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
89
89
  logger.log('Received list resources request');
@@ -119,19 +119,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
119
119
  description: "User ID for multi-user authentication (required if using multi-user mode)"
120
120
  },
121
121
  to: {
122
- type: "array",
123
- items: { type: "string" },
124
- description: "List of recipient email addresses"
122
+ oneOf: [
123
+ { type: "string" },
124
+ { type: "array", items: { type: "string" } }
125
+ ],
126
+ description: "Recipient email address (string) or list of recipient email addresses (array)"
125
127
  },
126
128
  cc: {
127
- type: "array",
128
- items: { type: "string" },
129
- description: "List of CC recipient email addresses (optional)"
129
+ oneOf: [
130
+ { type: "string" },
131
+ { type: "array", items: { type: "string" } }
132
+ ],
133
+ description: "CC recipient email address (string) or list of CC recipient email addresses (array) - optional"
130
134
  },
131
135
  bcc: {
132
- type: "array",
133
- items: { type: "string" },
134
- description: "List of BCC recipient email addresses (optional)"
136
+ oneOf: [
137
+ { type: "string" },
138
+ { type: "array", items: { type: "string" } }
139
+ ],
140
+ description: "BCC recipient email address (string) or list of BCC recipient email addresses (array) - optional"
135
141
  },
136
142
  subject: {
137
143
  type: "string",
@@ -180,8 +186,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
180
186
  description: "List of email attachments (optional)"
181
187
  }
182
188
  },
183
- required: ["to", "subject"],
184
- additionalProperties: false
189
+ required: ["to", "subject"]
185
190
  }
186
191
  },
187
192
  {
@@ -275,19 +280,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
275
280
  },
276
281
  // Draft email parameters
277
282
  draftTo: {
278
- type: "array",
279
- items: { type: "string" },
280
- description: "List of recipient email addresses (required for draft action)"
283
+ oneOf: [
284
+ { type: "string" },
285
+ { type: "array", items: { type: "string" } }
286
+ ],
287
+ description: "Recipient email address (string) or list of recipient email addresses (array) - required for draft action"
281
288
  },
282
289
  draftCc: {
283
- type: "array",
284
- items: { type: "string" },
285
- description: "List of CC recipient email addresses (optional for draft action)"
290
+ oneOf: [
291
+ { type: "string" },
292
+ { type: "array", items: { type: "string" } }
293
+ ],
294
+ description: "CC recipient email address (string) or list of CC recipient email addresses (array) - optional for draft action"
286
295
  },
287
296
  draftBcc: {
288
- type: "array",
289
- items: { type: "string" },
290
- description: "List of BCC recipient email addresses (optional for draft action)"
297
+ oneOf: [
298
+ { type: "string" },
299
+ { type: "array", items: { type: "string" } }
300
+ ],
301
+ description: "BCC recipient email address (string) or list of BCC recipient email addresses (array) - optional for draft action"
291
302
  },
292
303
  draftSubject: {
293
304
  type: "string",
@@ -350,8 +361,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
350
361
  description: "Conversation ID for threading drafts (optional)"
351
362
  }
352
363
  },
353
- required: ["action"],
354
- additionalProperties: false
364
+ required: ["action"]
355
365
  }
356
366
  },
357
367
  {
@@ -373,8 +383,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
373
383
  description: "Attachment ID to download"
374
384
  }
375
385
  },
376
- required: ["messageId", "attachmentId"],
377
- additionalProperties: false
386
+ required: ["messageId", "attachmentId"]
378
387
  }
379
388
  },
380
389
  {
@@ -395,8 +404,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
395
404
  type: "string",
396
405
  description: "Optional: Search for folders by name (case-insensitive partial match)"
397
406
  }
398
- },
399
- additionalProperties: false
407
+ }
400
408
  }
401
409
  },
402
410
  {
@@ -425,8 +433,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
425
433
  minimum: 1,
426
434
  maximum: 500
427
435
  }
428
- },
429
- additionalProperties: false
436
+ }
430
437
  }
431
438
  }
432
439
  ];
@@ -442,8 +449,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
442
449
  type: "string",
443
450
  description: "User's email address (optional, for identification)"
444
451
  }
445
- },
446
- additionalProperties: false
452
+ }
447
453
  }
448
454
  },
449
455
  {
@@ -457,8 +463,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
457
463
  description: "Your User ID to remove"
458
464
  }
459
465
  },
460
- required: ["userId"],
461
- additionalProperties: false
466
+ required: ["userId"]
462
467
  }
463
468
  }
464
469
  ];
@@ -486,8 +491,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
486
491
  description: "Account key for logout action (default: current user)",
487
492
  default: "default-user"
488
493
  }
489
- },
490
- additionalProperties: false
494
+ }
491
495
  }
492
496
  }
493
497
  ];
@@ -851,15 +855,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
851
855
  if (!args?.draftTo || !args?.draftSubject || !args?.draftBody) {
852
856
  throw new Error("draftTo, draftSubject, and draftBody are required for draft action");
853
857
  }
858
+ // Helper function to normalize email arrays for drafts
859
+ const normalizeDraftEmailArray = (field) => {
860
+ if (!field)
861
+ return [];
862
+ if (typeof field === 'string')
863
+ return [field];
864
+ if (Array.isArray(field))
865
+ return field;
866
+ throw new Error(`Invalid email field format. Expected string or array of strings.`);
867
+ };
854
868
  // Check if this looks like a reply based on subject
855
869
  const isLikelyReply = args.draftSubject?.toString().toLowerCase().startsWith('re:');
856
870
  if (isLikelyReply) {
857
871
  logger.log('🔔 SUGGESTION: Consider using "reply_draft" action instead of "draft" for threaded replies that appear in conversations');
858
872
  }
859
873
  const draftResult = await ms365Ops.saveDraftEmail({
860
- to: args.draftTo,
861
- cc: args.draftCc,
862
- bcc: args.draftBcc,
874
+ to: normalizeDraftEmailArray(args.draftTo),
875
+ cc: normalizeDraftEmailArray(args.draftCc),
876
+ bcc: normalizeDraftEmailArray(args.draftBcc),
863
877
  subject: args.draftSubject,
864
878
  body: args.draftBody,
865
879
  bodyType: args.draftBodyType || 'text',
@@ -884,13 +898,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
884
898
  if (!args?.draftId) {
885
899
  throw new Error("draftId is required for update_draft action");
886
900
  }
901
+ // Helper function to normalize email arrays for draft updates
902
+ const normalizeUpdateEmailArray = (field) => {
903
+ if (!field)
904
+ return [];
905
+ if (typeof field === 'string')
906
+ return [field];
907
+ if (Array.isArray(field))
908
+ return field;
909
+ throw new Error(`Invalid email field format. Expected string or array of strings.`);
910
+ };
887
911
  const updates = {};
888
912
  if (args.draftTo)
889
- updates.to = args.draftTo;
913
+ updates.to = normalizeUpdateEmailArray(args.draftTo);
890
914
  if (args.draftCc)
891
- updates.cc = args.draftCc;
915
+ updates.cc = normalizeUpdateEmailArray(args.draftCc);
892
916
  if (args.draftBcc)
893
- updates.bcc = args.draftBcc;
917
+ updates.bcc = normalizeUpdateEmailArray(args.draftBcc);
894
918
  if (args.draftSubject)
895
919
  updates.subject = args.draftSubject;
896
920
  if (args.draftBody)
@@ -937,7 +961,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
937
961
  if (!args?.originalMessageId) {
938
962
  throw new Error("originalMessageId is required for reply_draft action");
939
963
  }
940
- const replyDraftResult = await ms365Ops.createReplyDraft(args.originalMessageId, args.draftBody, args.replyToAll || false);
964
+ const replyDraftResult = await ms365Ops.createReplyDraft(args.originalMessageId, args.draftBody, args.replyToAll || false, args.draftBodyType || 'text');
941
965
  return {
942
966
  content: [
943
967
  {
@@ -950,7 +974,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
950
974
  if (!args?.originalMessageId) {
951
975
  throw new Error("originalMessageId is required for forward_draft action");
952
976
  }
953
- const forwardDraftResult = await ms365Ops.createForwardDraft(args.originalMessageId, args.draftBody);
977
+ const forwardDraftResult = await ms365Ops.createForwardDraft(args.originalMessageId, args.draftBody, args.draftBodyType || 'text');
954
978
  return {
955
979
  content: [
956
980
  {
@@ -1018,7 +1042,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1018
1042
  const graphClient = await enhancedMS365Auth.getGraphClient();
1019
1043
  ms365Ops.setGraphClient(graphClient);
1020
1044
  }
1021
- const emailResult = await ms365Ops.sendEmail(args);
1045
+ // Validate and normalize email input
1046
+ if (!args?.to) {
1047
+ throw new Error("'to' field is required for sending email");
1048
+ }
1049
+ if (!args?.subject) {
1050
+ throw new Error("'subject' field is required for sending email");
1051
+ }
1052
+ // Helper function to normalize email arrays
1053
+ const normalizeEmailArray = (field) => {
1054
+ if (!field)
1055
+ return [];
1056
+ if (typeof field === 'string')
1057
+ return [field];
1058
+ if (Array.isArray(field))
1059
+ return field;
1060
+ throw new Error(`Invalid email field format. Expected string or array of strings.`);
1061
+ };
1062
+ // Normalize the email message
1063
+ const emailMessage = {
1064
+ to: normalizeEmailArray(args.to),
1065
+ cc: normalizeEmailArray(args.cc),
1066
+ bcc: normalizeEmailArray(args.bcc),
1067
+ subject: args.subject,
1068
+ body: args.body || '',
1069
+ bodyType: args.bodyType || 'text',
1070
+ replyTo: args.replyTo,
1071
+ importance: args.importance || 'normal',
1072
+ attachments: args.attachments || []
1073
+ };
1074
+ const emailResult = await ms365Ops.sendEmail(emailMessage);
1022
1075
  return {
1023
1076
  content: [
1024
1077
  {
@@ -522,7 +522,7 @@ export class MS365Operations {
522
522
  /**
523
523
  * Create a threaded reply draft from a specific message
524
524
  */
525
- async createReplyDraft(originalMessageId, body, replyToAll = false) {
525
+ async createReplyDraft(originalMessageId, body, replyToAll = false, bodyType = 'text') {
526
526
  try {
527
527
  const graphClient = await this.getGraphClient();
528
528
  logger.log(`Creating reply draft for message: ${originalMessageId}`);
@@ -536,7 +536,25 @@ export class MS365Operations {
536
536
  const originalBodyContent = originalMessage.body?.content || '';
537
537
  const fromDisplay = originalMessage.from?.emailAddress?.name || originalMessage.from?.emailAddress?.address || '';
538
538
  const sentDate = new Date(originalMessage.sentDateTime).toLocaleString();
539
- const completeReplyBody = `${body || ''}
539
+ // Helper function to escape HTML characters
540
+ const escapeHtml = (text) => {
541
+ return text
542
+ .replace(/&/g, '&')
543
+ .replace(/</g, '&lt;')
544
+ .replace(/>/g, '&gt;')
545
+ .replace(/"/g, '&quot;')
546
+ .replace(/'/g, '&#x27;');
547
+ };
548
+ // Helper function to convert text to HTML
549
+ const textToHtml = (text) => {
550
+ return escapeHtml(text).replace(/\n/g, '<br>');
551
+ };
552
+ // Process the user's body based on the specified type
553
+ let processedUserBody = body || '';
554
+ if (bodyType === 'text' && processedUserBody) {
555
+ processedUserBody = textToHtml(processedUserBody);
556
+ }
557
+ const completeReplyBody = `${processedUserBody}
540
558
 
541
559
  <br><br>
542
560
  <div style="border-left: 2px solid #ccc; padding-left: 10px; margin-top: 10px;">
@@ -670,20 +688,38 @@ ${originalBodyContent}
670
688
  /**
671
689
  * Create a threaded forward draft from a specific message
672
690
  */
673
- async createForwardDraft(originalMessageId, comment) {
691
+ async createForwardDraft(originalMessageId, comment, bodyType = 'text') {
674
692
  try {
675
693
  const graphClient = await this.getGraphClient();
676
694
  logger.log(`Creating forward draft for message: ${originalMessageId}`);
677
695
  // First, try using the official Microsoft Graph createForward endpoint for proper threading
678
696
  try {
679
697
  logger.log(`Using official Graph API endpoint: /me/messages/${originalMessageId}/createForward`);
698
+ // Helper function to escape HTML characters
699
+ const escapeHtml = (text) => {
700
+ return text
701
+ .replace(/&/g, '&amp;')
702
+ .replace(/</g, '&lt;')
703
+ .replace(/>/g, '&gt;')
704
+ .replace(/"/g, '&quot;')
705
+ .replace(/'/g, '&#x27;');
706
+ };
707
+ // Helper function to convert text to HTML
708
+ const textToHtml = (text) => {
709
+ return escapeHtml(text).replace(/\n/g, '<br>');
710
+ };
711
+ // Process the comment based on the specified type
712
+ let processedComment = comment || '';
713
+ if (bodyType === 'text' && processedComment) {
714
+ processedComment = textToHtml(processedComment);
715
+ }
680
716
  const forwardDraft = await graphClient
681
717
  .api(`/me/messages/${originalMessageId}/createForward`)
682
718
  .post({
683
719
  message: {
684
720
  body: {
685
721
  contentType: 'html',
686
- content: comment || ''
722
+ content: processedComment
687
723
  }
688
724
  }
689
725
  });
@@ -717,11 +753,29 @@ ${originalBodyContent}
717
753
  referencesHeader = `${existingReferences.value} ${referencesHeader}`;
718
754
  }
719
755
  }
720
- const forwardedBody = `${comment ? comment + '\n\n' : ''}---------- Forwarded message ----------\nFrom: ${originalMessage.from?.emailAddress?.name || originalMessage.from?.emailAddress?.address}\nDate: ${originalMessage.sentDateTime}\nSubject: ${originalMessage.subject}\nTo: ${originalMessage.toRecipients?.map((r) => r.emailAddress.address).join(', ')}\n\n${originalMessage.body?.content || ''}`;
756
+ // Helper function to escape HTML characters for fallback
757
+ const escapeHtml = (text) => {
758
+ return text
759
+ .replace(/&/g, '&amp;')
760
+ .replace(/</g, '&lt;')
761
+ .replace(/>/g, '&gt;')
762
+ .replace(/"/g, '&quot;')
763
+ .replace(/'/g, '&#x27;');
764
+ };
765
+ // Helper function to convert text to HTML for fallback
766
+ const textToHtml = (text) => {
767
+ return escapeHtml(text).replace(/\n/g, '<br>');
768
+ };
769
+ // Process the comment based on the specified type for fallback
770
+ let processedComment = comment || '';
771
+ if (bodyType === 'text' && processedComment) {
772
+ processedComment = textToHtml(processedComment);
773
+ }
774
+ const forwardedBody = `${processedComment ? processedComment + '<br><br>' : ''}---------- Forwarded message ----------<br>From: ${originalMessage.from?.emailAddress?.name || originalMessage.from?.emailAddress?.address}<br>Date: ${originalMessage.sentDateTime}<br>Subject: ${originalMessage.subject}<br>To: ${originalMessage.toRecipients?.map((r) => r.emailAddress.address).join(', ')}<br><br>${originalMessage.body?.content || ''}`;
721
775
  const draftBody = {
722
776
  subject: originalMessage.subject?.startsWith('Fwd:') ? originalMessage.subject : `Fwd: ${originalMessage.subject}`,
723
777
  body: {
724
- contentType: originalMessage.body?.contentType || 'html',
778
+ contentType: 'html',
725
779
  content: forwardedBody
726
780
  },
727
781
  conversationId: originalMessage.conversationId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ms365-mcp-server",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
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",