ms365-mcp-server 1.0.0 → 1.0.2

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
@@ -55,7 +55,7 @@ function parseArgs() {
55
55
  }
56
56
  const server = new Server({
57
57
  name: "ms365-mcp-server",
58
- version: "1.0.0"
58
+ version: "1.0.2"
59
59
  }, {
60
60
  capabilities: {
61
61
  resources: {
@@ -261,6 +261,64 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
261
261
  additionalProperties: false
262
262
  }
263
263
  },
264
+ {
265
+ name: "search_emails_to_me",
266
+ description: "Search for emails addressed to YOU in both TO and CC fields. This automatically finds all emails where you are a recipient (direct TO or CC'd), without needing to specify your email address.",
267
+ inputSchema: {
268
+ type: "object",
269
+ properties: {
270
+ userId: {
271
+ type: "string",
272
+ description: "User ID for multi-user authentication (required if using multi-user mode)"
273
+ },
274
+ query: {
275
+ type: "string",
276
+ description: "General search query using natural language or specific terms"
277
+ },
278
+ from: {
279
+ type: "string",
280
+ description: "Search for emails from specific sender"
281
+ },
282
+ subject: {
283
+ type: "string",
284
+ description: "Search for emails with specific subject"
285
+ },
286
+ after: {
287
+ type: "string",
288
+ description: "Search for emails after date (format: YYYY-MM-DD)"
289
+ },
290
+ before: {
291
+ type: "string",
292
+ description: "Search for emails before date (format: YYYY-MM-DD)"
293
+ },
294
+ hasAttachment: {
295
+ type: "boolean",
296
+ description: "Filter emails that have attachments"
297
+ },
298
+ folder: {
299
+ type: "string",
300
+ description: "Search within specific folder (default: inbox)"
301
+ },
302
+ isUnread: {
303
+ type: "boolean",
304
+ description: "Filter for unread emails only"
305
+ },
306
+ importance: {
307
+ type: "string",
308
+ enum: ["low", "normal", "high"],
309
+ description: "Filter by email importance level"
310
+ },
311
+ maxResults: {
312
+ type: "number",
313
+ description: "Maximum number of results to return (default: 50, max: 200)",
314
+ minimum: 1,
315
+ maximum: 200,
316
+ default: 50
317
+ }
318
+ },
319
+ additionalProperties: false
320
+ }
321
+ },
264
322
  {
265
323
  name: "list_emails",
266
324
  description: "List emails in inbox, sent, or custom folder. Returns basic email information including subjects, senders, and snippets with support for folder navigation.",
@@ -453,21 +511,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
453
511
  additionalProperties: false
454
512
  }
455
513
  },
456
- {
457
- name: "get_my_session_info",
458
- description: "Get information about your own authentication session (user-specific, secure).",
459
- inputSchema: {
460
- type: "object",
461
- properties: {
462
- userId: {
463
- type: "string",
464
- description: "Your User ID to get information for"
465
- }
466
- },
467
- required: ["userId"],
468
- additionalProperties: false
469
- }
470
- },
471
514
  {
472
515
  name: "remove_my_session",
473
516
  description: "Remove your own authentication session (user-specific, secure).",
@@ -487,47 +530,38 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
487
530
  // Add one-time authentication tool for single-user mode
488
531
  const oneTimeAuthTools = [
489
532
  {
490
- name: "quick_authenticate",
491
- description: "One-time Microsoft 365 authentication for immediate access. Opens browser automatically and completes authentication flow.",
533
+ name: "get_auth_link",
534
+ description: "Get a clickable Microsoft 365 authentication link without auto-opening browser. Perfect for manual authentication control.",
492
535
  inputSchema: {
493
536
  type: "object",
494
537
  properties: {
495
538
  force: {
496
539
  type: "boolean",
497
- description: "Force re-authentication even if already authenticated (default: false)",
540
+ description: "Force new authentication link even if already authenticated (default: false)",
498
541
  default: false
499
542
  }
500
543
  },
501
544
  additionalProperties: false
502
545
  }
503
- },
546
+ }
547
+ ];
548
+ // Enhanced authentication tools with device code flow
549
+ const enhancedAuthTools = [
504
550
  {
505
- name: "get_auth_link",
506
- description: "Get a clickable Microsoft 365 authentication link without auto-opening browser. Perfect for manual authentication control.",
551
+ name: "authenticate_with_device_code",
552
+ description: "Complete device code authentication in MCP. First call shows device code, second call (after browser auth) completes the process. RECOMMENDED method.",
507
553
  inputSchema: {
508
554
  type: "object",
509
555
  properties: {
510
556
  force: {
511
557
  type: "boolean",
512
- description: "Force new authentication link even if already authenticated (default: false)",
558
+ description: "Force new authentication even if already authenticated (default: false)",
513
559
  default: false
514
560
  }
515
561
  },
516
562
  additionalProperties: false
517
563
  }
518
564
  },
519
- {
520
- name: "check_quick_auth_status",
521
- description: "Check if quick authentication has completed successfully. Use this after using quick_authenticate to see if you're now authenticated.",
522
- inputSchema: {
523
- type: "object",
524
- properties: {},
525
- additionalProperties: false
526
- }
527
- }
528
- ];
529
- // Enhanced authentication tools with device code flow
530
- const enhancedAuthTools = [
531
565
  {
532
566
  name: "device_code_login",
533
567
  description: "Start device code authentication flow and get the URL/code to enter. Shows authentication details immediately in the UI.",
@@ -543,15 +577,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
543
577
  additionalProperties: false
544
578
  }
545
579
  },
546
- {
547
- name: "complete_device_code_auth",
548
- description: "Check if device code authentication has completed after visiting the URL and entering the code. This is a quick status check - authentication happens automatically in the background.",
549
- inputSchema: {
550
- type: "object",
551
- properties: {},
552
- additionalProperties: false
553
- }
554
- },
555
580
  {
556
581
  name: "check_pending_auth",
557
582
  description: "Check if there's a pending device code authentication and get the URL/code again if needed.",
@@ -579,24 +604,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
579
604
  additionalProperties: false
580
605
  }
581
606
  },
582
- {
583
- name: "list_authenticated_accounts",
584
- description: "List all authenticated Microsoft 365 accounts stored securely. Shows which accounts are available for use.",
585
- inputSchema: {
586
- type: "object",
587
- properties: {},
588
- additionalProperties: false
589
- }
590
- },
591
- {
592
- name: "get_storage_info",
593
- description: "Get information about how credentials are stored (OS Keychain vs encrypted file). Useful for security auditing.",
594
- inputSchema: {
595
- type: "object",
596
- properties: {},
597
- additionalProperties: false
598
- }
599
- },
600
607
  {
601
608
  name: "logout",
602
609
  description: "Log out and clear stored authentication tokens for the current user. This will require re-authentication.",
@@ -633,26 +640,54 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
633
640
  try {
634
641
  switch (name) {
635
642
  // ============ ENHANCED AUTHENTICATION TOOLS ============
636
- case "quick_authenticate":
637
- if (!await enhancedMS365Auth.isAuthenticated() || args?.force) {
638
- // Use the enhanced device code flow for better UI experience
639
- const deviceCodeInfo = await enhancedMS365Auth.startDeviceCodeAuth();
640
- // Return device code info immediately, user will need to complete manually
643
+ case "authenticate_with_device_code":
644
+ try {
645
+ // Check if already authenticated
646
+ if (await enhancedMS365Auth.isAuthenticated() && !args?.force) {
647
+ return {
648
+ content: [
649
+ {
650
+ type: "text",
651
+ text: `āœ… Already authenticated with Microsoft 365! Use force: true to re-authenticate.`
652
+ }
653
+ ]
654
+ };
655
+ }
656
+ // First, try to complete any existing pending authentication
657
+ const existingResult = await enhancedMS365Auth.completeDeviceCodeAuth();
658
+ if (existingResult) {
659
+ const currentUser = await enhancedMS365Auth.getCurrentUser();
660
+ return {
661
+ content: [
662
+ {
663
+ type: "text",
664
+ text: `āœ… Device code authentication completed successfully!\n\nšŸ‘¤ User: ${currentUser || 'authenticated-user'}\nšŸ” Status: Valid\n\nšŸš€ You can now use all Microsoft 365 email features!`
665
+ }
666
+ ]
667
+ };
668
+ }
669
+ // Check if there's already a pending device code
670
+ let deviceCodeInfo = await enhancedMS365Auth.getPendingDeviceCodeInfo();
671
+ if (!deviceCodeInfo || args?.force) {
672
+ // No pending code or force new one, start fresh
673
+ deviceCodeInfo = await enhancedMS365Auth.startDeviceCodeAuth();
674
+ }
675
+ // Return device code information immediately (MCP pattern)
641
676
  return {
642
677
  content: [
643
678
  {
644
679
  type: "text",
645
- text: `šŸ” Microsoft 365 Authentication Required!\n\nšŸ“± Please visit: ${deviceCodeInfo.verificationUri}\nšŸ”‘ Enter this code: ${deviceCodeInfo.userCode}\n\nā³ Authentication is in progress. The system will automatically complete once you enter the code.\n\nšŸ’” This code expires in 15 minutes.`
680
+ text: `šŸ” Microsoft 365 Device Code Authentication\n\nšŸ“± Visit: ${deviceCodeInfo.verificationUri}\nšŸ”‘ Enter this code: ${deviceCodeInfo.userCode}\n\nā³ After completing authentication in your browser, call this tool again to finish the process.\n\nšŸ’” This code will expire in 15 minutes.`
646
681
  }
647
682
  ]
648
683
  };
649
684
  }
650
- else {
685
+ catch (error) {
651
686
  return {
652
687
  content: [
653
688
  {
654
689
  type: "text",
655
- text: "āœ… Already authenticated with Microsoft 365. Use force: true to re-authenticate."
690
+ text: `āŒ Authentication failed: ${error.message}\n\nšŸ’” Try again or use CLI: node dist/index.js --login`
656
691
  }
657
692
  ]
658
693
  };
@@ -679,40 +714,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
679
714
  ]
680
715
  };
681
716
  }
682
- case "check_quick_auth_status":
683
- const isCurrentlyAuthenticated = await enhancedMS365Auth.isAuthenticated();
684
- const hasPending = enhancedMS365Auth.hasPendingAuth();
685
- if (isCurrentlyAuthenticated) {
686
- return {
687
- content: [
688
- {
689
- type: "text",
690
- text: "āœ… Quick authentication completed successfully! You can now use all Microsoft 365 email features."
691
- }
692
- ]
693
- };
694
- }
695
- else if (hasPending) {
696
- const pendingInfo = enhancedMS365Auth.getPendingDeviceCodeInfo();
697
- return {
698
- content: [
699
- {
700
- type: "text",
701
- text: `ā³ Authentication still pending.\n\nšŸ“± Please visit: ${pendingInfo?.verificationUri}\nšŸ”‘ Enter code: ${pendingInfo?.userCode}\n\nšŸ’” Check again after entering the code in your browser. Authentication completes automatically - no need to wait or use additional tools.`
702
- }
703
- ]
704
- };
705
- }
706
- else {
707
- return {
708
- content: [
709
- {
710
- type: "text",
711
- text: "āŒ No authentication found. Please use quick_authenticate to start the authentication process."
712
- }
713
- ]
714
- };
715
- }
716
717
  case "device_code_login":
717
718
  if (!await enhancedMS365Auth.isAuthenticated() || args?.force) {
718
719
  const deviceCodeInfo = await enhancedMS365Auth.startDeviceCodeAuth();
@@ -720,7 +721,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
720
721
  content: [
721
722
  {
722
723
  type: "text",
723
- text: `šŸ” Microsoft 365 Device Code Authentication Started!\n\nšŸ“± Visit: ${deviceCodeInfo.verificationUri}\nšŸ”‘ Enter this code: ${deviceCodeInfo.userCode}\n\nā³ After entering the code, use the "complete_device_code_auth" tool to finish authentication.\n\nšŸ’” This code will expire in 15 minutes.`
724
+ text: `šŸ” Microsoft 365 Device Code Authentication Started!\n\nšŸ“± Visit: ${deviceCodeInfo.verificationUri}\nšŸ”‘ Enter this code: ${deviceCodeInfo.userCode}\n\nā³ After entering the code, use the "authenticate_with_device_code" tool to complete authentication.\n\nšŸ’” This code will expire in 15 minutes.`
724
725
  }
725
726
  ]
726
727
  };
@@ -735,53 +736,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
735
736
  ]
736
737
  };
737
738
  }
738
- case "complete_device_code_auth":
739
- if (!enhancedMS365Auth.hasPendingAuth()) {
740
- return {
741
- content: [
742
- {
743
- type: "text",
744
- text: "āŒ No pending device code authentication found. Use 'device_code_login' first to start the authentication process."
745
- }
746
- ]
747
- };
748
- }
749
- // Check if authentication has already completed (non-blocking)
750
- const isNowAuthenticated = await enhancedMS365Auth.isAuthenticated();
751
- if (isNowAuthenticated) {
739
+ case "check_pending_auth":
740
+ const pendingDeviceCodeState = await enhancedMS365Auth.getPendingDeviceCodeInfo();
741
+ if (pendingDeviceCodeState) {
752
742
  return {
753
743
  content: [
754
744
  {
755
745
  type: "text",
756
- text: "āœ… Device code authentication completed successfully! You can now use all Microsoft 365 email features."
746
+ text: `ā³ Pending Device Code Authentication\n\nšŸ“± Visit: ${pendingDeviceCodeState.verificationUri}\nšŸ”‘ Enter this code: ${pendingDeviceCodeState.userCode}\n\nšŸ’” Use 'authenticate_with_device_code' to finish authentication after entering the code.`
757
747
  }
758
748
  ]
759
749
  };
760
750
  }
761
- // If still pending, return status with instructions
762
- const pendingInfo = enhancedMS365Auth.getPendingDeviceCodeInfo();
763
- return {
764
- content: [
765
- {
766
- type: "text",
767
- text: `ā³ Device code authentication still in progress.\n\nšŸ“± Please visit: ${pendingInfo?.verificationUri}\nšŸ”‘ Enter code: ${pendingInfo?.userCode}\n\nšŸ’” Try this tool again after completing the authentication in your browser.\n\nNote: Authentication completes automatically once you enter the code - no need to wait.`
768
- }
769
- ]
770
- };
771
- case "check_pending_auth":
772
- if (enhancedMS365Auth.hasPendingAuth()) {
773
- const pendingInfo = enhancedMS365Auth.getPendingDeviceCodeInfo();
774
- if (pendingInfo) {
775
- return {
776
- content: [
777
- {
778
- type: "text",
779
- text: `ā³ Pending Device Code Authentication\n\nšŸ“± Visit: ${pendingInfo.verificationUri}\nšŸ”‘ Enter this code: ${pendingInfo.userCode}\n\nšŸ’” Use 'complete_device_code_auth' to finish authentication after entering the code.`
780
- }
781
- ]
782
- };
783
- }
784
- }
785
751
  return {
786
752
  content: [
787
753
  {
@@ -802,64 +768,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
802
768
  };
803
769
  case "verify_authentication":
804
770
  const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
805
- const accounts = await enhancedMS365Auth.listAuthenticatedAccounts();
771
+ const currentUser = await enhancedMS365Auth.getCurrentUser();
806
772
  const storageInfo = enhancedMS365Auth.getStorageInfo();
807
773
  return {
808
774
  content: [
809
775
  {
810
776
  type: "text",
811
- text: `šŸ“Š Microsoft 365 Authentication Status\n\nšŸ” Authentication: ${isAuthenticated ? 'āœ… Valid' : 'āŒ Not authenticated'}\nšŸ’¾ Storage method: ${storageInfo.method}\nšŸ“ Storage location: ${storageInfo.location}\nšŸ‘„ Authenticated accounts: ${accounts.length > 0 ? accounts.join(', ') : 'None'}`
777
+ text: `šŸ“Š Microsoft 365 Authentication Status\n\nšŸ” Authentication: ${isAuthenticated ? 'āœ… Valid' : 'āŒ Not authenticated'}\nšŸ‘¤ Current User: ${currentUser || 'None'}\nšŸ’¾ Storage method: ${storageInfo.method}\nšŸ“ Storage location: ${storageInfo.location}`
812
778
  }
813
779
  ]
814
780
  };
815
- case "list_authenticated_accounts":
816
- const authenticatedAccounts = await enhancedMS365Auth.listAuthenticatedAccounts();
817
- return {
818
- content: [
819
- {
820
- type: "text",
821
- text: `šŸ‘„ Authenticated Microsoft 365 Accounts:\n\n${authenticatedAccounts.length > 0 ? authenticatedAccounts.map(account => `• ${account}`).join('\n') : 'No authenticated accounts found'}`
822
- }
823
- ]
824
- };
825
- case "get_storage_info":
826
- const storage = enhancedMS365Auth.getStorageInfo();
781
+ case "logout":
782
+ const accountKey = args?.accountKey;
783
+ const wasAuthenticated = await enhancedMS365Auth.isAuthenticated();
784
+ await enhancedMS365Auth.resetAuth();
827
785
  return {
828
786
  content: [
829
787
  {
830
788
  type: "text",
831
- text: `šŸ”’ Credential Storage Information\n\nšŸ’¾ Method: ${storage.method}\nšŸ“ Location: ${storage.location}\nšŸ” Keychain Available: ${storage.method === 'OS Keychain' ? 'Yes' : 'No'}`
789
+ text: wasAuthenticated ?
790
+ `āœ… Successfully logged out from Microsoft 365.` :
791
+ `ā„¹ļø No active authentication found.`
832
792
  }
833
793
  ]
834
794
  };
835
- case "logout":
836
- const accountKey = args?.accountKey;
837
- // Get list of authenticated accounts before logout for better feedback
838
- const authenticatedAccountsBefore = await enhancedMS365Auth.listAuthenticatedAccounts();
839
- await enhancedMS365Auth.resetAuth(accountKey);
840
- // Check what was actually logged out
841
- const authenticatedAccountsAfter = await enhancedMS365Auth.listAuthenticatedAccounts();
842
- const loggedOutAccounts = authenticatedAccountsBefore.filter(account => !authenticatedAccountsAfter.includes(account));
843
- if (accountKey) {
844
- return {
845
- content: [
846
- {
847
- type: "text",
848
- text: `āœ… Successfully logged out from Microsoft 365. Account: ${accountKey}`
849
- }
850
- ]
851
- };
852
- }
853
- else {
854
- return {
855
- content: [
856
- {
857
- type: "text",
858
- text: `āœ… Successfully logged out from Microsoft 365.\n${loggedOutAccounts.length > 0 ? `Logged out accounts: ${loggedOutAccounts.join(', ')}` : 'No accounts found to logout'}`
859
- }
860
- ]
861
- };
862
- }
863
795
  // ============ MULTI-USER AUTHENTICATION TOOLS ============
864
796
  case "authenticate_user":
865
797
  if (!ms365Config.multiUser) {
@@ -874,25 +806,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
874
806
  }
875
807
  ]
876
808
  };
877
- case "get_my_session_info":
878
- if (!ms365Config.multiUser) {
879
- throw new Error("Multi-user mode not enabled. Start server with --multi-user flag.");
880
- }
881
- if (!args?.userId) {
882
- throw new Error("User ID is required");
883
- }
884
- const session = multiUserMS365Auth.getUserSession(args.userId);
885
- if (!session) {
886
- throw new Error(`User session not found: ${args.userId}`);
887
- }
888
- return {
889
- content: [
890
- {
891
- type: "text",
892
- text: `šŸ‘¤ Session Information\n\nšŸ†” User ID: ${session.userId}\nšŸ“§ Email: ${session.userEmail || 'Unknown'}\nšŸ” Authenticated: ${session.authenticated ? 'Yes' : 'No'}\nā° Expires: ${new Date(session.expiresOn).toLocaleString()}`
893
- }
894
- ]
895
- };
896
809
  case "remove_my_session":
897
810
  if (!ms365Config.multiUser) {
898
811
  throw new Error("Multi-user mode not enabled. Start server with --multi-user flag.");
@@ -976,6 +889,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
976
889
  }
977
890
  ]
978
891
  };
892
+ case "search_emails_to_me":
893
+ if (ms365Config.multiUser) {
894
+ const userId = args?.userId;
895
+ if (!userId) {
896
+ throw new Error("User ID is required in multi-user mode");
897
+ }
898
+ const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
899
+ ms365Ops.setGraphClient(graphClient);
900
+ }
901
+ else {
902
+ const graphClient = await enhancedMS365Auth.getGraphClient();
903
+ ms365Ops.setGraphClient(graphClient);
904
+ }
905
+ const searchToMeResults = await ms365Ops.searchEmailsToMe(args);
906
+ return {
907
+ content: [
908
+ {
909
+ type: "text",
910
+ text: `šŸ” Emails Addressed to You (TO & CC) - ${searchToMeResults.messages.length} found\n\n${searchToMeResults.messages.map((email, index) => `${index + 1}. šŸ“§ ${email.subject}\n šŸ‘¤ From: ${email.from.name} <${email.from.address}>\n šŸ“… ${new Date(email.receivedDateTime).toLocaleDateString()}\n šŸ†” ID: ${email.id}\n`).join('\n')}`
911
+ }
912
+ ]
913
+ };
979
914
  case "list_emails":
980
915
  if (ms365Config.multiUser) {
981
916
  const userId = args?.userId;
@@ -1218,13 +1153,13 @@ async function main() {
1218
1153
  if (ms365Config.verifyLogin) {
1219
1154
  try {
1220
1155
  const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
1221
- const accounts = await enhancedMS365Auth.listAuthenticatedAccounts();
1156
+ const currentUser = await enhancedMS365Auth.getCurrentUser();
1222
1157
  const storageInfo = enhancedMS365Auth.getStorageInfo();
1223
1158
  console.log('\nšŸ“Š MS365 Authentication Status\n');
1224
1159
  console.log(`Authentication: ${isAuthenticated ? 'āœ… Valid' : 'āŒ Not authenticated'}`);
1160
+ console.log(`Current User: ${currentUser || 'None'}`);
1225
1161
  console.log(`Storage method: ${storageInfo.method}`);
1226
- console.log(`Storage location: ${storageInfo.location}`);
1227
- console.log(`Authenticated accounts: ${accounts.length > 0 ? accounts.join(', ') : 'None'}\n`);
1162
+ console.log(`Storage location: ${storageInfo.location}\n`);
1228
1163
  process.exit(isAuthenticated ? 0 : 1);
1229
1164
  }
1230
1165
  catch (error) {