ms365-mcp-server 1.0.0 → 1.0.1

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
@@ -1,6 +1,6 @@
1
1
  # ✉️ Microsoft 365 MCP Server
2
2
 
3
- A powerful **Model Context Protocol (MCP) server** that enables seamless Microsoft 365 email integration through natural language interactions. Built for **Claude Desktop**, **LLMs**, and any MCP-compatible application.
3
+ A powerful **Model Context Protocol (MCP) server** that enables seamless Microsoft 365 email integration through natural language interactions. Built for **Claude Desktop**, **LLMs**, and any MCP-compatible application with **enhanced security** and **simplified authentication**.
4
4
 
5
5
  This project provides an **MCP (Model Context Protocol) server** that enables **Claude Desktop** to access your **Microsoft 365 email** seamlessly. It acts as a bridge between AI models and email services by exposing mailbox capabilities through a **secure, standardized interface**.
6
6
 
@@ -11,9 +11,9 @@ This project provides an **MCP (Model Context Protocol) server** that enables **
11
11
  - **Smart email search** with advanced filtering and criteria support
12
12
  - **Email organization** with read/unread marking and folder management
13
13
  - **File attachment support** - send and receive attachments seamlessly
14
- - **Multi-user support** for teams and shared environments
14
+ - **Enhanced security** with simplified single-user authentication model
15
15
  - **Contact management** - retrieve and search your contact directory
16
- - **Secure authentication** with local token storage and automatic refresh
16
+ - **Secure authentication** with OS keychain storage and automatic refresh
17
17
  - **Cross-platform compatibility** (macOS, Linux, Windows)
18
18
 
19
19
  ## 🛠️ Tools & Capabilities
@@ -31,19 +31,16 @@ This project provides an **MCP (Model Context Protocol) server** that enables **
31
31
  - **`get_contacts`** - Retrieve contact list from Microsoft 365
32
32
  - **`search_contacts`** - Search contacts by name or email address
33
33
 
34
- ### 🔐 Authentication Tools (10 tools)
35
- - **`quick_authenticate`** - One-time authentication with auto browser launch (single-user mode)
36
- - **`get_auth_link`** - Get clickable authentication link for manual control (single-user mode)
37
- - **`device_code_login`** - Device code authentication flow (no browser auto-open)
34
+ ### 🔐 Secure Authentication Tools (7 tools)
35
+ - **`authenticate_with_device_code`** - Complete authentication in one step (RECOMMENDED)
36
+ - **`device_code_login`** - Device code authentication flow (manual code entry)
37
+ - **`check_pending_auth`** - Check for pending device code authentication
38
38
  - **`get_device_code`** - Get device code info for manual authentication
39
- - **`verify_authentication`** - Check current authentication status
40
- - **`list_authenticated_accounts`** - Show all authenticated accounts
41
- - **`get_storage_info`** - Display credential storage method information
42
- - **`logout`** - Clear authentication tokens for current user
43
- - **`authenticate_user`** - Start authentication flow for new users (multi-user mode)
44
- - **`get_my_session_info`** - View your own session information (multi-user mode)
39
+ - **`get_auth_link`** - Get authentication link without auto browser launch
40
+ - **`verify_authentication`** - Check current authentication status (secure)
41
+ - **`logout`** - Clear authentication tokens and reset session
45
42
 
46
- **Total: 21 tools** (11 email operations + 10 authentication tools)
43
+ **Total: 18 tools** (11 email operations + 7 secure authentication tools)
47
44
 
48
45
  ## 🌟 Key Feature Highlights
49
46
 
@@ -76,6 +73,23 @@ This project provides an **MCP (Model Context Protocol) server** that enables **
76
73
  * Perform smart contact searches
77
74
  * Integration with Microsoft 365 address book
78
75
 
76
+ ### 🛡️ Enhanced Security Features
77
+
78
+ * **Simplified Authentication Model**
79
+ * Single-user authentication for better security
80
+ * No account enumeration or information disclosure
81
+ * Secure credential storage with OS keychain integration
82
+
83
+ * **Privacy Protection**
84
+ * No sensitive system information exposed
85
+ * User can only see their own authentication status
86
+ * Secure token management with automatic refresh
87
+
88
+ * **Device Code Authentication**
89
+ * No browser auto-open for better security control
90
+ * Manual code entry prevents unauthorized access
91
+ * Built-in Microsoft app requires no Azure registration
92
+
79
93
  ### 🌟 Advanced Features
80
94
 
81
95
  * **Advanced Search Filters**
@@ -130,69 +144,69 @@ Add to your `claude_desktop_config.json`:
130
144
  ```
131
145
 
132
146
  ### First Time Authentication
133
- **Option 1: Device Code Flow (Recommended)**
147
+ **Device Code Flow (Recommended & Secure)**
134
148
  ```bash
135
149
  ms365-mcp-server --login
136
150
  # Follow the on-screen instructions - no Azure app registration needed!
137
151
  ```
138
152
 
139
- **Option 2: Interactive Setup**
153
+ **Interactive Setup (Advanced)**
140
154
  ```bash
141
155
  ms365-mcp-server --setup-auth
142
156
  # Choose device code flow (recommended) or custom Azure app
143
157
  ```
144
158
 
145
- ## 🔐 **Enhanced Authentication System**
159
+ ## 🔐 **Enhanced Security & Authentication**
146
160
 
147
- Our MS365 MCP Server features a **dual-layer authentication system** inspired by [Softeria's implementation](https://github.com/Softeria/ms-365-mcp-server) with significant enhancements:
161
+ Our MS365 MCP Server features a **security-first authentication system** with simplified single-user model:
148
162
 
149
- ### **🎯 Authentication Methods**
163
+ ### **🔒 Security Improvements**
164
+
165
+ - **🚫 No Account Enumeration**: Removed tools that could expose user lists
166
+ - **🚫 No System Information Disclosure**: Hidden sensitive storage details
167
+ - **🚫 No Multi-User Complexity**: Simplified to single-user for better security
168
+ - **✅ Secure Credential Storage**: OS Keychain with encrypted file fallback
169
+ - **✅ Privacy Protection**: Users can only see their own information
170
+ - **✅ Token Security**: Automatic refresh with secure MSAL library
171
+
172
+ ### **🎯 Secure Authentication Flow**
150
173
 
151
174
  1. **🔥 Device Code Flow (Recommended)**
152
175
  - **No Azure app registration required**
153
176
  - **Built-in Microsoft app** for instant setup
154
- - **Secure device code authentication**
155
- - Perfect for personal use and secure environments
177
+ - **Manual code entry** prevents unauthorized access
178
+ - **No local server** required (more secure)
156
179
 
157
180
  2. **⚙️ Custom Azure App (Advanced)**
158
181
  - **Full control** over permissions and settings
159
182
  - **Redirect-based OAuth2** authentication
160
183
  - **Custom tenant configuration**
161
- - Ideal for enterprise environments
162
-
163
- ### **🔒 Secure Credential Storage**
184
+ - Requires Azure app registration
164
185
 
165
- - **OS Keychain Integration** (macOS Keychain, Windows Credential Manager, Linux Secret Service)
166
- - **Encrypted File Fallback** for cross-platform compatibility
167
- - **Automatic token refresh** with MSAL library
168
- - **Multi-account support** with isolated storage
169
-
170
- ### **⚡ New Authentication Commands**
186
+ ### **⚡ Secure Authentication Commands**
171
187
 
172
188
  ```bash
173
- # Device code authentication (easiest)
189
+ # Device code authentication (most secure)
174
190
  ms365-mcp-server --login
175
191
 
176
- # Verify authentication status
192
+ # Verify your authentication status
177
193
  ms365-mcp-server --verify-login
178
194
 
179
- # Logout and clear tokens
195
+ # Secure logout and clear tokens
180
196
  ms365-mcp-server --logout
181
197
 
182
198
  # Interactive credential setup
183
199
  ms365-mcp-server --setup-auth
184
200
  ```
185
201
 
186
- ### **🔄 Authentication Flow Comparison**
202
+ ### **🔄 Authentication Flow**
187
203
 
188
204
  | Feature | Device Code Flow *(Recommended)* | Redirect Flow *(Advanced)* |
189
205
  |---------|----------------------------------|----------------------------|
190
206
  | **Setup Required** | ❌ None (uses built-in app) | ✅ Azure app registration |
191
- | **Callback Endpoint** | ❌ Not needed | ✅ `http://localhost:44001/oauth2callback` |
192
- | **Port Requirements** | ❌ No ports needed | ✅ Port 44001 must be free |
193
- | **Browser Auto-open** | ❌ Manual code entry | ✅ Automatic browser redirect |
194
207
  | **Security** | 🔒 High (no local server) | 🔒 High (local callback) |
195
- | **Enterprise Support** | Excellent | Full control |
208
+ | **Privacy** | 🔒 Manual code entry | 🔒 Browser auto-open |
209
+ | **Ease of Use** | ✅ Simple setup | ⚙️ Advanced configuration |
196
210
 
197
211
  **Most users should use Device Code Flow** - it's simpler, more secure, and requires no setup!
198
212
 
@@ -222,11 +236,20 @@ node dist/index.js
222
236
 
223
237
  ## 🔑 Authentication Setup
224
238
 
225
- ### Step 1: Azure App Registration *(Optional - Only for Advanced Users)*
239
+ ### Step 1: Recommended Setup (No Azure Registration)
240
+
241
+ **Most users should use this method:**
242
+
243
+ ```bash
244
+ # One-command authentication
245
+ ms365-mcp-server --login
246
+ ```
226
247
 
227
- > **💡 Note**: Most users can skip this step! The recommended device code flow uses a built-in Microsoft app and doesn't require Azure app registration.
248
+ This uses a built-in Microsoft app with device code flow - **no Azure registration needed!**
228
249
 
229
- **Only needed if you want a custom Azure app with redirect-based authentication:**
250
+ ### Step 2: Advanced Setup (Custom Azure App)
251
+
252
+ > **💡 Note**: Only needed for advanced users who want custom Azure app configuration.
230
253
 
231
254
  1. Go to [Azure Portal](https://portal.azure.com)
232
255
  2. Navigate to **Azure Active Directory** > **App registrations**
@@ -234,7 +257,7 @@ node dist/index.js
234
257
  4. Configure your application:
235
258
  - **Name**: MS365 MCP Server (or your preferred name)
236
259
  - **Supported account types**: Choose appropriate option
237
- - **Redirect URI**: `http://localhost:44001/oauth2callback` *(only needed for redirect flow)*
260
+ - **Redirect URI**: `http://localhost:44001/oauth2callback` *(only for redirect flow)*
238
261
  5. **Grant API permissions** for Microsoft Graph:
239
262
  - `Mail.ReadWrite` - Read and write access to user mail
240
263
  - `Mail.Send` - Send mail as user
@@ -247,9 +270,9 @@ node dist/index.js
247
270
  - **Directory (tenant) ID**
248
271
  - **Client secret value**
249
272
 
250
- ### Step 2: Configure Credentials
273
+ ### Step 3: Configure Credentials (Advanced Only)
251
274
 
252
- **Method 1: Environment Variables (Recommended)**
275
+ **Method 1: Environment Variables**
253
276
  ```bash
254
277
  export MS365_CLIENT_ID="your_client_id_here"
255
278
  export MS365_CLIENT_SECRET="your_client_secret_here"
@@ -262,31 +285,6 @@ ms365-mcp-server --setup-auth
262
285
  # Follow the prompts to enter your credentials
263
286
  ```
264
287
 
265
- **Method 3: Credentials File**
266
- ```bash
267
- # Save credentials to ~/.ms365-mcp/credentials.json
268
- {
269
- "clientId": "your_client_id",
270
- "clientSecret": "your_client_secret",
271
- "tenantId": "your_tenant_id",
272
- "redirectUri": "http://localhost:44001/oauth2callback"
273
- }
274
- ```
275
-
276
- ### Step 3: Authentication Modes
277
-
278
- **Single-User Mode (Personal Use)**
279
- ```bash
280
- ms365-mcp-server
281
- # Use quick_authenticate or get_auth_link tools
282
- ```
283
-
284
- **Multi-User Mode (Teams/Shared)**
285
- ```bash
286
- ms365-mcp-server --multi-user
287
- # Use authenticate_user tool for each user
288
- ```
289
-
290
288
  ## 📖 Usage Examples
291
289
 
292
290
  ### Sending Emails
@@ -322,6 +320,14 @@ ms365-mcp-server --multi-user
322
320
  }
323
321
  ```
324
322
 
323
+ ### Secure Authentication Check
324
+ ```javascript
325
+ {
326
+ "tool": "verify_authentication",
327
+ "arguments": {}
328
+ }
329
+ ```
330
+
325
331
  ### Contact Search
326
332
  ```javascript
327
333
  {
@@ -340,17 +346,20 @@ ms365-mcp-server --multi-user
340
346
  - `--logout` - Logout and clear stored authentication tokens
341
347
  - `--verify-login` - Verify current authentication status
342
348
  - `--reset-auth` - Clear stored authentication tokens
343
- - `--multi-user` - Enable multi-user authentication mode
344
349
  - `--debug` - Enable detailed debug logging
345
350
  - `--help` - Show help information
346
351
 
352
+ 🔒 **Security Note**: Removed `--multi-user` mode for enhanced security and simplicity.
353
+
347
354
  ## 🔒 Security & Privacy
348
355
 
349
- - **Local token storage** in `~/.ms365-mcp/token.json`
350
- - **User isolation** in multi-user mode prevents cross-user data access
356
+ - **Secure token storage** with OS Keychain integration
357
+ - **Encrypted file fallback** for cross-platform compatibility
351
358
  - **OAuth2 compliance** with Microsoft MSAL library
352
359
  - **No external data transmission** - all data stays local
353
360
  - **Automatic token refresh** with secure credential management
361
+ - **Privacy protection** - users can only access their own data
362
+ - **No information disclosure** - system details are protected
354
363
 
355
364
  ## 📊 Logging & Debugging
356
365
 
@@ -366,20 +375,20 @@ ms365-mcp-server --debug
366
375
  ### Common Issues
367
376
 
368
377
  **"MS365 credentials not configured"**
369
- - Set environment variables or run `--setup-auth`
370
- - Ensure credentials file exists at correct path
378
+ - Run `ms365-mcp-server --login` for quick setup
379
+ - Use `--setup-auth` for advanced configuration
371
380
 
372
- **"Access blocked" during OAuth**
373
- - Verify your app has required Microsoft Graph permissions
374
- - Ensure redirect URI matches Azure app registration
381
+ **Device code authentication issues**
382
+ - Use `ms365-mcp-server --verify-login` to check status
383
+ - Try `ms365-mcp-server --logout` then `--login` to reset
375
384
 
376
385
  **Authentication timeout**
377
- - Use `--reset-auth` to clear old tokens
378
- - Check that redirect URI matches Azure app settings
386
+ - Device codes expire in 15 minutes
387
+ - Use `--reset-auth` to clear old tokens and start fresh
379
388
 
380
389
  **Permission errors**
381
- - Verify Microsoft Graph API permissions are granted in Azure Portal
382
- - Ensure admin consent is provided if required
390
+ - Verify Microsoft Graph API permissions if using custom Azure app
391
+ - Device code flow handles permissions automatically
383
392
 
384
393
  ### Reset Authentication
385
394
  ```bash
@@ -420,62 +429,38 @@ npm run version-bump major
420
429
  - **Research assistance** - Query and analyze email communications
421
430
  - **Email productivity** - Automate routine email tasks and responses
422
431
 
423
- ## 🆚 **Enhanced Features vs Softeria's Implementation**
424
-
425
- While inspired by [Softeria's excellent MS365 MCP server](https://github.com/Softeria/ms-365-mcp-server), our implementation focuses on **email specialization** with significant enhancements:
426
-
427
- ### **🎯 Our Email-First Advantages**
428
-
429
- | Feature | Our Implementation | Softeria's |
430
- |---------|-------------------|------------|
431
- | **Email Tools** | 11 comprehensive email tools | Basic mail operations |
432
- | **Authentication** | Device code + Redirect flows | Device code only |
433
- | **Storage Security** | OS Keychain + encrypted file | OS keychain fallback |
434
- | **Multi-user Support** | ✅ Full user isolation | ❌ Single user only |
435
- | **Email Search** | Advanced filtering & criteria | Basic search |
436
- | **Attachment Handling** | Full send/receive support | Basic attachment support |
437
- | **Documentation** | Comprehensive setup guides | Basic documentation |
438
- | **CLI Features** | Rich authentication commands | Basic login/logout |
439
-
440
- ### **🚀 Unique Email Features**
432
+ ## 🆚 **Enhanced Security vs Other Implementations**
441
433
 
442
- - **Advanced Email Filtering**: Filter by date ranges, attachments, importance, read status
443
- - **Multi-folder Search**: Search across all mailbox folders simultaneously
444
- - **Email Organization**: Move emails between folders, mark read/unread status
445
- - **Contact Integration**: Full contact search and management
446
- - **Professional Email Composition**: HTML/text emails with attachments and CC/BCC
447
- - **Seamless Claude Integration**: Purpose-built for AI email assistance
434
+ Our implementation prioritizes **security and simplicity** over feature complexity:
448
435
 
449
- ### **🔄 Migration from Softeria**
436
+ ### **🛡️ Our Security-First Advantages**
450
437
 
451
- If you're currently using Softeria's server and want **enhanced email capabilities**:
438
+ | Feature | Our Implementation | Other Implementations |
439
+ |---------|-------------------|----------------------|
440
+ | **Account Enumeration** | 🚫 Blocked (security risk) | ✅ Often exposed |
441
+ | **System Information** | 🚫 Hidden (privacy protection) | ✅ Often exposed |
442
+ | **Multi-User Complexity** | 🚫 Simplified to single-user | ✅ Complex multi-user |
443
+ | **Authentication Model** | ✅ Secure device code focus | ⚠️ Various approaches |
444
+ | **Information Disclosure** | 🚫 Minimized | ⚠️ Often excessive |
445
+ | **Privacy Protection** | ✅ User sees only own data | ⚠️ Variable |
452
446
 
453
- ```bash
454
- # Install our server
455
- npm install -g ms365-mcp-server
447
+ ### **🚀 Security Features**
456
448
 
457
- # Quick authentication (uses built-in app like Softeria)
458
- ms365-mcp-server --login
449
+ - **Privacy by Design**: Users can only access their own information
450
+ - **Minimal Information Disclosure**: No sensitive system details exposed
451
+ - **Simplified Authentication**: Single-user model reduces attack surface
452
+ - **Secure Token Management**: OS keychain with automatic refresh
453
+ - **Device Code Focus**: More secure than redirect-based flows
459
454
 
460
- # Update Claude Desktop config
461
- {
462
- "mcpServers": {
463
- "ms365": {
464
- "command": "ms365-mcp-server",
465
- "args": []
466
- }
467
- }
468
- }
469
- ```
455
+ ### **🔄 Migration Considerations**
470
456
 
471
- **Why Choose Our Implementation?**
472
- - **Email-specialized tools** for comprehensive mailbox management
473
- - **Multi-user support** for teams and shared environments
474
- - **Enhanced security** with OS keychain + encrypted file fallback
475
- - **Professional documentation** and troubleshooting guides
476
- - **Active development** focused on email productivity
457
+ **Enhanced Security Benefits:**
458
+ - **Reduced Attack Surface**: Fewer tools mean less potential vulnerabilities
459
+ - **Privacy Protection**: No account enumeration or system information disclosure
460
+ - **Simplified Model**: Single-user approach is more secure and easier to audit
461
+ - **Security Transparency**: Clear notices about what information is protected
477
462
 
478
- Both implementations are excellent - choose ours for **deep email functionality** or Softeria's for **broad Microsoft 365 integration**.
463
+ Choose our implementation for **maximum security and privacy protection** in your Microsoft 365 integrations.
479
464
 
480
465
  ## 📝 License
481
466
 
@@ -494,4 +479,4 @@ For issues and questions:
494
479
 
495
480
  ---
496
481
 
497
- **Built with ❤️ for the AI and productivity community**
482
+ **Built with 🔒 Security and ❤️ for the AI and productivity community**
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.1"
59
59
  }, {
60
60
  capabilities: {
61
61
  resources: {
@@ -453,21 +453,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
453
453
  additionalProperties: false
454
454
  }
455
455
  },
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
456
  {
472
457
  name: "remove_my_session",
473
458
  description: "Remove your own authentication session (user-specific, secure).",
@@ -487,47 +472,38 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
487
472
  // Add one-time authentication tool for single-user mode
488
473
  const oneTimeAuthTools = [
489
474
  {
490
- name: "quick_authenticate",
491
- description: "One-time Microsoft 365 authentication for immediate access. Opens browser automatically and completes authentication flow.",
475
+ name: "get_auth_link",
476
+ description: "Get a clickable Microsoft 365 authentication link without auto-opening browser. Perfect for manual authentication control.",
492
477
  inputSchema: {
493
478
  type: "object",
494
479
  properties: {
495
480
  force: {
496
481
  type: "boolean",
497
- description: "Force re-authentication even if already authenticated (default: false)",
482
+ description: "Force new authentication link even if already authenticated (default: false)",
498
483
  default: false
499
484
  }
500
485
  },
501
486
  additionalProperties: false
502
487
  }
503
- },
488
+ }
489
+ ];
490
+ // Enhanced authentication tools with device code flow
491
+ const enhancedAuthTools = [
504
492
  {
505
- name: "get_auth_link",
506
- description: "Get a clickable Microsoft 365 authentication link without auto-opening browser. Perfect for manual authentication control.",
493
+ name: "authenticate_with_device_code",
494
+ description: "Complete device code authentication in MCP. First call shows device code, second call (after browser auth) completes the process. RECOMMENDED method.",
507
495
  inputSchema: {
508
496
  type: "object",
509
497
  properties: {
510
498
  force: {
511
499
  type: "boolean",
512
- description: "Force new authentication link even if already authenticated (default: false)",
500
+ description: "Force new authentication even if already authenticated (default: false)",
513
501
  default: false
514
502
  }
515
503
  },
516
504
  additionalProperties: false
517
505
  }
518
506
  },
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
507
  {
532
508
  name: "device_code_login",
533
509
  description: "Start device code authentication flow and get the URL/code to enter. Shows authentication details immediately in the UI.",
@@ -543,15 +519,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
543
519
  additionalProperties: false
544
520
  }
545
521
  },
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
522
  {
556
523
  name: "check_pending_auth",
557
524
  description: "Check if there's a pending device code authentication and get the URL/code again if needed.",
@@ -579,24 +546,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
579
546
  additionalProperties: false
580
547
  }
581
548
  },
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
549
  {
601
550
  name: "logout",
602
551
  description: "Log out and clear stored authentication tokens for the current user. This will require re-authentication.",
@@ -633,26 +582,54 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
633
582
  try {
634
583
  switch (name) {
635
584
  // ============ 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
585
+ case "authenticate_with_device_code":
586
+ try {
587
+ // Check if already authenticated
588
+ if (await enhancedMS365Auth.isAuthenticated() && !args?.force) {
589
+ return {
590
+ content: [
591
+ {
592
+ type: "text",
593
+ text: `✅ Already authenticated with Microsoft 365! Use force: true to re-authenticate.`
594
+ }
595
+ ]
596
+ };
597
+ }
598
+ // First, try to complete any existing pending authentication
599
+ const existingResult = await enhancedMS365Auth.completeDeviceCodeAuth();
600
+ if (existingResult) {
601
+ const currentUser = await enhancedMS365Auth.getCurrentUser();
602
+ return {
603
+ content: [
604
+ {
605
+ type: "text",
606
+ 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!`
607
+ }
608
+ ]
609
+ };
610
+ }
611
+ // Check if there's already a pending device code
612
+ let deviceCodeInfo = await enhancedMS365Auth.getPendingDeviceCodeInfo();
613
+ if (!deviceCodeInfo || args?.force) {
614
+ // No pending code or force new one, start fresh
615
+ deviceCodeInfo = await enhancedMS365Auth.startDeviceCodeAuth();
616
+ }
617
+ // Return device code information immediately (MCP pattern)
641
618
  return {
642
619
  content: [
643
620
  {
644
621
  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.`
622
+ 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
623
  }
647
624
  ]
648
625
  };
649
626
  }
650
- else {
627
+ catch (error) {
651
628
  return {
652
629
  content: [
653
630
  {
654
631
  type: "text",
655
- text: "✅ Already authenticated with Microsoft 365. Use force: true to re-authenticate."
632
+ text: `❌ Authentication failed: ${error.message}\n\n💡 Try again or use CLI: node dist/index.js --login`
656
633
  }
657
634
  ]
658
635
  };
@@ -679,40 +656,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
679
656
  ]
680
657
  };
681
658
  }
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
659
  case "device_code_login":
717
660
  if (!await enhancedMS365Auth.isAuthenticated() || args?.force) {
718
661
  const deviceCodeInfo = await enhancedMS365Auth.startDeviceCodeAuth();
@@ -720,7 +663,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
720
663
  content: [
721
664
  {
722
665
  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.`
666
+ 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
667
  }
725
668
  ]
726
669
  };
@@ -735,53 +678,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
735
678
  ]
736
679
  };
737
680
  }
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) {
681
+ case "check_pending_auth":
682
+ const pendingDeviceCodeState = await enhancedMS365Auth.getPendingDeviceCodeInfo();
683
+ if (pendingDeviceCodeState) {
752
684
  return {
753
685
  content: [
754
686
  {
755
687
  type: "text",
756
- text: "✅ Device code authentication completed successfully! You can now use all Microsoft 365 email features."
688
+ 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
689
  }
758
690
  ]
759
691
  };
760
692
  }
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
693
  return {
786
694
  content: [
787
695
  {
@@ -802,64 +710,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
802
710
  };
803
711
  case "verify_authentication":
804
712
  const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
805
- const accounts = await enhancedMS365Auth.listAuthenticatedAccounts();
713
+ const currentUser = await enhancedMS365Auth.getCurrentUser();
806
714
  const storageInfo = enhancedMS365Auth.getStorageInfo();
807
715
  return {
808
716
  content: [
809
717
  {
810
718
  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'}`
719
+ 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
720
  }
813
721
  ]
814
722
  };
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();
723
+ case "logout":
724
+ const accountKey = args?.accountKey;
725
+ const wasAuthenticated = await enhancedMS365Auth.isAuthenticated();
726
+ await enhancedMS365Auth.resetAuth();
827
727
  return {
828
728
  content: [
829
729
  {
830
730
  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'}`
731
+ text: wasAuthenticated ?
732
+ `✅ Successfully logged out from Microsoft 365.` :
733
+ `ℹ️ No active authentication found.`
832
734
  }
833
735
  ]
834
736
  };
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
737
  // ============ MULTI-USER AUTHENTICATION TOOLS ============
864
738
  case "authenticate_user":
865
739
  if (!ms365Config.multiUser) {
@@ -874,25 +748,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
874
748
  }
875
749
  ]
876
750
  };
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
751
  case "remove_my_session":
897
752
  if (!ms365Config.multiUser) {
898
753
  throw new Error("Multi-user mode not enabled. Start server with --multi-user flag.");
@@ -1218,13 +1073,13 @@ async function main() {
1218
1073
  if (ms365Config.verifyLogin) {
1219
1074
  try {
1220
1075
  const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
1221
- const accounts = await enhancedMS365Auth.listAuthenticatedAccounts();
1076
+ const currentUser = await enhancedMS365Auth.getCurrentUser();
1222
1077
  const storageInfo = enhancedMS365Auth.getStorageInfo();
1223
1078
  console.log('\n📊 MS365 Authentication Status\n');
1224
1079
  console.log(`Authentication: ${isAuthenticated ? '✅ Valid' : '❌ Not authenticated'}`);
1080
+ console.log(`Current User: ${currentUser || 'None'}`);
1225
1081
  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`);
1082
+ console.log(`Storage location: ${storageInfo.location}\n`);
1228
1083
  process.exit(isAuthenticated ? 0 : 1);
1229
1084
  }
1230
1085
  catch (error) {
@@ -23,6 +23,7 @@ const DEFAULT_TENANT_ID = "common";
23
23
  // Configuration directory and file paths
24
24
  const CONFIG_DIR = path.join(os.homedir(), '.ms365-mcp');
25
25
  const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials.json');
26
+ const DEVICE_CODE_FILE = path.join(CONFIG_DIR, 'device-code.json');
26
27
  /**
27
28
  * Enhanced Microsoft 365 authentication manager with device code flow support
28
29
  */
@@ -259,7 +260,7 @@ export class EnhancedMS365Auth {
259
260
  return 'device';
260
261
  }
261
262
  /**
262
- * Save token using secure credential store
263
+ * Save token using secure credential store (simplified single account)
263
264
  */
264
265
  async saveToken(token, authType) {
265
266
  try {
@@ -270,8 +271,8 @@ export class EnhancedMS365Auth {
270
271
  account: token.account,
271
272
  authType: authType
272
273
  };
273
- const accountKey = token.account?.username || 'default-user';
274
- await credentialStore.setCredentials(accountKey, tokenData);
274
+ // Always use a single account key for simplicity
275
+ await credentialStore.setCredentials('ms365-user', tokenData);
275
276
  logger.log('Saved MS365 access token securely');
276
277
  }
277
278
  catch (error) {
@@ -279,11 +280,11 @@ export class EnhancedMS365Auth {
279
280
  }
280
281
  }
281
282
  /**
282
- * Load stored token using secure credential store
283
+ * Load stored token using secure credential store (simplified single account)
283
284
  */
284
- async loadStoredToken(accountKey = 'default-user') {
285
+ async loadStoredToken() {
285
286
  try {
286
- return await credentialStore.getCredentials(accountKey);
287
+ return await credentialStore.getCredentials('ms365-user');
287
288
  }
288
289
  catch (error) {
289
290
  logger.error('Error loading stored token:', error);
@@ -329,22 +330,14 @@ export class EnhancedMS365Auth {
329
330
  /**
330
331
  * Get authenticated Microsoft Graph client
331
332
  */
332
- async getGraphClient(accountKey) {
333
- // If no specific account key provided, use the first available account
334
- if (!accountKey) {
335
- const accounts = await this.listAuthenticatedAccounts();
336
- if (accounts.length === 0) {
337
- throw new Error('No authenticated accounts found. Please authenticate first.');
338
- }
339
- accountKey = accounts[0];
340
- }
341
- const storedToken = await this.loadStoredToken(accountKey);
333
+ async getGraphClient() {
334
+ const storedToken = await this.loadStoredToken();
342
335
  if (!storedToken) {
343
336
  throw new Error('No stored token found. Please authenticate first.');
344
337
  }
345
338
  // Check if token is expired
346
339
  if (storedToken.expiresOn < Date.now()) {
347
- await this.refreshToken(accountKey);
340
+ await this.refreshToken();
348
341
  }
349
342
  const client = Client.init({
350
343
  authProvider: (done) => {
@@ -356,8 +349,8 @@ export class EnhancedMS365Auth {
356
349
  /**
357
350
  * Refresh access token
358
351
  */
359
- async refreshToken(accountKey = 'default-user') {
360
- const storedToken = await this.loadStoredToken(accountKey);
352
+ async refreshToken() {
353
+ const storedToken = await this.loadStoredToken();
361
354
  if (!storedToken?.account) {
362
355
  throw new Error('No account information available. Please re-authenticate.');
363
356
  }
@@ -384,29 +377,15 @@ export class EnhancedMS365Auth {
384
377
  /**
385
378
  * Check if user is authenticated
386
379
  */
387
- async isAuthenticated(accountKey) {
388
- // If no specific account key provided, check all available accounts
389
- if (!accountKey) {
390
- const accounts = await this.listAuthenticatedAccounts();
391
- if (accounts.length === 0) {
392
- return false;
393
- }
394
- // Check if any account has valid authentication
395
- for (const account of accounts) {
396
- if (await this.isAuthenticated(account)) {
397
- return true;
398
- }
399
- }
400
- return false;
401
- }
402
- const storedToken = await this.loadStoredToken(accountKey);
380
+ async isAuthenticated() {
381
+ const storedToken = await this.loadStoredToken();
403
382
  if (!storedToken) {
404
383
  return false;
405
384
  }
406
385
  // If token is expired, try to refresh
407
386
  if (storedToken.expiresOn < Date.now()) {
408
387
  try {
409
- await this.refreshToken(accountKey);
388
+ await this.refreshToken();
410
389
  return true;
411
390
  }
412
391
  catch (error) {
@@ -425,33 +404,160 @@ export class EnhancedMS365Auth {
425
404
  /**
426
405
  * Clear stored authentication data
427
406
  */
428
- async resetAuth(accountKey) {
407
+ async resetAuth() {
408
+ try {
409
+ await credentialStore.deleteCredentials('ms365-user');
410
+ await this.clearDeviceCodeState();
411
+ logger.log('Cleared stored authentication tokens');
412
+ }
413
+ catch (error) {
414
+ logger.error('Error clearing authentication data:', error);
415
+ }
416
+ }
417
+ /**
418
+ * Save device code state to file
419
+ */
420
+ async saveDeviceCodeState(state) {
421
+ try {
422
+ fs.writeFileSync(DEVICE_CODE_FILE, JSON.stringify(state, null, 2));
423
+ logger.log('Saved device code state to file');
424
+ }
425
+ catch (error) {
426
+ logger.error('Error saving device code state:', error);
427
+ throw new Error('Failed to save device code state');
428
+ }
429
+ }
430
+ /**
431
+ * Load device code state from file
432
+ */
433
+ async loadDeviceCodeState() {
429
434
  try {
430
- if (accountKey) {
431
- // Delete specific account
432
- await credentialStore.deleteCredentials(accountKey);
433
- logger.log(`Cleared stored authentication tokens for account: ${accountKey}`);
435
+ if (!fs.existsSync(DEVICE_CODE_FILE)) {
436
+ return null;
434
437
  }
435
- else {
436
- // Delete all authenticated accounts
437
- const authenticatedAccounts = await this.listAuthenticatedAccounts();
438
- if (authenticatedAccounts.length === 0) {
439
- // If no accounts found by listing, try deleting the default-user key as fallback
440
- await credentialStore.deleteCredentials('default-user');
441
- logger.log('Cleared stored authentication tokens (fallback to default-user)');
442
- }
443
- else {
444
- // Delete all found accounts
445
- for (const account of authenticatedAccounts) {
446
- await credentialStore.deleteCredentials(account);
447
- logger.log(`Cleared stored authentication tokens for account: ${account}`);
438
+ const stateData = fs.readFileSync(DEVICE_CODE_FILE, 'utf8');
439
+ const state = JSON.parse(stateData);
440
+ // Check if device code has expired
441
+ const now = Date.now();
442
+ const elapsed = (now - state.startTime) / 1000;
443
+ if (elapsed > state.expiresIn) {
444
+ // Device code has expired, clean up
445
+ await this.clearDeviceCodeState();
446
+ return null;
447
+ }
448
+ return state;
449
+ }
450
+ catch (error) {
451
+ logger.error('Error loading device code state:', error);
452
+ return null;
453
+ }
454
+ }
455
+ /**
456
+ * Clear device code state file
457
+ */
458
+ async clearDeviceCodeState() {
459
+ try {
460
+ if (fs.existsSync(DEVICE_CODE_FILE)) {
461
+ fs.unlinkSync(DEVICE_CODE_FILE);
462
+ logger.log('Cleared device code state file');
463
+ }
464
+ }
465
+ catch (error) {
466
+ logger.error('Error clearing device code state:', error);
467
+ }
468
+ }
469
+ /**
470
+ * Complete device code authentication using saved state
471
+ */
472
+ async completeDeviceCodeAuth() {
473
+ const deviceCodeState = await this.loadDeviceCodeState();
474
+ if (!deviceCodeState) {
475
+ return false; // No pending device code authentication
476
+ }
477
+ if (!await this.loadCredentials()) {
478
+ throw new Error('MS365 credentials not configured');
479
+ }
480
+ try {
481
+ // Use the raw MSAL token endpoint to check if authentication completed
482
+ const tokenUrl = `https://login.microsoftonline.com/${this.credentials.tenantId}/oauth2/v2.0/token`;
483
+ const response = await fetch(tokenUrl, {
484
+ method: 'POST',
485
+ headers: {
486
+ 'Content-Type': 'application/x-www-form-urlencoded',
487
+ },
488
+ body: new URLSearchParams({
489
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
490
+ device_code: deviceCodeState.deviceCode,
491
+ client_id: this.credentials.clientId,
492
+ }),
493
+ });
494
+ const result = await response.json();
495
+ if (response.ok && result.access_token) {
496
+ // Authentication completed successfully
497
+ // Microsoft's token response doesn't include account info, so we need to get it from the token
498
+ let username = 'authenticated-user';
499
+ try {
500
+ // Try to decode the access token to get user info
501
+ const tokenParts = result.access_token.split('.');
502
+ if (tokenParts.length >= 2) {
503
+ const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString());
504
+ username = payload.upn || payload.unique_name || payload.preferred_username || 'authenticated-user';
448
505
  }
449
506
  }
507
+ catch (decodeError) {
508
+ logger.log('Could not decode token for username, using default');
509
+ }
510
+ const tokenResponse = {
511
+ accessToken: result.access_token,
512
+ refreshToken: result.refresh_token || '',
513
+ expiresOn: new Date(Date.now() + (result.expires_in * 1000)),
514
+ account: {
515
+ username: username,
516
+ homeAccountId: `${username}.${this.credentials.tenantId}`,
517
+ environment: 'login.microsoftonline.com',
518
+ tenantId: this.credentials.tenantId,
519
+ localAccountId: username
520
+ }
521
+ };
522
+ await this.saveToken(tokenResponse, 'device');
523
+ await this.clearDeviceCodeState();
524
+ logger.log('MS365 device code authentication completed successfully');
525
+ return true;
526
+ }
527
+ else if (result.error === 'authorization_pending') {
528
+ // Still waiting for user to complete authentication
529
+ return false;
530
+ }
531
+ else if (result.error === 'expired_token') {
532
+ // Device code has expired
533
+ await this.clearDeviceCodeState();
534
+ return false;
535
+ }
536
+ else {
537
+ // Other error - don't clear device code state, just return false
538
+ logger.error('Device code authentication error:', result);
539
+ return false;
450
540
  }
451
541
  }
452
542
  catch (error) {
453
- logger.error('Error clearing authentication data:', error);
543
+ logger.error('Error completing device code authentication:', error);
544
+ // Don't clear device code state on network/other errors
545
+ return false;
546
+ }
547
+ }
548
+ /**
549
+ * Get pending device code info from saved state
550
+ */
551
+ async getPendingDeviceCodeInfo() {
552
+ const deviceCodeState = await this.loadDeviceCodeState();
553
+ if (!deviceCodeState) {
554
+ return null;
454
555
  }
556
+ return {
557
+ verificationUri: deviceCodeState.verificationUri,
558
+ userCode: deviceCodeState.userCode,
559
+ message: deviceCodeState.message
560
+ };
455
561
  }
456
562
  /**
457
563
  * Get authentication URL for device code flow
@@ -487,36 +593,47 @@ export class EnhancedMS365Auth {
487
593
  return new Promise((resolve, reject) => {
488
594
  const deviceCodeRequest = {
489
595
  scopes: SCOPES,
490
- deviceCodeCallback: (response) => {
596
+ deviceCodeCallback: async (response) => {
491
597
  const deviceCodeInfo = {
492
598
  verificationUri: response.verificationUri,
493
599
  userCode: response.userCode,
494
600
  message: response.message
495
601
  };
496
- // Store the pending auth promise
497
- const authPromise = msalClient.acquireTokenByDeviceCode(deviceCodeRequest)
498
- .then(async (tokenResponse) => {
499
- if (!tokenResponse) {
500
- throw new Error('Failed to acquire token via device code');
501
- }
502
- await this.saveToken(tokenResponse, 'device');
503
- logger.log('MS365 device code authentication successful');
504
- this.pendingAuth = null; // Clear pending auth
505
- return tokenResponse;
506
- })
507
- .catch((error) => {
508
- this.pendingAuth = null; // Clear pending auth on error
509
- throw error;
510
- });
511
- this.pendingAuth = { authPromise, deviceCodeInfo };
512
- logger.log(`Device code authentication: ${response.verificationUri} - ${response.userCode}`);
513
- // Return device code info immediately
514
- resolve(deviceCodeInfo);
602
+ // Save device code state for later completion
603
+ const deviceCodeState = {
604
+ deviceCode: response.deviceCode,
605
+ userCode: response.userCode,
606
+ verificationUri: response.verificationUri,
607
+ expiresIn: response.expiresIn,
608
+ startTime: Date.now(),
609
+ message: response.message
610
+ };
611
+ try {
612
+ await this.saveDeviceCodeState(deviceCodeState);
613
+ logger.log(`Device code authentication started: ${response.verificationUri} - ${response.userCode}`);
614
+ // Return device code info immediately
615
+ resolve(deviceCodeInfo);
616
+ }
617
+ catch (error) {
618
+ reject(error);
619
+ }
515
620
  }
516
621
  };
517
- // This will never complete, but will call the callback immediately
518
- msalClient.acquireTokenByDeviceCode(deviceCodeRequest).catch(() => {
519
- // Ignore the error from this call since we're handling it in the stored promise
622
+ // Start the device code flow - we need this to run to get the device code
623
+ // The callback will resolve our promise, and we'll handle the auth later
624
+ msalClient.acquireTokenByDeviceCode(deviceCodeRequest).then((result) => {
625
+ // If this completes immediately (unlikely but possible), save the token
626
+ if (result) {
627
+ this.saveToken(result, 'device').catch(() => {
628
+ // Ignore save errors here since we primarily care about getting the device code
629
+ });
630
+ }
631
+ }).catch((error) => {
632
+ // Only reject if we haven't already resolved with device code info
633
+ // The most common case is that the user hasn't completed auth yet
634
+ if (error.errorCode !== 'user_cancelled' && error.errorCode !== 'authorization_pending') {
635
+ logger.error('Device code flow error:', error);
636
+ }
520
637
  });
521
638
  });
522
639
  }
@@ -535,12 +652,6 @@ export class EnhancedMS365Auth {
535
652
  hasPendingAuth() {
536
653
  return this.pendingAuth !== null;
537
654
  }
538
- /**
539
- * Get pending device code info
540
- */
541
- getPendingDeviceCodeInfo() {
542
- return this.pendingAuth?.deviceCodeInfo || null;
543
- }
544
655
  /**
545
656
  * Setup credentials interactively
546
657
  */
@@ -612,10 +723,14 @@ export class EnhancedMS365Auth {
612
723
  };
613
724
  }
614
725
  /**
615
- * List all authenticated accounts
726
+ * Get current authenticated user (secure - only your own info)
616
727
  */
617
- async listAuthenticatedAccounts() {
618
- return await credentialStore.listAccounts();
728
+ async getCurrentUser() {
729
+ const storedToken = await this.loadStoredToken();
730
+ if (storedToken && storedToken.expiresOn > Date.now()) {
731
+ return storedToken.account?.username || 'authenticated-user';
732
+ }
733
+ return null;
619
734
  }
620
735
  /**
621
736
  * Get authentication URL without opening browser (redirect flow)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ms365-mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
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",