@littlebearapps/outlook-assistant 3.3.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/.env.example +22 -0
- package/LICENSE +21 -0
- package/README.md +422 -0
- package/advanced/index.js +652 -0
- package/auth/index.js +32 -0
- package/auth/oauth-server.js +233 -0
- package/auth/token-manager.js +105 -0
- package/auth/token-storage.js +359 -0
- package/auth/tools.js +159 -0
- package/calendar/accept.js +72 -0
- package/calendar/cancel.js +72 -0
- package/calendar/create.js +115 -0
- package/calendar/decline.js +72 -0
- package/calendar/delete.js +67 -0
- package/calendar/index.js +130 -0
- package/calendar/list.js +108 -0
- package/categories/index.js +955 -0
- package/config.js +95 -0
- package/contacts/index.js +754 -0
- package/email/attachments.js +365 -0
- package/email/conversations.js +666 -0
- package/email/delta.js +210 -0
- package/email/export.js +572 -0
- package/email/folder-utils.js +192 -0
- package/email/headers.js +344 -0
- package/email/index.js +537 -0
- package/email/list.js +136 -0
- package/email/mark-as-read.js +114 -0
- package/email/mime.js +286 -0
- package/email/read.js +161 -0
- package/email/search.js +628 -0
- package/email/send.js +169 -0
- package/folder/create.js +137 -0
- package/folder/delete.js +108 -0
- package/folder/index.js +112 -0
- package/folder/list.js +289 -0
- package/folder/move.js +186 -0
- package/folder/stats.js +322 -0
- package/index.js +162 -0
- package/llms.txt +76 -0
- package/outlook-auth-server.js +384 -0
- package/package.json +97 -0
- package/rules/create.js +273 -0
- package/rules/index.js +276 -0
- package/rules/list.js +216 -0
- package/settings/index.js +678 -0
- package/utils/field-presets.js +311 -0
- package/utils/graph-api.js +268 -0
- package/utils/mock-data.js +154 -0
- package/utils/odata-helpers.js +33 -0
- package/utils/response-formatter.js +457 -0
- package/utils/safety.js +123 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced module for Outlook Assistant server
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Shared mailbox access
|
|
6
|
+
* - Meeting room search
|
|
7
|
+
*
|
|
8
|
+
* Note: Message flag handlers are still exported from here but their tool
|
|
9
|
+
* definitions have moved to the email module's `update-email` tool.
|
|
10
|
+
*/
|
|
11
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
12
|
+
const { ensureAuthenticated } = require('../auth');
|
|
13
|
+
const { FIELD_PRESETS } = require('../utils/field-presets');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Format an email for display (simplified)
|
|
17
|
+
*/
|
|
18
|
+
function formatEmail(email, verbosity = 'standard') {
|
|
19
|
+
if (verbosity === 'minimal') {
|
|
20
|
+
return {
|
|
21
|
+
id: email.id,
|
|
22
|
+
subject: email.subject,
|
|
23
|
+
from: email.from?.emailAddress?.address,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
id: email.id,
|
|
29
|
+
subject: email.subject,
|
|
30
|
+
from: email.from?.emailAddress
|
|
31
|
+
? {
|
|
32
|
+
name: email.from.emailAddress.name,
|
|
33
|
+
address: email.from.emailAddress.address,
|
|
34
|
+
}
|
|
35
|
+
: null,
|
|
36
|
+
receivedDateTime: email.receivedDateTime,
|
|
37
|
+
isRead: email.isRead,
|
|
38
|
+
hasAttachments: email.hasAttachments,
|
|
39
|
+
flag: email.flag,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Access shared mailbox handler
|
|
45
|
+
* Requires Mail.Read.Shared permission
|
|
46
|
+
*/
|
|
47
|
+
async function handleAccessSharedMailbox(args) {
|
|
48
|
+
const { sharedMailbox, folder, count, outputVerbosity } = args;
|
|
49
|
+
|
|
50
|
+
if (!sharedMailbox) {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: 'text',
|
|
55
|
+
text: "Shared mailbox email address is required (e.g., 'shared@company.com').",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const mailFolder = folder || 'inbox';
|
|
62
|
+
const pageSize = Math.min(count || 25, 50);
|
|
63
|
+
const verbosity = outputVerbosity || 'standard';
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const accessToken = await ensureAuthenticated();
|
|
67
|
+
|
|
68
|
+
// Build endpoint for shared mailbox
|
|
69
|
+
const endpoint = `users/${sharedMailbox}/mailFolders/${mailFolder}/messages`;
|
|
70
|
+
const fieldSet = verbosity === 'full' ? 'read' : 'list';
|
|
71
|
+
const queryParams = {
|
|
72
|
+
$top: pageSize.toString(),
|
|
73
|
+
$orderby: 'receivedDateTime desc',
|
|
74
|
+
$select: FIELD_PRESETS[fieldSet].join(','),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const response = await callGraphAPI(
|
|
78
|
+
accessToken,
|
|
79
|
+
'GET',
|
|
80
|
+
endpoint,
|
|
81
|
+
null,
|
|
82
|
+
queryParams
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const messages = response.value || [];
|
|
86
|
+
|
|
87
|
+
if (messages.length === 0) {
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: 'text',
|
|
92
|
+
text: `No emails found in ${sharedMailbox}/${mailFolder}.\n\nNote: Make sure you have access to this shared mailbox and the Mail.Read.Shared permission is granted.`,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let output = [];
|
|
99
|
+
output.push(`# Shared Mailbox: ${sharedMailbox}`);
|
|
100
|
+
output.push(`**Folder**: ${mailFolder} | **Count**: ${messages.length}\n`);
|
|
101
|
+
|
|
102
|
+
if (verbosity === 'minimal') {
|
|
103
|
+
messages.forEach((msg, i) => {
|
|
104
|
+
output.push(`${i + 1}. ${msg.subject}`);
|
|
105
|
+
output.push(` From: ${msg.from?.emailAddress?.address || 'Unknown'}`);
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
output.push('| # | Subject | From | Date | Read |');
|
|
109
|
+
output.push('|---|---------|------|------|------|');
|
|
110
|
+
messages.forEach((msg, i) => {
|
|
111
|
+
const date = new Date(msg.receivedDateTime).toLocaleDateString();
|
|
112
|
+
const from =
|
|
113
|
+
msg.from?.emailAddress?.name ||
|
|
114
|
+
msg.from?.emailAddress?.address ||
|
|
115
|
+
'Unknown';
|
|
116
|
+
const read = msg.isRead ? 'Y' : 'N';
|
|
117
|
+
output.push(
|
|
118
|
+
`| ${i + 1} | ${msg.subject?.substring(0, 40)}${msg.subject?.length > 40 ? '...' : ''} | ${from.substring(0, 20)} | ${date} | ${read} |`
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (verbosity === 'full') {
|
|
124
|
+
output.push('\n## Message IDs');
|
|
125
|
+
messages.forEach((msg, i) => {
|
|
126
|
+
output.push(`${i + 1}. \`${msg.id}\``);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: 'text',
|
|
134
|
+
text: output.join('\n'),
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
_meta: {
|
|
138
|
+
sharedMailbox,
|
|
139
|
+
folder: mailFolder,
|
|
140
|
+
count: messages.length,
|
|
141
|
+
messages: messages.map((m) => formatEmail(m, verbosity)),
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error.message === 'Authentication required') {
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: 'text',
|
|
150
|
+
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (
|
|
157
|
+
error.message.includes('Access is denied') ||
|
|
158
|
+
error.message.includes('403')
|
|
159
|
+
) {
|
|
160
|
+
return {
|
|
161
|
+
content: [
|
|
162
|
+
{
|
|
163
|
+
type: 'text',
|
|
164
|
+
text: `Access denied to shared mailbox "${sharedMailbox}".\n\n**Possible causes:**\n- You don't have access to this shared mailbox\n- The Mail.Read.Shared permission is not granted\n- The shared mailbox address is incorrect`,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (error.message.includes('not found') || error.message.includes('404')) {
|
|
171
|
+
return {
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: 'text',
|
|
175
|
+
text: `Shared mailbox "${sharedMailbox}" not found. Please verify the email address.`,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
content: [
|
|
183
|
+
{
|
|
184
|
+
type: 'text',
|
|
185
|
+
text: `Error accessing shared mailbox: ${error.message}`,
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Set message flag handler
|
|
194
|
+
*/
|
|
195
|
+
async function handleSetMessageFlag(args) {
|
|
196
|
+
const {
|
|
197
|
+
messageId,
|
|
198
|
+
messageIds,
|
|
199
|
+
dueDateTime,
|
|
200
|
+
startDateTime,
|
|
201
|
+
reminderDateTime: _reminderDateTime,
|
|
202
|
+
} = args;
|
|
203
|
+
|
|
204
|
+
// Support single ID or array
|
|
205
|
+
const ids = messageIds || (messageId ? [messageId] : []);
|
|
206
|
+
|
|
207
|
+
if (ids.length === 0) {
|
|
208
|
+
return {
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: 'Message ID (messageId) or IDs (messageIds) required.',
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const accessToken = await ensureAuthenticated();
|
|
220
|
+
|
|
221
|
+
// Build flag object
|
|
222
|
+
const flag = {
|
|
223
|
+
flagStatus: 'flagged',
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
if (dueDateTime) {
|
|
227
|
+
flag.dueDateTime = {
|
|
228
|
+
dateTime: new Date(dueDateTime).toISOString(),
|
|
229
|
+
timeZone: 'UTC',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (startDateTime) {
|
|
234
|
+
flag.startDateTime = {
|
|
235
|
+
dateTime: new Date(startDateTime).toISOString(),
|
|
236
|
+
timeZone: 'UTC',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Process all messages
|
|
241
|
+
const results = [];
|
|
242
|
+
const errors = [];
|
|
243
|
+
|
|
244
|
+
for (const id of ids) {
|
|
245
|
+
try {
|
|
246
|
+
await callGraphAPI(accessToken, 'PATCH', `me/messages/${id}`, {
|
|
247
|
+
flag,
|
|
248
|
+
});
|
|
249
|
+
results.push({ id, success: true });
|
|
250
|
+
} catch (err) {
|
|
251
|
+
errors.push({ id, error: err.message });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let output = [];
|
|
256
|
+
|
|
257
|
+
if (results.length > 0) {
|
|
258
|
+
output.push(`Flagged ${results.length} message(s) for follow-up`);
|
|
259
|
+
|
|
260
|
+
if (dueDateTime) {
|
|
261
|
+
output.push(`**Due**: ${new Date(dueDateTime).toLocaleString()}`);
|
|
262
|
+
}
|
|
263
|
+
if (startDateTime) {
|
|
264
|
+
output.push(`**Start**: ${new Date(startDateTime).toLocaleString()}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (errors.length > 0) {
|
|
269
|
+
output.push(`\n${errors.length} error(s):`);
|
|
270
|
+
errors.forEach((e) => {
|
|
271
|
+
output.push(`- ${e.id.substring(0, 20)}...: ${e.error}`);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
content: [
|
|
277
|
+
{
|
|
278
|
+
type: 'text',
|
|
279
|
+
text: output.join('\n'),
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
_meta: {
|
|
283
|
+
successful: results.length,
|
|
284
|
+
failed: errors.length,
|
|
285
|
+
results,
|
|
286
|
+
errors,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error.message === 'Authentication required') {
|
|
291
|
+
return {
|
|
292
|
+
content: [
|
|
293
|
+
{
|
|
294
|
+
type: 'text',
|
|
295
|
+
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
content: [
|
|
302
|
+
{
|
|
303
|
+
type: 'text',
|
|
304
|
+
text: `Error setting message flag: ${error.message}`,
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Clear message flag handler
|
|
313
|
+
*/
|
|
314
|
+
async function handleClearMessageFlag(args) {
|
|
315
|
+
const { messageId, messageIds, markComplete } = args;
|
|
316
|
+
|
|
317
|
+
// Support single ID or array
|
|
318
|
+
const ids = messageIds || (messageId ? [messageId] : []);
|
|
319
|
+
|
|
320
|
+
if (ids.length === 0) {
|
|
321
|
+
return {
|
|
322
|
+
content: [
|
|
323
|
+
{
|
|
324
|
+
type: 'text',
|
|
325
|
+
text: 'Message ID (messageId) or IDs (messageIds) required.',
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
const accessToken = await ensureAuthenticated();
|
|
333
|
+
|
|
334
|
+
// Determine flag status
|
|
335
|
+
const flag = {
|
|
336
|
+
flagStatus: markComplete ? 'complete' : 'notFlagged',
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// Clear completion date if marking complete
|
|
340
|
+
if (markComplete) {
|
|
341
|
+
flag.completedDateTime = {
|
|
342
|
+
dateTime: new Date().toISOString(),
|
|
343
|
+
timeZone: 'UTC',
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Process all messages
|
|
348
|
+
const results = [];
|
|
349
|
+
const errors = [];
|
|
350
|
+
|
|
351
|
+
for (const id of ids) {
|
|
352
|
+
try {
|
|
353
|
+
await callGraphAPI(accessToken, 'PATCH', `me/messages/${id}`, {
|
|
354
|
+
flag,
|
|
355
|
+
});
|
|
356
|
+
results.push({ id, success: true });
|
|
357
|
+
} catch (err) {
|
|
358
|
+
errors.push({ id, error: err.message });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const action = markComplete ? 'marked complete' : 'cleared';
|
|
363
|
+
let output = [];
|
|
364
|
+
|
|
365
|
+
if (results.length > 0) {
|
|
366
|
+
output.push(`${results.length} message(s) ${action}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (errors.length > 0) {
|
|
370
|
+
output.push(`\n${errors.length} error(s):`);
|
|
371
|
+
errors.forEach((e) => {
|
|
372
|
+
output.push(`- ${e.id.substring(0, 20)}...: ${e.error}`);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
content: [
|
|
378
|
+
{
|
|
379
|
+
type: 'text',
|
|
380
|
+
text: output.join('\n'),
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
_meta: {
|
|
384
|
+
action: markComplete ? 'complete' : 'cleared',
|
|
385
|
+
successful: results.length,
|
|
386
|
+
failed: errors.length,
|
|
387
|
+
results,
|
|
388
|
+
errors,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
} catch (error) {
|
|
392
|
+
if (error.message === 'Authentication required') {
|
|
393
|
+
return {
|
|
394
|
+
content: [
|
|
395
|
+
{
|
|
396
|
+
type: 'text',
|
|
397
|
+
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
content: [
|
|
404
|
+
{
|
|
405
|
+
type: 'text',
|
|
406
|
+
text: `Error clearing message flag: ${error.message}`,
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Find meeting rooms handler
|
|
415
|
+
*/
|
|
416
|
+
async function handleFindMeetingRooms(args) {
|
|
417
|
+
const { query, building, floor, capacity, outputVerbosity } = args;
|
|
418
|
+
const verbosity = outputVerbosity || 'standard';
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
const accessToken = await ensureAuthenticated();
|
|
422
|
+
|
|
423
|
+
// Try the findRooms endpoint first (may not be available in all tenants)
|
|
424
|
+
let rooms = [];
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
// Try /places endpoint for room lists
|
|
428
|
+
const placesResponse = await callGraphAPI(
|
|
429
|
+
accessToken,
|
|
430
|
+
'GET',
|
|
431
|
+
'places/microsoft.graph.room'
|
|
432
|
+
);
|
|
433
|
+
rooms = placesResponse.value || [];
|
|
434
|
+
} catch (_placesError) {
|
|
435
|
+
// Fall back to findRooms
|
|
436
|
+
try {
|
|
437
|
+
const roomsResponse = await callGraphAPI(
|
|
438
|
+
accessToken,
|
|
439
|
+
'GET',
|
|
440
|
+
'me/findRooms'
|
|
441
|
+
);
|
|
442
|
+
rooms = roomsResponse.value || [];
|
|
443
|
+
} catch (findRoomsError) {
|
|
444
|
+
return {
|
|
445
|
+
content: [
|
|
446
|
+
{
|
|
447
|
+
type: 'text',
|
|
448
|
+
text: `Unable to find meeting rooms.\n\n**Note**: This feature requires:\n- Places.Read.All permission\n- Meeting rooms configured in your organization\n\nError: ${findRoomsError.message}`,
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Apply filters
|
|
456
|
+
if (query) {
|
|
457
|
+
const q = query.toLowerCase();
|
|
458
|
+
rooms = rooms.filter(
|
|
459
|
+
(r) =>
|
|
460
|
+
r.displayName?.toLowerCase().includes(q) ||
|
|
461
|
+
r.emailAddress?.toLowerCase().includes(q) ||
|
|
462
|
+
r.nickname?.toLowerCase().includes(q)
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (building) {
|
|
467
|
+
const b = building.toLowerCase();
|
|
468
|
+
rooms = rooms.filter((r) => r.building?.toLowerCase().includes(b));
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (floor !== undefined) {
|
|
472
|
+
rooms = rooms.filter((r) => r.floorNumber === floor);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (capacity) {
|
|
476
|
+
rooms = rooms.filter((r) => r.capacity >= capacity);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (rooms.length === 0) {
|
|
480
|
+
return {
|
|
481
|
+
content: [
|
|
482
|
+
{
|
|
483
|
+
type: 'text',
|
|
484
|
+
text: 'No meeting rooms found matching your criteria.\n\nTry broadening your search or check if rooms are configured in your organization.',
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
let output = [];
|
|
491
|
+
output.push(`# Meeting Rooms (${rooms.length})\n`);
|
|
492
|
+
|
|
493
|
+
if (verbosity === 'minimal') {
|
|
494
|
+
rooms.forEach((room) => {
|
|
495
|
+
output.push(
|
|
496
|
+
`- ${room.displayName || room.name} (${room.emailAddress})`
|
|
497
|
+
);
|
|
498
|
+
});
|
|
499
|
+
} else {
|
|
500
|
+
rooms.forEach((room) => {
|
|
501
|
+
output.push(`## ${room.displayName || room.name}`);
|
|
502
|
+
output.push(`**Email**: ${room.emailAddress || 'N/A'}`);
|
|
503
|
+
|
|
504
|
+
if (room.capacity) {
|
|
505
|
+
output.push(`**Capacity**: ${room.capacity}`);
|
|
506
|
+
}
|
|
507
|
+
if (room.building) {
|
|
508
|
+
output.push(`**Building**: ${room.building}`);
|
|
509
|
+
}
|
|
510
|
+
if (room.floorNumber !== undefined) {
|
|
511
|
+
output.push(`**Floor**: ${room.floorNumber}`);
|
|
512
|
+
}
|
|
513
|
+
if (room.floorLabel) {
|
|
514
|
+
output.push(`**Floor Label**: ${room.floorLabel}`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (verbosity === 'full') {
|
|
518
|
+
if (room.audioDeviceName) {
|
|
519
|
+
output.push(`**Audio**: ${room.audioDeviceName}`);
|
|
520
|
+
}
|
|
521
|
+
if (room.videoDeviceName) {
|
|
522
|
+
output.push(`**Video**: ${room.videoDeviceName}`);
|
|
523
|
+
}
|
|
524
|
+
if (room.displayDeviceName) {
|
|
525
|
+
output.push(`**Display**: ${room.displayDeviceName}`);
|
|
526
|
+
}
|
|
527
|
+
if (room.isWheelChairAccessible !== undefined) {
|
|
528
|
+
output.push(
|
|
529
|
+
`**Wheelchair Accessible**: ${room.isWheelChairAccessible ? 'Yes' : 'No'}`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
output.push('');
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
content: [
|
|
540
|
+
{
|
|
541
|
+
type: 'text',
|
|
542
|
+
text: output.join('\n'),
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
_meta: {
|
|
546
|
+
count: rooms.length,
|
|
547
|
+
rooms: rooms,
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
} catch (error) {
|
|
551
|
+
if (error.message === 'Authentication required') {
|
|
552
|
+
return {
|
|
553
|
+
content: [
|
|
554
|
+
{
|
|
555
|
+
type: 'text',
|
|
556
|
+
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
content: [
|
|
563
|
+
{
|
|
564
|
+
type: 'text',
|
|
565
|
+
text: `Error finding meeting rooms: ${error.message}`,
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Consolidated tool definitions (4 → 2, flag tools moved to email/update-email)
|
|
573
|
+
const advancedTools = [
|
|
574
|
+
{
|
|
575
|
+
name: 'access-shared-mailbox',
|
|
576
|
+
description: 'Read emails from a shared mailbox you have access to',
|
|
577
|
+
annotations: {
|
|
578
|
+
title: 'Shared Mailbox',
|
|
579
|
+
readOnlyHint: true,
|
|
580
|
+
openWorldHint: false,
|
|
581
|
+
},
|
|
582
|
+
inputSchema: {
|
|
583
|
+
type: 'object',
|
|
584
|
+
properties: {
|
|
585
|
+
sharedMailbox: {
|
|
586
|
+
type: 'string',
|
|
587
|
+
description: 'Email address of the shared mailbox (required)',
|
|
588
|
+
},
|
|
589
|
+
folder: {
|
|
590
|
+
type: 'string',
|
|
591
|
+
description: 'Folder to read from (default: inbox)',
|
|
592
|
+
},
|
|
593
|
+
count: {
|
|
594
|
+
type: 'number',
|
|
595
|
+
description: 'Number of emails to retrieve (default: 25, max: 50)',
|
|
596
|
+
},
|
|
597
|
+
outputVerbosity: {
|
|
598
|
+
type: 'string',
|
|
599
|
+
enum: ['minimal', 'standard', 'full'],
|
|
600
|
+
description: 'Output detail level (default: standard)',
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
required: ['sharedMailbox'],
|
|
604
|
+
},
|
|
605
|
+
handler: handleAccessSharedMailbox,
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
name: 'find-meeting-rooms',
|
|
609
|
+
description: 'Search for meeting rooms in your organisation',
|
|
610
|
+
annotations: {
|
|
611
|
+
title: 'Meeting Rooms',
|
|
612
|
+
readOnlyHint: true,
|
|
613
|
+
openWorldHint: false,
|
|
614
|
+
},
|
|
615
|
+
inputSchema: {
|
|
616
|
+
type: 'object',
|
|
617
|
+
properties: {
|
|
618
|
+
query: {
|
|
619
|
+
type: 'string',
|
|
620
|
+
description: 'Search query (room name, email)',
|
|
621
|
+
},
|
|
622
|
+
building: {
|
|
623
|
+
type: 'string',
|
|
624
|
+
description: 'Filter by building name',
|
|
625
|
+
},
|
|
626
|
+
floor: {
|
|
627
|
+
type: 'number',
|
|
628
|
+
description: 'Filter by floor number',
|
|
629
|
+
},
|
|
630
|
+
capacity: {
|
|
631
|
+
type: 'number',
|
|
632
|
+
description: 'Minimum capacity required',
|
|
633
|
+
},
|
|
634
|
+
outputVerbosity: {
|
|
635
|
+
type: 'string',
|
|
636
|
+
enum: ['minimal', 'standard', 'full'],
|
|
637
|
+
description: 'Output detail level (default: standard)',
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
required: [],
|
|
641
|
+
},
|
|
642
|
+
handler: handleFindMeetingRooms,
|
|
643
|
+
},
|
|
644
|
+
];
|
|
645
|
+
|
|
646
|
+
module.exports = {
|
|
647
|
+
advancedTools,
|
|
648
|
+
handleAccessSharedMailbox,
|
|
649
|
+
handleSetMessageFlag,
|
|
650
|
+
handleClearMessageFlag,
|
|
651
|
+
handleFindMeetingRooms,
|
|
652
|
+
};
|
package/auth/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication module for Outlook Assistant server
|
|
3
|
+
*/
|
|
4
|
+
const tokenManager = require('./token-manager');
|
|
5
|
+
const { authTools } = require('./tools');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Ensures the user is authenticated and returns an access token
|
|
9
|
+
* @param {boolean} forceNew - Whether to force a new authentication
|
|
10
|
+
* @returns {Promise<string>} - Access token
|
|
11
|
+
* @throws {Error} - If authentication fails
|
|
12
|
+
*/
|
|
13
|
+
async function ensureAuthenticated(forceNew = false) {
|
|
14
|
+
if (forceNew) {
|
|
15
|
+
// Force re-authentication
|
|
16
|
+
throw new Error('Authentication required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check for existing token
|
|
20
|
+
const accessToken = tokenManager.getAccessToken();
|
|
21
|
+
if (!accessToken) {
|
|
22
|
+
throw new Error('Authentication required');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return accessToken;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
tokenManager,
|
|
30
|
+
authTools,
|
|
31
|
+
ensureAuthenticated,
|
|
32
|
+
};
|