ms365-mcp-server 1.0.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/LICENSE +21 -0
- package/README.md +497 -0
- package/bin/cli.js +247 -0
- package/dist/index.js +1251 -0
- package/dist/utils/api.js +43 -0
- package/dist/utils/credential-store.js +258 -0
- package/dist/utils/ms365-auth-enhanced.js +639 -0
- package/dist/utils/ms365-auth.js +363 -0
- package/dist/utils/ms365-operations.js +644 -0
- package/dist/utils/multi-user-auth.js +359 -0
- package/install.js +41 -0
- package/package.json +59 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft 365 MCP Server
|
|
3
|
+
* A comprehensive server implementation using Model Context Protocol for Microsoft 365 API
|
|
4
|
+
*/
|
|
5
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { logger } from './utils/api.js';
|
|
9
|
+
import { MS365Operations } from './utils/ms365-operations.js';
|
|
10
|
+
import { multiUserMS365Auth } from './utils/multi-user-auth.js';
|
|
11
|
+
import { enhancedMS365Auth } from './utils/ms365-auth-enhanced.js';
|
|
12
|
+
// Create singleton MS365Operations instance
|
|
13
|
+
const ms365Ops = new MS365Operations();
|
|
14
|
+
let ms365Config;
|
|
15
|
+
function parseArgs() {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const config = {
|
|
18
|
+
setupAuth: false,
|
|
19
|
+
resetAuth: false,
|
|
20
|
+
debug: false,
|
|
21
|
+
nonInteractive: false,
|
|
22
|
+
multiUser: false,
|
|
23
|
+
login: false,
|
|
24
|
+
logout: false,
|
|
25
|
+
verifyLogin: false
|
|
26
|
+
};
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
const arg = args[i];
|
|
29
|
+
if (arg === '--setup-auth') {
|
|
30
|
+
config.setupAuth = true;
|
|
31
|
+
}
|
|
32
|
+
else if (arg === '--reset-auth') {
|
|
33
|
+
config.resetAuth = true;
|
|
34
|
+
}
|
|
35
|
+
else if (arg === '--debug' || arg === '-d') {
|
|
36
|
+
config.debug = true;
|
|
37
|
+
}
|
|
38
|
+
else if (arg === '--non-interactive' || arg === '-n') {
|
|
39
|
+
config.nonInteractive = true;
|
|
40
|
+
}
|
|
41
|
+
else if (arg === '--multi-user') {
|
|
42
|
+
config.multiUser = true;
|
|
43
|
+
}
|
|
44
|
+
else if (arg === '--login') {
|
|
45
|
+
config.login = true;
|
|
46
|
+
}
|
|
47
|
+
else if (arg === '--logout') {
|
|
48
|
+
config.logout = true;
|
|
49
|
+
}
|
|
50
|
+
else if (arg === '--verify-login') {
|
|
51
|
+
config.verifyLogin = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return config;
|
|
55
|
+
}
|
|
56
|
+
const server = new Server({
|
|
57
|
+
name: "ms365-mcp-server",
|
|
58
|
+
version: "1.0.0"
|
|
59
|
+
}, {
|
|
60
|
+
capabilities: {
|
|
61
|
+
resources: {
|
|
62
|
+
read: true,
|
|
63
|
+
list: true
|
|
64
|
+
},
|
|
65
|
+
tools: {
|
|
66
|
+
list: true,
|
|
67
|
+
call: true
|
|
68
|
+
},
|
|
69
|
+
prompts: {
|
|
70
|
+
list: true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// Set up the resource listing request handler
|
|
75
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
76
|
+
logger.log('Received list resources request');
|
|
77
|
+
return { resources: [] };
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Handler for reading resource information.
|
|
81
|
+
*/
|
|
82
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
83
|
+
logger.log('Received read resource request: ' + JSON.stringify(request));
|
|
84
|
+
throw new Error("Resource reading not implemented");
|
|
85
|
+
});
|
|
86
|
+
/**
|
|
87
|
+
* Handler for listing available prompts.
|
|
88
|
+
*/
|
|
89
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
90
|
+
logger.log('Received list prompts request');
|
|
91
|
+
return { prompts: [] };
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* List available tools for interacting with Microsoft 365.
|
|
95
|
+
*/
|
|
96
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
97
|
+
const baseTools = [
|
|
98
|
+
{
|
|
99
|
+
name: "send_email",
|
|
100
|
+
description: "Send an email message with support for HTML content, attachments, and international characters. Supports both plain text and HTML emails with proper formatting.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
userId: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
107
|
+
},
|
|
108
|
+
to: {
|
|
109
|
+
type: "array",
|
|
110
|
+
items: { type: "string" },
|
|
111
|
+
description: "List of recipient email addresses"
|
|
112
|
+
},
|
|
113
|
+
cc: {
|
|
114
|
+
type: "array",
|
|
115
|
+
items: { type: "string" },
|
|
116
|
+
description: "List of CC recipient email addresses (optional)"
|
|
117
|
+
},
|
|
118
|
+
bcc: {
|
|
119
|
+
type: "array",
|
|
120
|
+
items: { type: "string" },
|
|
121
|
+
description: "List of BCC recipient email addresses (optional)"
|
|
122
|
+
},
|
|
123
|
+
subject: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "Email subject line with full support for international characters"
|
|
126
|
+
},
|
|
127
|
+
body: {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "Email content (text or HTML based on bodyType)"
|
|
130
|
+
},
|
|
131
|
+
bodyType: {
|
|
132
|
+
type: "string",
|
|
133
|
+
enum: ["text", "html"],
|
|
134
|
+
description: "Content type of the email body (default: text)",
|
|
135
|
+
default: "text"
|
|
136
|
+
},
|
|
137
|
+
replyTo: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "Reply-to email address (optional)"
|
|
140
|
+
},
|
|
141
|
+
importance: {
|
|
142
|
+
type: "string",
|
|
143
|
+
enum: ["low", "normal", "high"],
|
|
144
|
+
description: "Email importance level (default: normal)",
|
|
145
|
+
default: "normal"
|
|
146
|
+
},
|
|
147
|
+
attachments: {
|
|
148
|
+
type: "array",
|
|
149
|
+
items: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
name: {
|
|
153
|
+
type: "string",
|
|
154
|
+
description: "Name of the attachment file"
|
|
155
|
+
},
|
|
156
|
+
contentBytes: {
|
|
157
|
+
type: "string",
|
|
158
|
+
description: "Base64 encoded content of the attachment"
|
|
159
|
+
},
|
|
160
|
+
contentType: {
|
|
161
|
+
type: "string",
|
|
162
|
+
description: "MIME type of the attachment (optional, auto-detected if not provided)"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
required: ["name", "contentBytes"]
|
|
166
|
+
},
|
|
167
|
+
description: "List of email attachments (optional)"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
required: ["to", "subject"],
|
|
171
|
+
additionalProperties: false
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "read_email",
|
|
176
|
+
description: "Read email message by ID with advanced content extraction. Returns full email content including headers, body, and attachment information.",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
userId: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
183
|
+
},
|
|
184
|
+
messageId: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description: "Microsoft 365 message ID to retrieve"
|
|
187
|
+
},
|
|
188
|
+
includeAttachments: {
|
|
189
|
+
type: "boolean",
|
|
190
|
+
description: "Whether to include attachment information and content",
|
|
191
|
+
default: false
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
required: ["messageId"],
|
|
195
|
+
additionalProperties: false
|
|
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. 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: {
|
|
205
|
+
type: "string",
|
|
206
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
207
|
+
},
|
|
208
|
+
query: {
|
|
209
|
+
type: "string",
|
|
210
|
+
description: "General search query using natural language or specific terms"
|
|
211
|
+
},
|
|
212
|
+
from: {
|
|
213
|
+
type: "string",
|
|
214
|
+
description: "Search for emails from specific sender"
|
|
215
|
+
},
|
|
216
|
+
to: {
|
|
217
|
+
type: "string",
|
|
218
|
+
description: "Search for emails to specific recipient (useful for finding emails addressed to you)"
|
|
219
|
+
},
|
|
220
|
+
cc: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Search for emails with specific CC recipient (useful for finding emails where someone was CC'd)"
|
|
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: "list_emails",
|
|
266
|
+
description: "List emails in inbox, sent, or custom folder. Returns basic email information including subjects, senders, and snippets with support for folder navigation.",
|
|
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
|
+
folderId: {
|
|
275
|
+
type: "string",
|
|
276
|
+
description: "Folder ID or name (default: inbox). Common values: inbox, sentitems, drafts, deleteditems",
|
|
277
|
+
default: "inbox"
|
|
278
|
+
},
|
|
279
|
+
maxResults: {
|
|
280
|
+
type: "number",
|
|
281
|
+
description: "Maximum number of emails to retrieve (default: 50, max: 200)",
|
|
282
|
+
minimum: 1,
|
|
283
|
+
maximum: 200,
|
|
284
|
+
default: 50
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
additionalProperties: false
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "mark_email",
|
|
292
|
+
description: "Mark email as read or unread. Useful for managing email status and organizing your inbox.",
|
|
293
|
+
inputSchema: {
|
|
294
|
+
type: "object",
|
|
295
|
+
properties: {
|
|
296
|
+
userId: {
|
|
297
|
+
type: "string",
|
|
298
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
299
|
+
},
|
|
300
|
+
messageId: {
|
|
301
|
+
type: "string",
|
|
302
|
+
description: "Microsoft 365 message ID to mark"
|
|
303
|
+
},
|
|
304
|
+
isRead: {
|
|
305
|
+
type: "boolean",
|
|
306
|
+
description: "True to mark as read, false to mark as unread"
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
required: ["messageId", "isRead"],
|
|
310
|
+
additionalProperties: false
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "move_email",
|
|
315
|
+
description: "Move email to different folder. Supports moving emails between various Outlook folders for organization.",
|
|
316
|
+
inputSchema: {
|
|
317
|
+
type: "object",
|
|
318
|
+
properties: {
|
|
319
|
+
userId: {
|
|
320
|
+
type: "string",
|
|
321
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
322
|
+
},
|
|
323
|
+
messageId: {
|
|
324
|
+
type: "string",
|
|
325
|
+
description: "Microsoft 365 message ID to move"
|
|
326
|
+
},
|
|
327
|
+
destinationFolderId: {
|
|
328
|
+
type: "string",
|
|
329
|
+
description: "Destination folder ID or name (e.g., 'archive', 'drafts', 'deleteditems')"
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
required: ["messageId", "destinationFolderId"],
|
|
333
|
+
additionalProperties: false
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "delete_email",
|
|
338
|
+
description: "Permanently delete an email message. Use with caution as this action cannot be undone.",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
userId: {
|
|
343
|
+
type: "string",
|
|
344
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
345
|
+
},
|
|
346
|
+
messageId: {
|
|
347
|
+
type: "string",
|
|
348
|
+
description: "Microsoft 365 message ID to delete"
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
required: ["messageId"],
|
|
352
|
+
additionalProperties: false
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "get_attachment",
|
|
357
|
+
description: "Download and retrieve attachment information from an email. Returns attachment data, filename, and metadata.",
|
|
358
|
+
inputSchema: {
|
|
359
|
+
type: "object",
|
|
360
|
+
properties: {
|
|
361
|
+
userId: {
|
|
362
|
+
type: "string",
|
|
363
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
364
|
+
},
|
|
365
|
+
messageId: {
|
|
366
|
+
type: "string",
|
|
367
|
+
description: "Microsoft 365 message ID containing the attachment"
|
|
368
|
+
},
|
|
369
|
+
attachmentId: {
|
|
370
|
+
type: "string",
|
|
371
|
+
description: "Attachment ID to download"
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
required: ["messageId", "attachmentId"],
|
|
375
|
+
additionalProperties: false
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "list_folders",
|
|
380
|
+
description: "List all mail folders in the mailbox. Returns folder names, IDs, and item counts for navigation and organization.",
|
|
381
|
+
inputSchema: {
|
|
382
|
+
type: "object",
|
|
383
|
+
properties: {
|
|
384
|
+
userId: {
|
|
385
|
+
type: "string",
|
|
386
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
additionalProperties: false
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: "get_contacts",
|
|
394
|
+
description: "Retrieve contact list from Microsoft 365. Returns contact information including names, email addresses, and phone numbers.",
|
|
395
|
+
inputSchema: {
|
|
396
|
+
type: "object",
|
|
397
|
+
properties: {
|
|
398
|
+
userId: {
|
|
399
|
+
type: "string",
|
|
400
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
401
|
+
},
|
|
402
|
+
maxResults: {
|
|
403
|
+
type: "number",
|
|
404
|
+
description: "Maximum number of contacts to retrieve (default: 100, max: 500)",
|
|
405
|
+
minimum: 1,
|
|
406
|
+
maximum: 500,
|
|
407
|
+
default: 100
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
additionalProperties: false
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: "search_contacts",
|
|
415
|
+
description: "Search contacts by name or email address. Useful for finding specific contacts for email addressing.",
|
|
416
|
+
inputSchema: {
|
|
417
|
+
type: "object",
|
|
418
|
+
properties: {
|
|
419
|
+
userId: {
|
|
420
|
+
type: "string",
|
|
421
|
+
description: "User ID for multi-user authentication (required if using multi-user mode)"
|
|
422
|
+
},
|
|
423
|
+
query: {
|
|
424
|
+
type: "string",
|
|
425
|
+
description: "Search query for contact name or email address"
|
|
426
|
+
},
|
|
427
|
+
maxResults: {
|
|
428
|
+
type: "number",
|
|
429
|
+
description: "Maximum number of contacts to return (default: 50, max: 200)",
|
|
430
|
+
minimum: 1,
|
|
431
|
+
maximum: 200,
|
|
432
|
+
default: 50
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
required: ["query"],
|
|
436
|
+
additionalProperties: false
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
];
|
|
440
|
+
// Add multi-user specific tools
|
|
441
|
+
const multiUserTools = [
|
|
442
|
+
{
|
|
443
|
+
name: "authenticate_user",
|
|
444
|
+
description: "Start authentication flow for a new user. Returns authentication URL that user needs to visit for Microsoft 365 access.",
|
|
445
|
+
inputSchema: {
|
|
446
|
+
type: "object",
|
|
447
|
+
properties: {
|
|
448
|
+
userEmail: {
|
|
449
|
+
type: "string",
|
|
450
|
+
description: "User's email address (optional, for identification)"
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
additionalProperties: false
|
|
454
|
+
}
|
|
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
|
+
{
|
|
472
|
+
name: "remove_my_session",
|
|
473
|
+
description: "Remove your own authentication session (user-specific, secure).",
|
|
474
|
+
inputSchema: {
|
|
475
|
+
type: "object",
|
|
476
|
+
properties: {
|
|
477
|
+
userId: {
|
|
478
|
+
type: "string",
|
|
479
|
+
description: "Your User ID to remove"
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
required: ["userId"],
|
|
483
|
+
additionalProperties: false
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
];
|
|
487
|
+
// Add one-time authentication tool for single-user mode
|
|
488
|
+
const oneTimeAuthTools = [
|
|
489
|
+
{
|
|
490
|
+
name: "quick_authenticate",
|
|
491
|
+
description: "One-time Microsoft 365 authentication for immediate access. Opens browser automatically and completes authentication flow.",
|
|
492
|
+
inputSchema: {
|
|
493
|
+
type: "object",
|
|
494
|
+
properties: {
|
|
495
|
+
force: {
|
|
496
|
+
type: "boolean",
|
|
497
|
+
description: "Force re-authentication even if already authenticated (default: false)",
|
|
498
|
+
default: false
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
additionalProperties: false
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
name: "get_auth_link",
|
|
506
|
+
description: "Get a clickable Microsoft 365 authentication link without auto-opening browser. Perfect for manual authentication control.",
|
|
507
|
+
inputSchema: {
|
|
508
|
+
type: "object",
|
|
509
|
+
properties: {
|
|
510
|
+
force: {
|
|
511
|
+
type: "boolean",
|
|
512
|
+
description: "Force new authentication link even if already authenticated (default: false)",
|
|
513
|
+
default: false
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
additionalProperties: false
|
|
517
|
+
}
|
|
518
|
+
},
|
|
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
|
+
{
|
|
532
|
+
name: "device_code_login",
|
|
533
|
+
description: "Start device code authentication flow and get the URL/code to enter. Shows authentication details immediately in the UI.",
|
|
534
|
+
inputSchema: {
|
|
535
|
+
type: "object",
|
|
536
|
+
properties: {
|
|
537
|
+
force: {
|
|
538
|
+
type: "boolean",
|
|
539
|
+
description: "Force new authentication even if already authenticated (default: false)",
|
|
540
|
+
default: false
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
additionalProperties: false
|
|
544
|
+
}
|
|
545
|
+
},
|
|
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
|
+
{
|
|
556
|
+
name: "check_pending_auth",
|
|
557
|
+
description: "Check if there's a pending device code authentication and get the URL/code again if needed.",
|
|
558
|
+
inputSchema: {
|
|
559
|
+
type: "object",
|
|
560
|
+
properties: {},
|
|
561
|
+
additionalProperties: false
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: "get_device_code",
|
|
566
|
+
description: "Get device code information for manual authentication without starting the full flow. Returns URL and code to enter.",
|
|
567
|
+
inputSchema: {
|
|
568
|
+
type: "object",
|
|
569
|
+
properties: {},
|
|
570
|
+
additionalProperties: false
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "verify_authentication",
|
|
575
|
+
description: "Verify current authentication status and show account information. Useful for checking if authentication is still valid.",
|
|
576
|
+
inputSchema: {
|
|
577
|
+
type: "object",
|
|
578
|
+
properties: {},
|
|
579
|
+
additionalProperties: false
|
|
580
|
+
}
|
|
581
|
+
},
|
|
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
|
+
{
|
|
601
|
+
name: "logout",
|
|
602
|
+
description: "Log out and clear stored authentication tokens for the current user. This will require re-authentication.",
|
|
603
|
+
inputSchema: {
|
|
604
|
+
type: "object",
|
|
605
|
+
properties: {
|
|
606
|
+
accountKey: {
|
|
607
|
+
type: "string",
|
|
608
|
+
description: "Account key to logout (default: current user)",
|
|
609
|
+
default: "default-user"
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
additionalProperties: false
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
];
|
|
616
|
+
if (ms365Config.multiUser) {
|
|
617
|
+
return { tools: [...baseTools, ...multiUserTools] };
|
|
618
|
+
}
|
|
619
|
+
// For single-user mode, include enhanced authentication tools
|
|
620
|
+
// Check if user is authenticated using enhanced auth system
|
|
621
|
+
const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
|
|
622
|
+
if (!isAuthenticated) {
|
|
623
|
+
return { tools: [...baseTools, ...oneTimeAuthTools, ...enhancedAuthTools] };
|
|
624
|
+
}
|
|
625
|
+
// If authenticated, still provide auth management tools
|
|
626
|
+
return { tools: [...baseTools, ...enhancedAuthTools] };
|
|
627
|
+
});
|
|
628
|
+
/**
|
|
629
|
+
* Handle tool execution requests
|
|
630
|
+
*/
|
|
631
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
632
|
+
const { name, arguments: args } = request.params;
|
|
633
|
+
try {
|
|
634
|
+
switch (name) {
|
|
635
|
+
// ============ 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
|
|
641
|
+
return {
|
|
642
|
+
content: [
|
|
643
|
+
{
|
|
644
|
+
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.`
|
|
646
|
+
}
|
|
647
|
+
]
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
return {
|
|
652
|
+
content: [
|
|
653
|
+
{
|
|
654
|
+
type: "text",
|
|
655
|
+
text: "ā
Already authenticated with Microsoft 365. Use force: true to re-authenticate."
|
|
656
|
+
}
|
|
657
|
+
]
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
case "get_auth_link":
|
|
661
|
+
if (!await enhancedMS365Auth.isAuthenticated() || args?.force) {
|
|
662
|
+
const authUrl = await enhancedMS365Auth.getAuthUrl();
|
|
663
|
+
return {
|
|
664
|
+
content: [
|
|
665
|
+
{
|
|
666
|
+
type: "text",
|
|
667
|
+
text: `š Microsoft 365 Authentication Link:\n\n${authUrl}\n\nClick this link to authenticate with Microsoft 365.`
|
|
668
|
+
}
|
|
669
|
+
]
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
return {
|
|
674
|
+
content: [
|
|
675
|
+
{
|
|
676
|
+
type: "text",
|
|
677
|
+
text: "ā
Already authenticated with Microsoft 365. Use force: true to get a new authentication link."
|
|
678
|
+
}
|
|
679
|
+
]
|
|
680
|
+
};
|
|
681
|
+
}
|
|
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
|
+
case "device_code_login":
|
|
717
|
+
if (!await enhancedMS365Auth.isAuthenticated() || args?.force) {
|
|
718
|
+
const deviceCodeInfo = await enhancedMS365Auth.startDeviceCodeAuth();
|
|
719
|
+
return {
|
|
720
|
+
content: [
|
|
721
|
+
{
|
|
722
|
+
type: "text",
|
|
723
|
+
text: `š Microsoft 365 Device Code Authentication Started!\n\nš± Visit: ${deviceCodeInfo.verificationUri}\nš Enter this code: ${deviceCodeInfo.userCode}\n\nā³ After entering the code, use the "complete_device_code_auth" tool to finish authentication.\n\nš” This code will expire in 15 minutes.`
|
|
724
|
+
}
|
|
725
|
+
]
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
return {
|
|
730
|
+
content: [
|
|
731
|
+
{
|
|
732
|
+
type: "text",
|
|
733
|
+
text: "ā
Already authenticated with Microsoft 365. Use force: true to re-authenticate."
|
|
734
|
+
}
|
|
735
|
+
]
|
|
736
|
+
};
|
|
737
|
+
}
|
|
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) {
|
|
752
|
+
return {
|
|
753
|
+
content: [
|
|
754
|
+
{
|
|
755
|
+
type: "text",
|
|
756
|
+
text: "ā
Device code authentication completed successfully! You can now use all Microsoft 365 email features."
|
|
757
|
+
}
|
|
758
|
+
]
|
|
759
|
+
};
|
|
760
|
+
}
|
|
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
|
+
return {
|
|
786
|
+
content: [
|
|
787
|
+
{
|
|
788
|
+
type: "text",
|
|
789
|
+
text: "ā¹ļø No pending device code authentication found. Use 'device_code_login' to start a new authentication process."
|
|
790
|
+
}
|
|
791
|
+
]
|
|
792
|
+
};
|
|
793
|
+
case "get_device_code":
|
|
794
|
+
const deviceCodeInfo = await enhancedMS365Auth.getDeviceCodeInfo();
|
|
795
|
+
return {
|
|
796
|
+
content: [
|
|
797
|
+
{
|
|
798
|
+
type: "text",
|
|
799
|
+
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.`
|
|
800
|
+
}
|
|
801
|
+
]
|
|
802
|
+
};
|
|
803
|
+
case "verify_authentication":
|
|
804
|
+
const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
|
|
805
|
+
const accounts = await enhancedMS365Auth.listAuthenticatedAccounts();
|
|
806
|
+
const storageInfo = enhancedMS365Auth.getStorageInfo();
|
|
807
|
+
return {
|
|
808
|
+
content: [
|
|
809
|
+
{
|
|
810
|
+
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'}`
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
};
|
|
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();
|
|
827
|
+
return {
|
|
828
|
+
content: [
|
|
829
|
+
{
|
|
830
|
+
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'}`
|
|
832
|
+
}
|
|
833
|
+
]
|
|
834
|
+
};
|
|
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
|
+
// ============ MULTI-USER AUTHENTICATION TOOLS ============
|
|
864
|
+
case "authenticate_user":
|
|
865
|
+
if (!ms365Config.multiUser) {
|
|
866
|
+
throw new Error("Multi-user mode not enabled. Start server with --multi-user flag.");
|
|
867
|
+
}
|
|
868
|
+
const authResult = await multiUserMS365Auth.authenticateNewUser(args?.userEmail || undefined);
|
|
869
|
+
return {
|
|
870
|
+
content: [
|
|
871
|
+
{
|
|
872
|
+
type: "text",
|
|
873
|
+
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}`
|
|
874
|
+
}
|
|
875
|
+
]
|
|
876
|
+
};
|
|
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
|
+
case "remove_my_session":
|
|
897
|
+
if (!ms365Config.multiUser) {
|
|
898
|
+
throw new Error("Multi-user mode not enabled. Start server with --multi-user flag.");
|
|
899
|
+
}
|
|
900
|
+
if (!args?.userId) {
|
|
901
|
+
throw new Error("User ID is required");
|
|
902
|
+
}
|
|
903
|
+
const removed = multiUserMS365Auth.removeUser(args.userId);
|
|
904
|
+
return {
|
|
905
|
+
content: [
|
|
906
|
+
{
|
|
907
|
+
type: "text",
|
|
908
|
+
text: removed ? `ā
Session removed for user: ${args.userId}` : `ā Session not found for user: ${args.userId}`
|
|
909
|
+
}
|
|
910
|
+
]
|
|
911
|
+
};
|
|
912
|
+
// ============ EMAIL OPERATIONS ============
|
|
913
|
+
case "send_email":
|
|
914
|
+
if (ms365Config.multiUser) {
|
|
915
|
+
const userId = args?.userId;
|
|
916
|
+
if (!userId) {
|
|
917
|
+
throw new Error("User ID is required in multi-user mode");
|
|
918
|
+
}
|
|
919
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
920
|
+
ms365Ops.setGraphClient(graphClient);
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
924
|
+
ms365Ops.setGraphClient(graphClient);
|
|
925
|
+
}
|
|
926
|
+
const emailResult = await ms365Ops.sendEmail(args);
|
|
927
|
+
return {
|
|
928
|
+
content: [
|
|
929
|
+
{
|
|
930
|
+
type: "text",
|
|
931
|
+
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}`
|
|
932
|
+
}
|
|
933
|
+
]
|
|
934
|
+
};
|
|
935
|
+
case "read_email":
|
|
936
|
+
if (ms365Config.multiUser) {
|
|
937
|
+
const userId = args?.userId;
|
|
938
|
+
if (!userId) {
|
|
939
|
+
throw new Error("User ID is required in multi-user mode");
|
|
940
|
+
}
|
|
941
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
942
|
+
ms365Ops.setGraphClient(graphClient);
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
946
|
+
ms365Ops.setGraphClient(graphClient);
|
|
947
|
+
}
|
|
948
|
+
const email = await ms365Ops.getEmail(args?.messageId, args?.includeAttachments);
|
|
949
|
+
return {
|
|
950
|
+
content: [
|
|
951
|
+
{
|
|
952
|
+
type: "text",
|
|
953
|
+
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}`
|
|
954
|
+
}
|
|
955
|
+
]
|
|
956
|
+
};
|
|
957
|
+
case "search_emails":
|
|
958
|
+
if (ms365Config.multiUser) {
|
|
959
|
+
const userId = args?.userId;
|
|
960
|
+
if (!userId) {
|
|
961
|
+
throw new Error("User ID is required in multi-user mode");
|
|
962
|
+
}
|
|
963
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
964
|
+
ms365Ops.setGraphClient(graphClient);
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
968
|
+
ms365Ops.setGraphClient(graphClient);
|
|
969
|
+
}
|
|
970
|
+
const searchResults = await ms365Ops.searchEmails(args);
|
|
971
|
+
return {
|
|
972
|
+
content: [
|
|
973
|
+
{
|
|
974
|
+
type: "text",
|
|
975
|
+
text: `š Email Search Results (${searchResults.messages.length} found)\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 š ID: ${email.id}\n`).join('\n')}`
|
|
976
|
+
}
|
|
977
|
+
]
|
|
978
|
+
};
|
|
979
|
+
case "list_emails":
|
|
980
|
+
if (ms365Config.multiUser) {
|
|
981
|
+
const userId = args?.userId;
|
|
982
|
+
if (!userId) {
|
|
983
|
+
throw new Error("User ID is required in multi-user mode");
|
|
984
|
+
}
|
|
985
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
986
|
+
ms365Ops.setGraphClient(graphClient);
|
|
987
|
+
}
|
|
988
|
+
else {
|
|
989
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
990
|
+
ms365Ops.setGraphClient(graphClient);
|
|
991
|
+
}
|
|
992
|
+
const emailList = await ms365Ops.listEmails(args?.folderId, args?.maxResults);
|
|
993
|
+
return {
|
|
994
|
+
content: [
|
|
995
|
+
{
|
|
996
|
+
type: "text",
|
|
997
|
+
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')}`
|
|
998
|
+
}
|
|
999
|
+
]
|
|
1000
|
+
};
|
|
1001
|
+
case "mark_email":
|
|
1002
|
+
if (ms365Config.multiUser) {
|
|
1003
|
+
const userId = args?.userId;
|
|
1004
|
+
if (!userId) {
|
|
1005
|
+
throw new Error("User ID is required in multi-user mode");
|
|
1006
|
+
}
|
|
1007
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1008
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1012
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1013
|
+
}
|
|
1014
|
+
await ms365Ops.markEmail(args?.messageId, args?.isRead);
|
|
1015
|
+
return {
|
|
1016
|
+
content: [
|
|
1017
|
+
{
|
|
1018
|
+
type: "text",
|
|
1019
|
+
text: `ā
Email marked as ${args?.isRead ? 'read' : 'unread'}\nš Message ID: ${args?.messageId}`
|
|
1020
|
+
}
|
|
1021
|
+
]
|
|
1022
|
+
};
|
|
1023
|
+
case "move_email":
|
|
1024
|
+
if (ms365Config.multiUser) {
|
|
1025
|
+
const userId = args?.userId;
|
|
1026
|
+
if (!userId) {
|
|
1027
|
+
throw new Error("User ID is required in multi-user mode");
|
|
1028
|
+
}
|
|
1029
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1030
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1034
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1035
|
+
}
|
|
1036
|
+
await ms365Ops.moveEmail(args?.messageId, args?.destinationFolderId);
|
|
1037
|
+
return {
|
|
1038
|
+
content: [
|
|
1039
|
+
{
|
|
1040
|
+
type: "text",
|
|
1041
|
+
text: `ā
Email moved to folder: ${args?.destinationFolderId}\nš Message ID: ${args?.messageId}`
|
|
1042
|
+
}
|
|
1043
|
+
]
|
|
1044
|
+
};
|
|
1045
|
+
case "delete_email":
|
|
1046
|
+
if (ms365Config.multiUser) {
|
|
1047
|
+
const userId = args?.userId;
|
|
1048
|
+
if (!userId) {
|
|
1049
|
+
throw new Error("User ID is required in multi-user mode");
|
|
1050
|
+
}
|
|
1051
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1052
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1056
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1057
|
+
}
|
|
1058
|
+
await ms365Ops.deleteEmail(args?.messageId);
|
|
1059
|
+
return {
|
|
1060
|
+
content: [
|
|
1061
|
+
{
|
|
1062
|
+
type: "text",
|
|
1063
|
+
text: `ā
Email deleted permanently\nš Message ID: ${args?.messageId}`
|
|
1064
|
+
}
|
|
1065
|
+
]
|
|
1066
|
+
};
|
|
1067
|
+
case "get_attachment":
|
|
1068
|
+
if (ms365Config.multiUser) {
|
|
1069
|
+
const userId = args?.userId;
|
|
1070
|
+
if (!userId) {
|
|
1071
|
+
throw new Error("User ID is required in multi-user mode");
|
|
1072
|
+
}
|
|
1073
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1074
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1078
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1079
|
+
}
|
|
1080
|
+
const attachment = await ms365Ops.getAttachment(args?.messageId, args?.attachmentId);
|
|
1081
|
+
return {
|
|
1082
|
+
content: [
|
|
1083
|
+
{
|
|
1084
|
+
type: "text",
|
|
1085
|
+
text: `š Attachment Downloaded\n\nš Name: ${attachment.name}\nš¾ Size: ${attachment.size} bytes\nšļø Type: ${attachment.contentType}\n\nš Content available in base64 format`
|
|
1086
|
+
}
|
|
1087
|
+
]
|
|
1088
|
+
};
|
|
1089
|
+
case "list_folders":
|
|
1090
|
+
if (ms365Config.multiUser) {
|
|
1091
|
+
const userId = args?.userId;
|
|
1092
|
+
if (!userId) {
|
|
1093
|
+
throw new Error("User ID is required in multi-user mode");
|
|
1094
|
+
}
|
|
1095
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1096
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1100
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1101
|
+
}
|
|
1102
|
+
const folders = await ms365Ops.listFolders();
|
|
1103
|
+
return {
|
|
1104
|
+
content: [
|
|
1105
|
+
{
|
|
1106
|
+
type: "text",
|
|
1107
|
+
text: `š Mail Folders\n\n${folders.map((folder) => `š ${folder.displayName}\n š ID: ${folder.id}\n š§ Total Items: ${folder.totalItemCount}\n š© Unread: ${folder.unreadItemCount}\n`).join('\n')}`
|
|
1108
|
+
}
|
|
1109
|
+
]
|
|
1110
|
+
};
|
|
1111
|
+
case "get_contacts":
|
|
1112
|
+
if (ms365Config.multiUser) {
|
|
1113
|
+
const userId = args?.userId;
|
|
1114
|
+
if (!userId) {
|
|
1115
|
+
throw new Error("User ID is required in multi-user mode");
|
|
1116
|
+
}
|
|
1117
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1118
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1122
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1123
|
+
}
|
|
1124
|
+
const contacts = await ms365Ops.getContacts(args?.maxResults);
|
|
1125
|
+
return {
|
|
1126
|
+
content: [
|
|
1127
|
+
{
|
|
1128
|
+
type: "text",
|
|
1129
|
+
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')}`
|
|
1130
|
+
}
|
|
1131
|
+
]
|
|
1132
|
+
};
|
|
1133
|
+
case "search_contacts":
|
|
1134
|
+
if (ms365Config.multiUser) {
|
|
1135
|
+
const userId = args?.userId;
|
|
1136
|
+
if (!userId) {
|
|
1137
|
+
throw new Error("User ID is required in multi-user mode");
|
|
1138
|
+
}
|
|
1139
|
+
const graphClient = await multiUserMS365Auth.getGraphClientForUser(userId);
|
|
1140
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
1144
|
+
ms365Ops.setGraphClient(graphClient);
|
|
1145
|
+
}
|
|
1146
|
+
const searchContactResults = await ms365Ops.searchContacts(args?.query, args?.maxResults);
|
|
1147
|
+
return {
|
|
1148
|
+
content: [
|
|
1149
|
+
{
|
|
1150
|
+
type: "text",
|
|
1151
|
+
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')}`
|
|
1152
|
+
}
|
|
1153
|
+
]
|
|
1154
|
+
};
|
|
1155
|
+
default:
|
|
1156
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
catch (error) {
|
|
1160
|
+
logger.error(`Tool execution error for ${name}:`, error);
|
|
1161
|
+
return {
|
|
1162
|
+
content: [
|
|
1163
|
+
{
|
|
1164
|
+
type: "text",
|
|
1165
|
+
text: `ā Error executing ${name}: ${error instanceof Error ? error.message : String(error)}`
|
|
1166
|
+
}
|
|
1167
|
+
],
|
|
1168
|
+
isError: true
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
async function main() {
|
|
1173
|
+
ms365Config = parseArgs();
|
|
1174
|
+
if (ms365Config.setupAuth) {
|
|
1175
|
+
try {
|
|
1176
|
+
await enhancedMS365Auth.setupCredentials();
|
|
1177
|
+
process.exit(0);
|
|
1178
|
+
}
|
|
1179
|
+
catch (error) {
|
|
1180
|
+
console.error('Setup failed:', error);
|
|
1181
|
+
process.exit(1);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (ms365Config.resetAuth) {
|
|
1185
|
+
try {
|
|
1186
|
+
await enhancedMS365Auth.resetAuth();
|
|
1187
|
+
console.log('ā
Authentication data cleared successfully');
|
|
1188
|
+
process.exit(0);
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
console.error('Reset failed:', error);
|
|
1192
|
+
process.exit(1);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (ms365Config.login) {
|
|
1196
|
+
try {
|
|
1197
|
+
console.log('š Starting Microsoft 365 authentication...\n');
|
|
1198
|
+
await enhancedMS365Auth.authenticateWithDeviceCode();
|
|
1199
|
+
console.log('ā
Login successful! You can now use MS365 MCP server.\n');
|
|
1200
|
+
process.exit(0);
|
|
1201
|
+
}
|
|
1202
|
+
catch (error) {
|
|
1203
|
+
console.error('ā Login failed:', error);
|
|
1204
|
+
process.exit(1);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (ms365Config.logout) {
|
|
1208
|
+
try {
|
|
1209
|
+
await enhancedMS365Auth.resetAuth();
|
|
1210
|
+
console.log('ā
Logged out successfully');
|
|
1211
|
+
process.exit(0);
|
|
1212
|
+
}
|
|
1213
|
+
catch (error) {
|
|
1214
|
+
console.error('ā Logout failed:', error);
|
|
1215
|
+
process.exit(1);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (ms365Config.verifyLogin) {
|
|
1219
|
+
try {
|
|
1220
|
+
const isAuthenticated = await enhancedMS365Auth.isAuthenticated();
|
|
1221
|
+
const accounts = await enhancedMS365Auth.listAuthenticatedAccounts();
|
|
1222
|
+
const storageInfo = enhancedMS365Auth.getStorageInfo();
|
|
1223
|
+
console.log('\nš MS365 Authentication Status\n');
|
|
1224
|
+
console.log(`Authentication: ${isAuthenticated ? 'ā
Valid' : 'ā Not authenticated'}`);
|
|
1225
|
+
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`);
|
|
1228
|
+
process.exit(isAuthenticated ? 0 : 1);
|
|
1229
|
+
}
|
|
1230
|
+
catch (error) {
|
|
1231
|
+
console.error('ā Verification failed:', error);
|
|
1232
|
+
process.exit(1);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
logger.log('Starting MS365 MCP Server...');
|
|
1236
|
+
if (ms365Config.multiUser) {
|
|
1237
|
+
logger.log('Multi-user mode enabled');
|
|
1238
|
+
}
|
|
1239
|
+
// Display storage information
|
|
1240
|
+
const storageInfo = enhancedMS365Auth.getStorageInfo();
|
|
1241
|
+
logger.log(`Credential storage: ${storageInfo.method} at ${storageInfo.location}`);
|
|
1242
|
+
const transport = new StdioServerTransport();
|
|
1243
|
+
await server.connect(transport);
|
|
1244
|
+
logger.log('MS365 MCP Server running on stdio');
|
|
1245
|
+
}
|
|
1246
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1247
|
+
main().catch((error) => {
|
|
1248
|
+
logger.error('Failed to start MS365 MCP Server:', error);
|
|
1249
|
+
process.exit(1);
|
|
1250
|
+
});
|
|
1251
|
+
}
|