ms365-mcp-server 1.0.5 ā 1.1.0
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 +214 -116
- package/bin/cli.js +2 -2
- package/dist/index.js +320 -645
- package/package.json +2 -3
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
|
|
58
|
+
version: "1.1.0"
|
|
59
59
|
}, {
|
|
60
60
|
capabilities: {
|
|
61
61
|
resources: {
|
|
@@ -172,8 +172,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
172
172
|
}
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
|
-
name: "
|
|
176
|
-
description: "Read
|
|
175
|
+
name: "manage_email",
|
|
176
|
+
description: "UNIFIED EMAIL MANAGEMENT: Read, search, list, mark, move, or delete emails. Combines all email operations in one powerful tool. Supports intelligent partial name matching, folder management, and advanced search criteria.",
|
|
177
177
|
inputSchema: {
|
|
178
178
|
type: "object",
|
|
179
179
|
properties: {
|
|
@@ -181,29 +181,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
181
181
|
type: "string",
|
|
182
182
|
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
183
183
|
},
|
|
184
|
+
action: {
|
|
185
|
+
type: "string",
|
|
186
|
+
enum: ["read", "search", "list", "mark", "move", "delete", "search_to_me"],
|
|
187
|
+
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)"
|
|
188
|
+
},
|
|
184
189
|
messageId: {
|
|
185
190
|
type: "string",
|
|
186
|
-
description: "
|
|
191
|
+
description: "Email ID (required for: read, mark, move, delete)"
|
|
187
192
|
},
|
|
188
193
|
includeAttachments: {
|
|
189
194
|
type: "boolean",
|
|
190
|
-
description: "
|
|
195
|
+
description: "Include attachment info when reading email",
|
|
191
196
|
default: false
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
{
|
|
199
|
-
name: "search_emails",
|
|
200
|
-
description: "Search emails with various criteria including subject, sender, TO/CC recipients, date range, and advanced filtering. Features intelligent partial name matching for senders - just use their first name, last name, or partial name in the 'from' field. Supports complex queries and filtering, including emails addressed to you directly or where you were CC'd.",
|
|
201
|
-
inputSchema: {
|
|
202
|
-
type: "object",
|
|
203
|
-
properties: {
|
|
204
|
-
userId: {
|
|
197
|
+
},
|
|
198
|
+
isRead: {
|
|
199
|
+
type: "boolean",
|
|
200
|
+
description: "Mark as read (true) or unread (false) - used with mark action"
|
|
201
|
+
},
|
|
202
|
+
destinationFolderId: {
|
|
205
203
|
type: "string",
|
|
206
|
-
description: "
|
|
204
|
+
description: "Destination folder for move action (e.g., 'archive', 'drafts', 'deleteditems')"
|
|
205
|
+
},
|
|
206
|
+
folderId: {
|
|
207
|
+
type: "string",
|
|
208
|
+
description: "Folder to list emails from (default: inbox) - used with list action",
|
|
209
|
+
default: "inbox"
|
|
207
210
|
},
|
|
208
211
|
query: {
|
|
209
212
|
type: "string",
|
|
@@ -211,85 +214,27 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
211
214
|
},
|
|
212
215
|
from: {
|
|
213
216
|
type: "string",
|
|
214
|
-
description: "Search
|
|
217
|
+
description: "Search emails from specific sender (supports partial names like 'John' or 'Smith')"
|
|
215
218
|
},
|
|
216
219
|
to: {
|
|
217
220
|
type: "string",
|
|
218
|
-
description: "Search
|
|
221
|
+
description: "Search emails to specific recipient"
|
|
219
222
|
},
|
|
220
223
|
cc: {
|
|
221
224
|
type: "string",
|
|
222
|
-
description: "Search
|
|
223
|
-
},
|
|
224
|
-
subject: {
|
|
225
|
-
type: "string",
|
|
226
|
-
description: "Search for emails with specific subject"
|
|
227
|
-
},
|
|
228
|
-
after: {
|
|
229
|
-
type: "string",
|
|
230
|
-
description: "Search for emails after date (format: YYYY-MM-DD)"
|
|
231
|
-
},
|
|
232
|
-
before: {
|
|
233
|
-
type: "string",
|
|
234
|
-
description: "Search for emails before date (format: YYYY-MM-DD)"
|
|
235
|
-
},
|
|
236
|
-
hasAttachment: {
|
|
237
|
-
type: "boolean",
|
|
238
|
-
description: "Filter emails that have attachments"
|
|
239
|
-
},
|
|
240
|
-
folder: {
|
|
241
|
-
type: "string",
|
|
242
|
-
description: "Search within specific folder (default: inbox)"
|
|
243
|
-
},
|
|
244
|
-
isUnread: {
|
|
245
|
-
type: "boolean",
|
|
246
|
-
description: "Filter for unread emails only"
|
|
247
|
-
},
|
|
248
|
-
importance: {
|
|
249
|
-
type: "string",
|
|
250
|
-
enum: ["low", "normal", "high"],
|
|
251
|
-
description: "Filter by email importance level"
|
|
252
|
-
},
|
|
253
|
-
maxResults: {
|
|
254
|
-
type: "number",
|
|
255
|
-
description: "Maximum number of results to return (default: 50, max: 200)",
|
|
256
|
-
minimum: 1,
|
|
257
|
-
maximum: 200,
|
|
258
|
-
default: 50
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
additionalProperties: false
|
|
262
|
-
}
|
|
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"
|
|
225
|
+
description: "Search emails with specific CC recipient"
|
|
281
226
|
},
|
|
282
227
|
subject: {
|
|
283
228
|
type: "string",
|
|
284
|
-
description: "Search
|
|
229
|
+
description: "Search emails with specific subject"
|
|
285
230
|
},
|
|
286
231
|
after: {
|
|
287
232
|
type: "string",
|
|
288
|
-
description: "Search
|
|
233
|
+
description: "Search emails after date (format: YYYY-MM-DD)"
|
|
289
234
|
},
|
|
290
235
|
before: {
|
|
291
236
|
type: "string",
|
|
292
|
-
description: "Search
|
|
237
|
+
description: "Search emails before date (format: YYYY-MM-DD)"
|
|
293
238
|
},
|
|
294
239
|
hasAttachment: {
|
|
295
240
|
type: "boolean",
|
|
@@ -316,97 +261,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
316
261
|
default: 50
|
|
317
262
|
}
|
|
318
263
|
},
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
name: "list_emails",
|
|
324
|
-
description: "List emails in inbox, sent, or custom folder. Returns basic email information including subjects, senders, and snippets with support for folder navigation.",
|
|
325
|
-
inputSchema: {
|
|
326
|
-
type: "object",
|
|
327
|
-
properties: {
|
|
328
|
-
userId: {
|
|
329
|
-
type: "string",
|
|
330
|
-
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
331
|
-
},
|
|
332
|
-
folderId: {
|
|
333
|
-
type: "string",
|
|
334
|
-
description: "Folder ID or name (default: inbox). Common values: inbox, sentitems, drafts, deleteditems",
|
|
335
|
-
default: "inbox"
|
|
336
|
-
},
|
|
337
|
-
maxResults: {
|
|
338
|
-
type: "number",
|
|
339
|
-
description: "Maximum number of emails to retrieve (default: 50, max: 200)",
|
|
340
|
-
minimum: 1,
|
|
341
|
-
maximum: 200,
|
|
342
|
-
default: 50
|
|
343
|
-
}
|
|
344
|
-
},
|
|
345
|
-
additionalProperties: false
|
|
346
|
-
}
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
name: "mark_email",
|
|
350
|
-
description: "Mark email as read or unread. Useful for managing email status and organizing your inbox.",
|
|
351
|
-
inputSchema: {
|
|
352
|
-
type: "object",
|
|
353
|
-
properties: {
|
|
354
|
-
userId: {
|
|
355
|
-
type: "string",
|
|
356
|
-
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
357
|
-
},
|
|
358
|
-
messageId: {
|
|
359
|
-
type: "string",
|
|
360
|
-
description: "Microsoft 365 message ID to mark"
|
|
361
|
-
},
|
|
362
|
-
isRead: {
|
|
363
|
-
type: "boolean",
|
|
364
|
-
description: "True to mark as read, false to mark as unread"
|
|
365
|
-
}
|
|
366
|
-
},
|
|
367
|
-
required: ["messageId", "isRead"],
|
|
368
|
-
additionalProperties: false
|
|
369
|
-
}
|
|
370
|
-
},
|
|
371
|
-
{
|
|
372
|
-
name: "move_email",
|
|
373
|
-
description: "Move email to different folder. Supports moving emails between various Outlook folders for organization.",
|
|
374
|
-
inputSchema: {
|
|
375
|
-
type: "object",
|
|
376
|
-
properties: {
|
|
377
|
-
userId: {
|
|
378
|
-
type: "string",
|
|
379
|
-
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
380
|
-
},
|
|
381
|
-
messageId: {
|
|
382
|
-
type: "string",
|
|
383
|
-
description: "Microsoft 365 message ID to move"
|
|
384
|
-
},
|
|
385
|
-
destinationFolderId: {
|
|
386
|
-
type: "string",
|
|
387
|
-
description: "Destination folder ID or name (e.g., 'archive', 'drafts', 'deleteditems')"
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
required: ["messageId", "destinationFolderId"],
|
|
391
|
-
additionalProperties: false
|
|
392
|
-
}
|
|
393
|
-
},
|
|
394
|
-
{
|
|
395
|
-
name: "delete_email",
|
|
396
|
-
description: "Permanently delete an email message. Use with caution as this action cannot be undone.",
|
|
397
|
-
inputSchema: {
|
|
398
|
-
type: "object",
|
|
399
|
-
properties: {
|
|
400
|
-
userId: {
|
|
401
|
-
type: "string",
|
|
402
|
-
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
403
|
-
},
|
|
404
|
-
messageId: {
|
|
405
|
-
type: "string",
|
|
406
|
-
description: "Microsoft 365 message ID to delete"
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
required: ["messageId"],
|
|
264
|
+
required: ["action"],
|
|
410
265
|
additionalProperties: false
|
|
411
266
|
}
|
|
412
267
|
},
|
|
@@ -448,8 +303,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
448
303
|
}
|
|
449
304
|
},
|
|
450
305
|
{
|
|
451
|
-
name: "
|
|
452
|
-
description: "
|
|
306
|
+
name: "manage_contacts",
|
|
307
|
+
description: "UNIFIED CONTACT MANAGEMENT: Get all contacts or search contacts by name/email. Combines contact operations in one tool.",
|
|
453
308
|
inputSchema: {
|
|
454
309
|
type: "object",
|
|
455
310
|
properties: {
|
|
@@ -457,40 +312,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
457
312
|
type: "string",
|
|
458
313
|
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
459
314
|
},
|
|
460
|
-
|
|
461
|
-
type: "number",
|
|
462
|
-
description: "Maximum number of contacts to retrieve (default: 100, max: 500)",
|
|
463
|
-
minimum: 1,
|
|
464
|
-
maximum: 500,
|
|
465
|
-
default: 100
|
|
466
|
-
}
|
|
467
|
-
},
|
|
468
|
-
additionalProperties: false
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
name: "search_contacts",
|
|
473
|
-
description: "Search contacts by name or email address. Useful for finding specific contacts for email addressing.",
|
|
474
|
-
inputSchema: {
|
|
475
|
-
type: "object",
|
|
476
|
-
properties: {
|
|
477
|
-
userId: {
|
|
315
|
+
action: {
|
|
478
316
|
type: "string",
|
|
479
|
-
|
|
317
|
+
enum: ["list", "search"],
|
|
318
|
+
description: "Action: list (get all contacts) or search (find specific contacts)",
|
|
319
|
+
default: "list"
|
|
480
320
|
},
|
|
481
321
|
query: {
|
|
482
322
|
type: "string",
|
|
483
|
-
description: "Search query for contact name or email address"
|
|
323
|
+
description: "Search query for contact name or email address (required for search action)"
|
|
484
324
|
},
|
|
485
325
|
maxResults: {
|
|
486
326
|
type: "number",
|
|
487
|
-
description: "Maximum number of contacts to return (default: 50, max:
|
|
327
|
+
description: "Maximum number of contacts to return (default: 100 for list, 50 for search, max: 500)",
|
|
488
328
|
minimum: 1,
|
|
489
|
-
maximum:
|
|
490
|
-
default: 50
|
|
329
|
+
maximum: 500
|
|
491
330
|
}
|
|
492
331
|
},
|
|
493
|
-
required: ["query"],
|
|
494
332
|
additionalProperties: false
|
|
495
333
|
}
|
|
496
334
|
}
|
|
@@ -527,92 +365,28 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
527
365
|
}
|
|
528
366
|
}
|
|
529
367
|
];
|
|
530
|
-
//
|
|
531
|
-
const
|
|
532
|
-
{
|
|
533
|
-
name: "get_auth_link",
|
|
534
|
-
description: "Get a clickable Microsoft 365 authentication link without auto-opening browser. Perfect for manual authentication control.",
|
|
535
|
-
inputSchema: {
|
|
536
|
-
type: "object",
|
|
537
|
-
properties: {
|
|
538
|
-
force: {
|
|
539
|
-
type: "boolean",
|
|
540
|
-
description: "Force new authentication link even if already authenticated (default: false)",
|
|
541
|
-
default: false
|
|
542
|
-
}
|
|
543
|
-
},
|
|
544
|
-
additionalProperties: false
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
];
|
|
548
|
-
// Enhanced authentication tools with device code flow
|
|
549
|
-
const enhancedAuthTools = [
|
|
368
|
+
// Consolidated authentication tools
|
|
369
|
+
const authTools = [
|
|
550
370
|
{
|
|
551
|
-
name: "
|
|
552
|
-
description: "
|
|
553
|
-
inputSchema: {
|
|
554
|
-
type: "object",
|
|
555
|
-
properties: {
|
|
556
|
-
force: {
|
|
557
|
-
type: "boolean",
|
|
558
|
-
description: "Force new authentication even if already authenticated (default: false)",
|
|
559
|
-
default: false
|
|
560
|
-
}
|
|
561
|
-
},
|
|
562
|
-
additionalProperties: false
|
|
563
|
-
}
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
name: "device_code_login",
|
|
567
|
-
description: "Start device code authentication flow and get the URL/code to enter. Shows authentication details immediately in the UI.",
|
|
371
|
+
name: "authenticate",
|
|
372
|
+
description: "UNIFIED AUTHENTICATION: Handle all Microsoft 365 authentication needs. Supports device code flow, status checking, and logout. RECOMMENDED for all auth operations.",
|
|
568
373
|
inputSchema: {
|
|
569
374
|
type: "object",
|
|
570
375
|
properties: {
|
|
376
|
+
action: {
|
|
377
|
+
type: "string",
|
|
378
|
+
enum: ["login", "status", "logout", "device_code", "check_pending"],
|
|
379
|
+
description: "Auth action: login (device code auth), status (check auth status), logout (clear tokens), device_code (get device code only), check_pending (check pending auth)",
|
|
380
|
+
default: "login"
|
|
381
|
+
},
|
|
571
382
|
force: {
|
|
572
383
|
type: "boolean",
|
|
573
|
-
description: "Force new authentication even if already authenticated (
|
|
384
|
+
description: "Force new authentication even if already authenticated (for login action)",
|
|
574
385
|
default: false
|
|
575
|
-
}
|
|
576
|
-
},
|
|
577
|
-
additionalProperties: false
|
|
578
|
-
}
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
name: "check_pending_auth",
|
|
582
|
-
description: "Check if there's a pending device code authentication and get the URL/code again if needed.",
|
|
583
|
-
inputSchema: {
|
|
584
|
-
type: "object",
|
|
585
|
-
properties: {},
|
|
586
|
-
additionalProperties: false
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
name: "get_device_code",
|
|
591
|
-
description: "Get device code information for manual authentication without starting the full flow. Returns URL and code to enter.",
|
|
592
|
-
inputSchema: {
|
|
593
|
-
type: "object",
|
|
594
|
-
properties: {},
|
|
595
|
-
additionalProperties: false
|
|
596
|
-
}
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
name: "verify_authentication",
|
|
600
|
-
description: "Verify current authentication status and show account information. Useful for checking if authentication is still valid.",
|
|
601
|
-
inputSchema: {
|
|
602
|
-
type: "object",
|
|
603
|
-
properties: {},
|
|
604
|
-
additionalProperties: false
|
|
605
|
-
}
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
name: "logout",
|
|
609
|
-
description: "Log out and clear stored authentication tokens for the current user. This will require re-authentication.",
|
|
610
|
-
inputSchema: {
|
|
611
|
-
type: "object",
|
|
612
|
-
properties: {
|
|
386
|
+
},
|
|
613
387
|
accountKey: {
|
|
614
388
|
type: "string",
|
|
615
|
-
description: "Account key
|
|
389
|
+
description: "Account key for logout action (default: current user)",
|
|
616
390
|
default: "default-user"
|
|
617
391
|
}
|
|
618
392
|
},
|
|
@@ -623,14 +397,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
623
397
|
if (ms365Config.multiUser) {
|
|
624
398
|
return { tools: [...baseTools, ...multiUserTools] };
|
|
625
399
|
}
|
|
626
|
-
// For single-user mode, include
|
|
627
|
-
|
|
628
|
-
const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
|
|
629
|
-
if (!isAuthenticated) {
|
|
630
|
-
return { tools: [...baseTools, ...oneTimeAuthTools, ...enhancedAuthTools] };
|
|
631
|
-
}
|
|
632
|
-
// If authenticated, still provide auth management tools
|
|
633
|
-
return { tools: [...baseTools, ...enhancedAuthTools] };
|
|
400
|
+
// For single-user mode, always include auth tools
|
|
401
|
+
return { tools: [...baseTools, ...authTools] };
|
|
634
402
|
});
|
|
635
403
|
/**
|
|
636
404
|
* Handle tool execution requests
|
|
@@ -639,312 +407,257 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
639
407
|
const { name, arguments: args } = request.params;
|
|
640
408
|
try {
|
|
641
409
|
switch (name) {
|
|
642
|
-
// ============
|
|
643
|
-
case "
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
410
|
+
// ============ UNIFIED AUTHENTICATION TOOL ============
|
|
411
|
+
case "authenticate":
|
|
412
|
+
const action = args?.action || 'login';
|
|
413
|
+
switch (action) {
|
|
414
|
+
case "login":
|
|
415
|
+
try {
|
|
416
|
+
// Check if already authenticated
|
|
417
|
+
if (await enhancedMS365Auth.isAuthenticated() && !args?.force) {
|
|
418
|
+
return {
|
|
419
|
+
content: [
|
|
420
|
+
{
|
|
421
|
+
type: "text",
|
|
422
|
+
text: `ā
Already authenticated with Microsoft 365! Use force: true to re-authenticate.`
|
|
423
|
+
}
|
|
424
|
+
]
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
// First, try to complete any existing pending authentication
|
|
428
|
+
const existingResult = await enhancedMS365Auth.completeDeviceCodeAuth();
|
|
429
|
+
if (existingResult) {
|
|
430
|
+
const currentUser = await enhancedMS365Auth.getCurrentUser();
|
|
431
|
+
return {
|
|
432
|
+
content: [
|
|
433
|
+
{
|
|
434
|
+
type: "text",
|
|
435
|
+
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!`
|
|
436
|
+
}
|
|
437
|
+
]
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
// Check if there's already a pending device code
|
|
441
|
+
let deviceCodeInfo = await enhancedMS365Auth.getPendingDeviceCodeInfo();
|
|
442
|
+
if (!deviceCodeInfo || args?.force) {
|
|
443
|
+
// No pending code or force new one, start fresh
|
|
444
|
+
deviceCodeInfo = await enhancedMS365Auth.startDeviceCodeAuth();
|
|
445
|
+
}
|
|
446
|
+
// Return device code information immediately (MCP pattern)
|
|
447
|
+
return {
|
|
448
|
+
content: [
|
|
449
|
+
{
|
|
450
|
+
type: "text",
|
|
451
|
+
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.`
|
|
452
|
+
}
|
|
453
|
+
]
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
return {
|
|
458
|
+
content: [
|
|
459
|
+
{
|
|
460
|
+
type: "text",
|
|
461
|
+
text: `ā Authentication failed: ${error.message}\n\nš” Try again or use CLI: node dist/index.js --login`
|
|
462
|
+
}
|
|
463
|
+
]
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
case "status":
|
|
467
|
+
const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
|
|
468
|
+
const currentUser = await enhancedMS365Auth.getCurrentUser();
|
|
469
|
+
const storageInfo = enhancedMS365Auth.getStorageInfo();
|
|
470
|
+
const tokenInfo = await enhancedMS365Auth.getTokenExpirationInfo();
|
|
471
|
+
let statusText = `š 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}`;
|
|
472
|
+
if (isAuthenticated) {
|
|
473
|
+
statusText += `\nā° Token expires in: ${tokenInfo.expiresInMinutes} minutes`;
|
|
474
|
+
if (tokenInfo.needsRefresh) {
|
|
475
|
+
statusText += `\nā ļø Token will be refreshed automatically on next operation`;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
statusText += `\nš” Use "authenticate" with action: login to sign in`;
|
|
480
|
+
}
|
|
647
481
|
return {
|
|
648
482
|
content: [
|
|
649
483
|
{
|
|
650
484
|
type: "text",
|
|
651
|
-
text:
|
|
485
|
+
text: statusText
|
|
652
486
|
}
|
|
653
487
|
]
|
|
654
488
|
};
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const currentUser = await enhancedMS365Auth.getCurrentUser();
|
|
489
|
+
case "logout":
|
|
490
|
+
const accountKey = args?.accountKey;
|
|
491
|
+
const wasAuthenticated = await enhancedMS365Auth.isAuthenticated();
|
|
492
|
+
await enhancedMS365Auth.resetAuth();
|
|
660
493
|
return {
|
|
661
494
|
content: [
|
|
662
495
|
{
|
|
663
496
|
type: "text",
|
|
664
|
-
text:
|
|
497
|
+
text: wasAuthenticated ?
|
|
498
|
+
`ā
Successfully logged out from Microsoft 365.` :
|
|
499
|
+
`ā¹ļø No active authentication found.`
|
|
665
500
|
}
|
|
666
501
|
]
|
|
667
502
|
};
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
503
|
+
case "device_code":
|
|
504
|
+
const deviceCodeInfo = await enhancedMS365Auth.getDeviceCodeInfo();
|
|
505
|
+
return {
|
|
506
|
+
content: [
|
|
507
|
+
{
|
|
508
|
+
type: "text",
|
|
509
|
+
text: `š Microsoft 365 Device Code Authentication\n\nš± Visit: ${deviceCodeInfo.verificationUri}\nš Enter code: ${deviceCodeInfo.userCode}\n\nā³ This code will expire in 15 minutes.`
|
|
510
|
+
}
|
|
511
|
+
]
|
|
512
|
+
};
|
|
513
|
+
case "check_pending":
|
|
514
|
+
const pendingDeviceCodeState = await enhancedMS365Auth.getPendingDeviceCodeInfo();
|
|
515
|
+
if (pendingDeviceCodeState) {
|
|
516
|
+
return {
|
|
517
|
+
content: [
|
|
518
|
+
{
|
|
519
|
+
type: "text",
|
|
520
|
+
text: `ā³ Pending Device Code Authentication\n\nš± Visit: ${pendingDeviceCodeState.verificationUri}\nš Enter this code: ${pendingDeviceCodeState.userCode}\n\nš” Use "authenticate" with action: login to finish authentication after entering the code.`
|
|
521
|
+
}
|
|
522
|
+
]
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
content: [
|
|
527
|
+
{
|
|
528
|
+
type: "text",
|
|
529
|
+
text: "ā¹ļø No pending device code authentication found. Use 'authenticate' with action: login to start a new authentication process."
|
|
530
|
+
}
|
|
531
|
+
]
|
|
532
|
+
};
|
|
533
|
+
default:
|
|
534
|
+
throw new Error(`Unknown authentication action: ${action}`);
|
|
684
535
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
case "get_auth_link":
|
|
696
|
-
if (!await enhancedMS365Auth.isAuthenticated() || args?.force) {
|
|
697
|
-
const authUrl = await enhancedMS365Auth.getAuthUrl();
|
|
698
|
-
return {
|
|
699
|
-
content: [
|
|
700
|
-
{
|
|
701
|
-
type: "text",
|
|
702
|
-
text: `š Microsoft 365 Authentication Link:\n\n${authUrl}\n\nClick this link to authenticate with Microsoft 365.`
|
|
703
|
-
}
|
|
704
|
-
]
|
|
705
|
-
};
|
|
536
|
+
// ============ UNIFIED EMAIL MANAGEMENT TOOL ============
|
|
537
|
+
case "manage_email":
|
|
538
|
+
if (ms365Config.multiUser) {
|
|
539
|
+
const userId = args?.userId;
|
|
540
|
+
if (!userId) {
|
|
541
|
+
throw new Error("User ID is required in multi-user mode");
|
|
542
|
+
}
|
|
543
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
544
|
+
ms365Ops.setGraphClient(graphClient);
|
|
706
545
|
}
|
|
707
546
|
else {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
{
|
|
711
|
-
type: "text",
|
|
712
|
-
text: "ā
Already authenticated with Microsoft 365. Use force: true to get a new authentication link."
|
|
713
|
-
}
|
|
714
|
-
]
|
|
715
|
-
};
|
|
547
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
548
|
+
ms365Ops.setGraphClient(graphClient);
|
|
716
549
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
550
|
+
const emailAction = args?.action;
|
|
551
|
+
switch (emailAction) {
|
|
552
|
+
case "read":
|
|
553
|
+
if (!args?.messageId) {
|
|
554
|
+
throw new Error("messageId is required for read action");
|
|
555
|
+
}
|
|
556
|
+
const email = await ms365Ops.getEmail(args.messageId, args?.includeAttachments);
|
|
557
|
+
return {
|
|
558
|
+
content: [
|
|
559
|
+
{
|
|
560
|
+
type: "text",
|
|
561
|
+
text: `š§ Email Details\n\nš Subject: ${email.subject}\nš¤ From: ${email.from.name} <${email.from.address}>\nš
Date: ${email.receivedDateTime}\nš Attachments: ${email.attachments?.length || 0}\n\nš¬ Body:\n${email.body || email.bodyPreview}`
|
|
562
|
+
}
|
|
563
|
+
]
|
|
564
|
+
};
|
|
565
|
+
case "search":
|
|
566
|
+
const searchResults = await ms365Ops.searchEmails(args);
|
|
567
|
+
// Enhanced feedback for search results
|
|
568
|
+
let responseText = `š Email Search Results (${searchResults.messages.length} found)`;
|
|
569
|
+
if (searchResults.messages.length === 0) {
|
|
570
|
+
responseText = `š No emails found matching your criteria.\n\nš” Search Tips:\n`;
|
|
571
|
+
if (args?.from) {
|
|
572
|
+
responseText += `⢠Try partial names: "${args.from.split(' ')[0]}" or "${args.from.split(' ').pop()}"\n`;
|
|
573
|
+
responseText += `⢠Check spelling of sender name\n`;
|
|
725
574
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
return {
|
|
731
|
-
content: [
|
|
732
|
-
{
|
|
733
|
-
type: "text",
|
|
734
|
-
text: "ā
Already authenticated with Microsoft 365. Use force: true to re-authenticate."
|
|
575
|
+
if (args?.subject) {
|
|
576
|
+
responseText += `⢠Try broader subject terms\n`;
|
|
735
577
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
case "check_pending_auth":
|
|
740
|
-
const pendingDeviceCodeState = await enhancedMS365Auth.getPendingDeviceCodeInfo();
|
|
741
|
-
if (pendingDeviceCodeState) {
|
|
742
|
-
return {
|
|
743
|
-
content: [
|
|
744
|
-
{
|
|
745
|
-
type: "text",
|
|
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.`
|
|
578
|
+
if (args?.after || args?.before) {
|
|
579
|
+
responseText += `⢠Try expanding date range\n`;
|
|
747
580
|
}
|
|
748
|
-
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
return {
|
|
752
|
-
content: [
|
|
753
|
-
{
|
|
754
|
-
type: "text",
|
|
755
|
-
text: "ā¹ļø No pending device code authentication found. Use 'device_code_login' to start a new authentication process."
|
|
756
|
-
}
|
|
757
|
-
]
|
|
758
|
-
};
|
|
759
|
-
case "get_device_code":
|
|
760
|
-
const deviceCodeInfo = await enhancedMS365Auth.getDeviceCodeInfo();
|
|
761
|
-
return {
|
|
762
|
-
content: [
|
|
763
|
-
{
|
|
764
|
-
type: "text",
|
|
765
|
-
text: `š Microsoft 365 Device Code Authentication\n\nš± Visit: ${deviceCodeInfo.verificationUri}\nš Enter code: ${deviceCodeInfo.userCode}\n\nā³ This code will expire in 15 minutes.`
|
|
766
|
-
}
|
|
767
|
-
]
|
|
768
|
-
};
|
|
769
|
-
case "verify_authentication":
|
|
770
|
-
const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
|
|
771
|
-
const currentUser = await enhancedMS365Auth.getCurrentUser();
|
|
772
|
-
const storageInfo = enhancedMS365Auth.getStorageInfo();
|
|
773
|
-
const tokenInfo = await enhancedMS365Auth.getTokenExpirationInfo();
|
|
774
|
-
let statusText = `š 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}`;
|
|
775
|
-
if (isAuthenticated) {
|
|
776
|
-
statusText += `\nā° Token expires in: ${tokenInfo.expiresInMinutes} minutes`;
|
|
777
|
-
if (tokenInfo.needsRefresh) {
|
|
778
|
-
statusText += `\nā ļø Token will be refreshed automatically on next operation`;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
else {
|
|
782
|
-
statusText += `\nš” Use the "authenticate_with_device_code" tool to sign in`;
|
|
783
|
-
}
|
|
784
|
-
return {
|
|
785
|
-
content: [
|
|
786
|
-
{
|
|
787
|
-
type: "text",
|
|
788
|
-
text: statusText
|
|
789
|
-
}
|
|
790
|
-
]
|
|
791
|
-
};
|
|
792
|
-
case "logout":
|
|
793
|
-
const accountKey = args?.accountKey;
|
|
794
|
-
const wasAuthenticated = await enhancedMS365Auth.isAuthenticated();
|
|
795
|
-
await enhancedMS365Auth.resetAuth();
|
|
796
|
-
return {
|
|
797
|
-
content: [
|
|
798
|
-
{
|
|
799
|
-
type: "text",
|
|
800
|
-
text: wasAuthenticated ?
|
|
801
|
-
`ā
Successfully logged out from Microsoft 365.` :
|
|
802
|
-
`ā¹ļø No active authentication found.`
|
|
581
|
+
responseText += `⢠Remove some search criteria to get broader results`;
|
|
803
582
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
throw new Error("Multi-user mode not enabled. Start server with --multi-user flag.");
|
|
810
|
-
}
|
|
811
|
-
const authResult = await multiUserMS365Auth.authenticateNewUser(args?.userEmail || undefined);
|
|
812
|
-
return {
|
|
813
|
-
content: [
|
|
814
|
-
{
|
|
815
|
-
type: "text",
|
|
816
|
-
text: `š Authentication started for user: ${authResult.userId}\n\nš± Visit this URL to authenticate:\n${authResult.authUrl}\n\nā ļø Server running on port ${authResult.port}`
|
|
817
|
-
}
|
|
818
|
-
]
|
|
819
|
-
};
|
|
820
|
-
case "remove_my_session":
|
|
821
|
-
if (!ms365Config.multiUser) {
|
|
822
|
-
throw new Error("Multi-user mode not enabled. Start server with --multi-user flag.");
|
|
823
|
-
}
|
|
824
|
-
if (!args?.userId) {
|
|
825
|
-
throw new Error("User ID is required");
|
|
826
|
-
}
|
|
827
|
-
const removed = multiUserMS365Auth.removeUser(args.userId);
|
|
828
|
-
return {
|
|
829
|
-
content: [
|
|
830
|
-
{
|
|
831
|
-
type: "text",
|
|
832
|
-
text: removed ? `ā
Session removed for user: ${args.userId}` : `ā Session not found for user: ${args.userId}`
|
|
583
|
+
else {
|
|
584
|
+
responseText += `\n\n${searchResults.messages.map((email, index) => `${index + 1}. š§ ${email.subject}\n š¤ From: ${email.from.name} <${email.from.address}>\n š
${new Date(email.receivedDateTime).toLocaleDateString()}\n ${email.isRead ? 'š Read' : 'š© Unread'}\n š ID: ${email.id}\n`).join('\n')}`;
|
|
585
|
+
if (searchResults.hasMore) {
|
|
586
|
+
responseText += `\nš” There are more results available. Use maxResults parameter to get more emails.`;
|
|
587
|
+
}
|
|
833
588
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
589
|
+
return {
|
|
590
|
+
content: [
|
|
591
|
+
{
|
|
592
|
+
type: "text",
|
|
593
|
+
text: responseText
|
|
594
|
+
}
|
|
595
|
+
]
|
|
596
|
+
};
|
|
597
|
+
case "search_to_me":
|
|
598
|
+
const searchToMeResults = await ms365Ops.searchEmailsToMe(args);
|
|
599
|
+
return {
|
|
600
|
+
content: [
|
|
601
|
+
{
|
|
602
|
+
type: "text",
|
|
603
|
+
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')}`
|
|
604
|
+
}
|
|
605
|
+
]
|
|
606
|
+
};
|
|
607
|
+
case "list":
|
|
608
|
+
const emailList = await ms365Ops.listEmails(args?.folderId, args?.maxResults);
|
|
609
|
+
return {
|
|
610
|
+
content: [
|
|
611
|
+
{
|
|
612
|
+
type: "text",
|
|
613
|
+
text: `š¬ Email List (${emailList.messages.length} emails)\n\n${emailList.messages.map((email, index) => `${index + 1}. š§ ${email.subject}\n š¤ From: ${email.from.name} <${email.from.address}>\n š
${new Date(email.receivedDateTime).toLocaleDateString()}\n ${email.isRead ? 'š' : 'š©'} ${email.isRead ? 'Read' : 'Unread'}\n š ID: ${email.id}\n`).join('\n')}`
|
|
614
|
+
}
|
|
615
|
+
]
|
|
616
|
+
};
|
|
617
|
+
case "mark":
|
|
618
|
+
if (!args?.messageId || args?.isRead === undefined) {
|
|
619
|
+
throw new Error("messageId and isRead are required for mark action");
|
|
856
620
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
870
|
-
ms365Ops.setGraphClient(graphClient);
|
|
871
|
-
}
|
|
872
|
-
const email = await ms365Ops.getEmail(args?.messageId, args?.includeAttachments);
|
|
873
|
-
return {
|
|
874
|
-
content: [
|
|
875
|
-
{
|
|
876
|
-
type: "text",
|
|
877
|
-
text: `š§ Email Details\n\nš Subject: ${email.subject}\nš¤ From: ${email.from.name} <${email.from.address}>\nš
Date: ${email.receivedDateTime}\nš Attachments: ${email.attachments?.length || 0}\n\nš¬ Body:\n${email.body || email.bodyPreview}`
|
|
621
|
+
await ms365Ops.markEmail(args.messageId, args.isRead);
|
|
622
|
+
return {
|
|
623
|
+
content: [
|
|
624
|
+
{
|
|
625
|
+
type: "text",
|
|
626
|
+
text: `ā
Email marked as ${args.isRead ? 'read' : 'unread'}\nš Message ID: ${args.messageId}`
|
|
627
|
+
}
|
|
628
|
+
]
|
|
629
|
+
};
|
|
630
|
+
case "move":
|
|
631
|
+
if (!args?.messageId || !args?.destinationFolderId) {
|
|
632
|
+
throw new Error("messageId and destinationFolderId are required for move action");
|
|
878
633
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
892
|
-
ms365Ops.setGraphClient(graphClient);
|
|
893
|
-
}
|
|
894
|
-
const searchResults = await ms365Ops.searchEmails(args);
|
|
895
|
-
// Enhanced feedback for search results
|
|
896
|
-
let responseText = `š Email Search Results (${searchResults.messages.length} found)`;
|
|
897
|
-
if (searchResults.messages.length === 0) {
|
|
898
|
-
responseText = `š No emails found matching your criteria.\n\nš” Search Tips:\n`;
|
|
899
|
-
if (args?.from) {
|
|
900
|
-
responseText += `⢠Try partial names: "${args.from.split(' ')[0]}" or "${args.from.split(' ').pop()}"\n`;
|
|
901
|
-
responseText += `⢠Check spelling of sender name\n`;
|
|
902
|
-
}
|
|
903
|
-
if (args?.subject) {
|
|
904
|
-
responseText += `⢠Try broader subject terms\n`;
|
|
905
|
-
}
|
|
906
|
-
if (args?.after || args?.before) {
|
|
907
|
-
responseText += `⢠Try expanding date range\n`;
|
|
908
|
-
}
|
|
909
|
-
responseText += `⢠Remove some search criteria to get broader results`;
|
|
910
|
-
}
|
|
911
|
-
else {
|
|
912
|
-
responseText += `\n\n${searchResults.messages.map((email, index) => `${index + 1}. š§ ${email.subject}\n š¤ From: ${email.from.name} <${email.from.address}>\n š
${new Date(email.receivedDateTime).toLocaleDateString()}\n ${email.isRead ? 'š Read' : 'š© Unread'}\n š ID: ${email.id}\n`).join('\n')}`;
|
|
913
|
-
if (searchResults.hasMore) {
|
|
914
|
-
responseText += `\nš” There are more results available. Use maxResults parameter to get more emails.`;
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
return {
|
|
918
|
-
content: [
|
|
919
|
-
{
|
|
920
|
-
type: "text",
|
|
921
|
-
text: responseText
|
|
634
|
+
await ms365Ops.moveEmail(args.messageId, args.destinationFolderId);
|
|
635
|
+
return {
|
|
636
|
+
content: [
|
|
637
|
+
{
|
|
638
|
+
type: "text",
|
|
639
|
+
text: `ā
Email moved to folder: ${args.destinationFolderId}\nš Message ID: ${args.messageId}`
|
|
640
|
+
}
|
|
641
|
+
]
|
|
642
|
+
};
|
|
643
|
+
case "delete":
|
|
644
|
+
if (!args?.messageId) {
|
|
645
|
+
throw new Error("messageId is required for delete action");
|
|
922
646
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
else {
|
|
935
|
-
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
936
|
-
ms365Ops.setGraphClient(graphClient);
|
|
647
|
+
await ms365Ops.deleteEmail(args.messageId);
|
|
648
|
+
return {
|
|
649
|
+
content: [
|
|
650
|
+
{
|
|
651
|
+
type: "text",
|
|
652
|
+
text: `ā
Email deleted permanently\nš Message ID: ${args.messageId}`
|
|
653
|
+
}
|
|
654
|
+
]
|
|
655
|
+
};
|
|
656
|
+
default:
|
|
657
|
+
throw new Error(`Unknown email action: ${emailAction}`);
|
|
937
658
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
content: [
|
|
941
|
-
{
|
|
942
|
-
type: "text",
|
|
943
|
-
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')}`
|
|
944
|
-
}
|
|
945
|
-
]
|
|
946
|
-
};
|
|
947
|
-
case "mark_email":
|
|
659
|
+
// ============ UNIFIED CONTACT MANAGEMENT TOOL ============
|
|
660
|
+
case "manage_contacts":
|
|
948
661
|
if (ms365Config.multiUser) {
|
|
949
662
|
const userId = args?.userId;
|
|
950
663
|
if (!userId) {
|
|
@@ -957,38 +670,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
957
670
|
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
958
671
|
ms365Ops.setGraphClient(graphClient);
|
|
959
672
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
673
|
+
const contactAction = args?.action || 'list';
|
|
674
|
+
switch (contactAction) {
|
|
675
|
+
case "list":
|
|
676
|
+
const contacts = await ms365Ops.getContacts(args?.maxResults || 100);
|
|
677
|
+
return {
|
|
678
|
+
content: [
|
|
679
|
+
{
|
|
680
|
+
type: "text",
|
|
681
|
+
text: `š„ Contacts (${contacts.length} found)\n\n${contacts.map((contact) => `š¤ ${contact.displayName}\n š§ ${contact.emailAddresses?.[0]?.address || 'No email'}\n š ${contact.businessPhones?.[0] || 'No phone'}\n`).join('\n')}`
|
|
682
|
+
}
|
|
683
|
+
]
|
|
684
|
+
};
|
|
685
|
+
case "search":
|
|
686
|
+
if (!args?.query) {
|
|
687
|
+
throw new Error("query is required for search action");
|
|
966
688
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
else {
|
|
979
|
-
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
980
|
-
ms365Ops.setGraphClient(graphClient);
|
|
689
|
+
const searchContactResults = await ms365Ops.searchContacts(args.query, args?.maxResults || 50);
|
|
690
|
+
return {
|
|
691
|
+
content: [
|
|
692
|
+
{
|
|
693
|
+
type: "text",
|
|
694
|
+
text: `š Contact Search Results (${searchContactResults.length} found)\n\n${searchContactResults.map((contact) => `š¤ ${contact.displayName}\n š§ ${contact.emailAddresses?.[0]?.address || 'No email'}\n š ${contact.businessPhones?.[0] || 'No phone'}\n`).join('\n')}`
|
|
695
|
+
}
|
|
696
|
+
]
|
|
697
|
+
};
|
|
698
|
+
default:
|
|
699
|
+
throw new Error(`Unknown contact action: ${contactAction}`);
|
|
981
700
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
content: [
|
|
985
|
-
{
|
|
986
|
-
type: "text",
|
|
987
|
-
text: `ā
Email moved to folder: ${args?.destinationFolderId}\nš Message ID: ${args?.messageId}`
|
|
988
|
-
}
|
|
989
|
-
]
|
|
990
|
-
};
|
|
991
|
-
case "delete_email":
|
|
701
|
+
// ============ REMAINING ORIGINAL TOOLS ============
|
|
702
|
+
case "send_email":
|
|
992
703
|
if (ms365Config.multiUser) {
|
|
993
704
|
const userId = args?.userId;
|
|
994
705
|
if (!userId) {
|
|
@@ -1001,12 +712,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1001
712
|
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1002
713
|
ms365Ops.setGraphClient(graphClient);
|
|
1003
714
|
}
|
|
1004
|
-
await ms365Ops.
|
|
715
|
+
const emailResult = await ms365Ops.sendEmail(args);
|
|
1005
716
|
return {
|
|
1006
717
|
content: [
|
|
1007
718
|
{
|
|
1008
719
|
type: "text",
|
|
1009
|
-
text: `ā
Email
|
|
720
|
+
text: `ā
Email sent successfully!\n\nš§ To: ${Array.isArray(args?.to) ? args.to.join(', ') : args?.to}\nš Subject: ${args?.subject}\nš Message ID: ${emailResult.id}`
|
|
1010
721
|
}
|
|
1011
722
|
]
|
|
1012
723
|
};
|
|
@@ -1054,69 +765,33 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1054
765
|
}
|
|
1055
766
|
]
|
|
1056
767
|
};
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
throw new Error("User ID is required in multi-user mode");
|
|
1062
|
-
}
|
|
1063
|
-
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1064
|
-
ms365Ops.setGraphClient(graphClient);
|
|
1065
|
-
}
|
|
1066
|
-
else {
|
|
1067
|
-
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1068
|
-
ms365Ops.setGraphClient(graphClient);
|
|
1069
|
-
}
|
|
1070
|
-
const contacts = await ms365Ops.getContacts(args?.maxResults);
|
|
1071
|
-
return {
|
|
1072
|
-
content: [
|
|
1073
|
-
{
|
|
1074
|
-
type: "text",
|
|
1075
|
-
text: `š„ Contacts (${contacts.length} found)\n\n${contacts.map((contact) => `š¤ ${contact.displayName}\n š§ ${contact.emailAddresses?.[0]?.address || 'No email'}\n š ${contact.businessPhones?.[0] || 'No phone'}\n`).join('\n')}`
|
|
1076
|
-
}
|
|
1077
|
-
]
|
|
1078
|
-
};
|
|
1079
|
-
case "search_contacts":
|
|
1080
|
-
if (ms365Config.multiUser) {
|
|
1081
|
-
const userId = args?.userId;
|
|
1082
|
-
if (!userId) {
|
|
1083
|
-
throw new Error("User ID is required in multi-user mode");
|
|
1084
|
-
}
|
|
1085
|
-
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1086
|
-
ms365Ops.setGraphClient(graphClient);
|
|
1087
|
-
}
|
|
1088
|
-
else {
|
|
1089
|
-
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1090
|
-
ms365Ops.setGraphClient(graphClient);
|
|
768
|
+
// ============ MULTI-USER AUTHENTICATION TOOLS ============
|
|
769
|
+
case "authenticate_user":
|
|
770
|
+
if (!ms365Config.multiUser) {
|
|
771
|
+
throw new Error("Multi-user mode not enabled. Start server with --multi-user flag.");
|
|
1091
772
|
}
|
|
1092
|
-
const
|
|
773
|
+
const authResult = await multiUserMS365Auth.authenticateNewUser(args?.userEmail || undefined);
|
|
1093
774
|
return {
|
|
1094
775
|
content: [
|
|
1095
776
|
{
|
|
1096
777
|
type: "text",
|
|
1097
|
-
text:
|
|
778
|
+
text: `š Authentication started for user: ${authResult.userId}\n\nš± Visit this URL to authenticate:\n${authResult.authUrl}\n\nā ļø Server running on port ${authResult.port}`
|
|
1098
779
|
}
|
|
1099
780
|
]
|
|
1100
781
|
};
|
|
1101
|
-
case "
|
|
1102
|
-
if (ms365Config.multiUser) {
|
|
1103
|
-
|
|
1104
|
-
if (!userId) {
|
|
1105
|
-
throw new Error("User ID is required in multi-user mode");
|
|
1106
|
-
}
|
|
1107
|
-
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1108
|
-
ms365Ops.setGraphClient(graphClient);
|
|
782
|
+
case "remove_my_session":
|
|
783
|
+
if (!ms365Config.multiUser) {
|
|
784
|
+
throw new Error("Multi-user mode not enabled. Start server with --multi-user flag.");
|
|
1109
785
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
ms365Ops.setGraphClient(graphClient);
|
|
786
|
+
if (!args?.userId) {
|
|
787
|
+
throw new Error("User ID is required");
|
|
1113
788
|
}
|
|
1114
|
-
const
|
|
789
|
+
const removed = multiUserMS365Auth.removeUser(args.userId);
|
|
1115
790
|
return {
|
|
1116
791
|
content: [
|
|
1117
792
|
{
|
|
1118
793
|
type: "text",
|
|
1119
|
-
text:
|
|
794
|
+
text: removed ? `ā
Session removed for user: ${args.userId}` : `ā Session not found for user: ${args.userId}`
|
|
1120
795
|
}
|
|
1121
796
|
]
|
|
1122
797
|
};
|