@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
package/email/index.js
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email module for Outlook Assistant server
|
|
3
|
+
*
|
|
4
|
+
* Consolidated from 17 tools to 6 for token efficiency.
|
|
5
|
+
*/
|
|
6
|
+
const handleListEmails = require('./list');
|
|
7
|
+
const { handleSearchEmails, handleSearchByMessageId } = require('./search');
|
|
8
|
+
const handleReadEmail = require('./read');
|
|
9
|
+
const handleSendEmail = require('./send');
|
|
10
|
+
const handleMarkAsRead = require('./mark-as-read');
|
|
11
|
+
const {
|
|
12
|
+
handleListAttachments,
|
|
13
|
+
handleDownloadAttachment,
|
|
14
|
+
handleGetAttachmentContent,
|
|
15
|
+
} = require('./attachments');
|
|
16
|
+
const { handleExportEmail, handleBatchExportEmails } = require('./export');
|
|
17
|
+
const handleListEmailsDelta = require('./delta');
|
|
18
|
+
const { handleGetEmailHeaders } = require('./headers');
|
|
19
|
+
const { handleGetMimeContent } = require('./mime');
|
|
20
|
+
const {
|
|
21
|
+
handleListConversations,
|
|
22
|
+
handleGetConversation,
|
|
23
|
+
handleExportConversation,
|
|
24
|
+
} = require('./conversations');
|
|
25
|
+
|
|
26
|
+
// Import flag handlers from advanced module
|
|
27
|
+
const { handleSetMessageFlag, handleClearMessageFlag } = require('../advanced');
|
|
28
|
+
|
|
29
|
+
// Consolidated email tool definitions (17 → 6)
|
|
30
|
+
const emailTools = [
|
|
31
|
+
{
|
|
32
|
+
name: 'search-emails',
|
|
33
|
+
description:
|
|
34
|
+
'Search and list emails. With no query, lists recent emails (like list-emails). Supports search queries, KQL, delta sync, message-id lookup, and conversation listing.',
|
|
35
|
+
annotations: {
|
|
36
|
+
title: 'Search Emails',
|
|
37
|
+
readOnlyHint: true,
|
|
38
|
+
openWorldHint: false,
|
|
39
|
+
},
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
// Mode selectors (all optional — defaults to list mode)
|
|
44
|
+
deltaMode: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
description:
|
|
47
|
+
'Enable delta sync mode. Returns only changes since last sync. Use deltaToken for subsequent calls.',
|
|
48
|
+
},
|
|
49
|
+
internetMessageId: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description:
|
|
52
|
+
'Look up email by Message-ID header (e.g. <abc123@example.com>). For threading/deduplication.',
|
|
53
|
+
},
|
|
54
|
+
conversationId: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description:
|
|
57
|
+
'Get all messages in a conversation thread by conversationId.',
|
|
58
|
+
},
|
|
59
|
+
groupByConversation: {
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
description:
|
|
62
|
+
'List conversations (threads) grouped by conversationId instead of individual emails.',
|
|
63
|
+
},
|
|
64
|
+
// Search/list params
|
|
65
|
+
query: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'Search query text. Omit for list mode.',
|
|
68
|
+
},
|
|
69
|
+
kqlQuery: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description:
|
|
72
|
+
'Raw KQL (Keyword Query Language) query for advanced search. Bypasses other search params.',
|
|
73
|
+
},
|
|
74
|
+
folder: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: "Email folder (default: 'inbox')",
|
|
77
|
+
},
|
|
78
|
+
from: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: 'Filter by sender email/name',
|
|
81
|
+
},
|
|
82
|
+
to: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'Filter by recipient email/name',
|
|
85
|
+
},
|
|
86
|
+
subject: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: 'Filter by subject',
|
|
89
|
+
},
|
|
90
|
+
hasAttachments: {
|
|
91
|
+
type: 'boolean',
|
|
92
|
+
description: 'Filter to emails with attachments',
|
|
93
|
+
},
|
|
94
|
+
unreadOnly: {
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
description: 'Filter to unread emails only',
|
|
97
|
+
},
|
|
98
|
+
receivedAfter: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'Filter emails received after date (ISO 8601)',
|
|
101
|
+
},
|
|
102
|
+
receivedBefore: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'Filter emails received before date (ISO 8601)',
|
|
105
|
+
},
|
|
106
|
+
searchAllFolders: {
|
|
107
|
+
type: 'boolean',
|
|
108
|
+
description: 'Search across all mail folders',
|
|
109
|
+
},
|
|
110
|
+
count: {
|
|
111
|
+
type: 'number',
|
|
112
|
+
description:
|
|
113
|
+
'Number of results (list default: 25, search default: 10, max: 50)',
|
|
114
|
+
},
|
|
115
|
+
outputVerbosity: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
enum: ['minimal', 'standard', 'full'],
|
|
118
|
+
description: 'Output detail level (default: standard)',
|
|
119
|
+
},
|
|
120
|
+
// Delta mode params
|
|
121
|
+
deltaToken: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
description:
|
|
124
|
+
'Token from previous delta call for incremental sync (deltaMode only)',
|
|
125
|
+
},
|
|
126
|
+
maxResults: {
|
|
127
|
+
type: 'number',
|
|
128
|
+
description:
|
|
129
|
+
'Max results per page for delta sync (default: 100, max: 200)',
|
|
130
|
+
},
|
|
131
|
+
// Conversation params
|
|
132
|
+
includeHeaders: {
|
|
133
|
+
type: 'boolean',
|
|
134
|
+
description:
|
|
135
|
+
'Include email headers for each message (conversationId only)',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
required: [],
|
|
139
|
+
},
|
|
140
|
+
handler: async (args) => {
|
|
141
|
+
// Route to appropriate handler based on mode
|
|
142
|
+
if (args.deltaMode) {
|
|
143
|
+
return handleListEmailsDelta(args);
|
|
144
|
+
}
|
|
145
|
+
if (args.internetMessageId) {
|
|
146
|
+
return handleSearchByMessageId({
|
|
147
|
+
messageId: args.internetMessageId,
|
|
148
|
+
outputVerbosity: args.outputVerbosity,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (args.conversationId) {
|
|
152
|
+
return handleGetConversation(args);
|
|
153
|
+
}
|
|
154
|
+
if (args.groupByConversation) {
|
|
155
|
+
return handleListConversations(args);
|
|
156
|
+
}
|
|
157
|
+
// If any search params provided, use search handler
|
|
158
|
+
if (
|
|
159
|
+
args.query ||
|
|
160
|
+
args.kqlQuery ||
|
|
161
|
+
args.from ||
|
|
162
|
+
args.to ||
|
|
163
|
+
args.subject ||
|
|
164
|
+
args.hasAttachments ||
|
|
165
|
+
args.unreadOnly ||
|
|
166
|
+
args.receivedAfter ||
|
|
167
|
+
args.receivedBefore ||
|
|
168
|
+
args.searchAllFolders
|
|
169
|
+
) {
|
|
170
|
+
return handleSearchEmails(args);
|
|
171
|
+
}
|
|
172
|
+
// Default: list mode
|
|
173
|
+
return handleListEmails(args);
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'read-email',
|
|
178
|
+
description:
|
|
179
|
+
'Read email content. Set headersMode=true for forensic headers (DKIM, SPF, Received, Message-ID).',
|
|
180
|
+
annotations: {
|
|
181
|
+
title: 'Read Email',
|
|
182
|
+
readOnlyHint: true,
|
|
183
|
+
openWorldHint: false,
|
|
184
|
+
},
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: 'object',
|
|
187
|
+
properties: {
|
|
188
|
+
id: {
|
|
189
|
+
type: 'string',
|
|
190
|
+
description: 'ID of the email to read',
|
|
191
|
+
},
|
|
192
|
+
headersMode: {
|
|
193
|
+
type: 'boolean',
|
|
194
|
+
description:
|
|
195
|
+
'Return forensic headers instead of email content (default: false)',
|
|
196
|
+
},
|
|
197
|
+
includeHeaders: {
|
|
198
|
+
type: 'boolean',
|
|
199
|
+
description:
|
|
200
|
+
'Include basic headers alongside email content (default: false)',
|
|
201
|
+
},
|
|
202
|
+
outputVerbosity: {
|
|
203
|
+
type: 'string',
|
|
204
|
+
enum: ['minimal', 'standard', 'full'],
|
|
205
|
+
description: 'Output detail level (default: standard)',
|
|
206
|
+
},
|
|
207
|
+
// Headers mode params
|
|
208
|
+
groupByType: {
|
|
209
|
+
type: 'boolean',
|
|
210
|
+
description:
|
|
211
|
+
'Group headers by category (headersMode only, default: false)',
|
|
212
|
+
},
|
|
213
|
+
importantOnly: {
|
|
214
|
+
type: 'boolean',
|
|
215
|
+
description:
|
|
216
|
+
'Show only important headers (headersMode only, default: false)',
|
|
217
|
+
},
|
|
218
|
+
raw: {
|
|
219
|
+
type: 'boolean',
|
|
220
|
+
description:
|
|
221
|
+
'Return raw JSON instead of Markdown (headersMode only, default: false)',
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
required: ['id'],
|
|
225
|
+
},
|
|
226
|
+
handler: async (args) => {
|
|
227
|
+
if (args.headersMode) {
|
|
228
|
+
return handleGetEmailHeaders(args);
|
|
229
|
+
}
|
|
230
|
+
return handleReadEmail(args);
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: 'send-email',
|
|
235
|
+
description:
|
|
236
|
+
'Compose and send an email. Use dryRun=true to preview without sending. Subject to rate limits and recipient allowlist when configured.',
|
|
237
|
+
annotations: {
|
|
238
|
+
title: 'Send Email',
|
|
239
|
+
readOnlyHint: false,
|
|
240
|
+
destructiveHint: true,
|
|
241
|
+
idempotentHint: false,
|
|
242
|
+
openWorldHint: true,
|
|
243
|
+
},
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
properties: {
|
|
247
|
+
to: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
description: 'Comma-separated recipient email addresses',
|
|
250
|
+
},
|
|
251
|
+
cc: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: 'Comma-separated CC email addresses',
|
|
254
|
+
},
|
|
255
|
+
bcc: {
|
|
256
|
+
type: 'string',
|
|
257
|
+
description: 'Comma-separated BCC email addresses',
|
|
258
|
+
},
|
|
259
|
+
subject: {
|
|
260
|
+
type: 'string',
|
|
261
|
+
description: 'Email subject',
|
|
262
|
+
},
|
|
263
|
+
body: {
|
|
264
|
+
type: 'string',
|
|
265
|
+
description: 'Email body (plain text or HTML)',
|
|
266
|
+
},
|
|
267
|
+
importance: {
|
|
268
|
+
type: 'string',
|
|
269
|
+
enum: ['normal', 'high', 'low'],
|
|
270
|
+
description: 'Email importance (default: normal)',
|
|
271
|
+
},
|
|
272
|
+
saveToSentItems: {
|
|
273
|
+
type: 'boolean',
|
|
274
|
+
description: 'Save to sent items (default: true)',
|
|
275
|
+
},
|
|
276
|
+
dryRun: {
|
|
277
|
+
type: 'boolean',
|
|
278
|
+
description:
|
|
279
|
+
'Preview email without sending (default: false). Returns composed email for review.',
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
required: ['to', 'subject', 'body'],
|
|
283
|
+
},
|
|
284
|
+
handler: handleSendEmail,
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'update-email',
|
|
288
|
+
description:
|
|
289
|
+
'Update email state. action=mark-read/mark-unread changes read status. action=flag sets follow-up flag. action=unflag clears flag. action=complete marks flag as done.',
|
|
290
|
+
annotations: {
|
|
291
|
+
title: 'Update Email',
|
|
292
|
+
readOnlyHint: false,
|
|
293
|
+
destructiveHint: false,
|
|
294
|
+
idempotentHint: true,
|
|
295
|
+
openWorldHint: false,
|
|
296
|
+
},
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: 'object',
|
|
299
|
+
properties: {
|
|
300
|
+
action: {
|
|
301
|
+
type: 'string',
|
|
302
|
+
enum: ['mark-read', 'mark-unread', 'flag', 'unflag', 'complete'],
|
|
303
|
+
description: 'Action to perform (required)',
|
|
304
|
+
},
|
|
305
|
+
id: {
|
|
306
|
+
type: 'string',
|
|
307
|
+
description:
|
|
308
|
+
'Single message ID (required for mark-read/mark-unread, or use instead of ids for flag actions)',
|
|
309
|
+
},
|
|
310
|
+
ids: {
|
|
311
|
+
type: 'array',
|
|
312
|
+
items: { type: 'string' },
|
|
313
|
+
description:
|
|
314
|
+
'Array of message IDs for batch flag/unflag/complete operations',
|
|
315
|
+
},
|
|
316
|
+
// Flag params
|
|
317
|
+
dueDateTime: {
|
|
318
|
+
type: 'string',
|
|
319
|
+
description: 'Due date/time for follow-up, ISO 8601 (action=flag)',
|
|
320
|
+
},
|
|
321
|
+
startDateTime: {
|
|
322
|
+
type: 'string',
|
|
323
|
+
description: 'Start date/time for follow-up, ISO 8601 (action=flag)',
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
required: ['action'],
|
|
327
|
+
},
|
|
328
|
+
handler: async (args) => {
|
|
329
|
+
switch (args.action) {
|
|
330
|
+
case 'mark-read':
|
|
331
|
+
return handleMarkAsRead({ id: args.id, isRead: true });
|
|
332
|
+
case 'mark-unread':
|
|
333
|
+
return handleMarkAsRead({ id: args.id, isRead: false });
|
|
334
|
+
case 'flag':
|
|
335
|
+
return handleSetMessageFlag({
|
|
336
|
+
messageId: args.id,
|
|
337
|
+
messageIds: args.ids,
|
|
338
|
+
dueDateTime: args.dueDateTime,
|
|
339
|
+
startDateTime: args.startDateTime,
|
|
340
|
+
});
|
|
341
|
+
case 'unflag':
|
|
342
|
+
return handleClearMessageFlag({
|
|
343
|
+
messageId: args.id,
|
|
344
|
+
messageIds: args.ids,
|
|
345
|
+
markComplete: false,
|
|
346
|
+
});
|
|
347
|
+
case 'complete':
|
|
348
|
+
return handleClearMessageFlag({
|
|
349
|
+
messageId: args.id,
|
|
350
|
+
messageIds: args.ids,
|
|
351
|
+
markComplete: true,
|
|
352
|
+
});
|
|
353
|
+
default:
|
|
354
|
+
return {
|
|
355
|
+
content: [
|
|
356
|
+
{
|
|
357
|
+
type: 'text',
|
|
358
|
+
text: "Invalid action. Use 'mark-read', 'mark-unread', 'flag', 'unflag', or 'complete'.",
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: 'attachments',
|
|
367
|
+
description:
|
|
368
|
+
'Manage email attachments. action=list shows attachments for a message. action=view shows content/metadata. action=download saves to disk.',
|
|
369
|
+
annotations: {
|
|
370
|
+
title: 'Attachments',
|
|
371
|
+
readOnlyHint: false,
|
|
372
|
+
destructiveHint: false,
|
|
373
|
+
openWorldHint: false,
|
|
374
|
+
},
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: 'object',
|
|
377
|
+
properties: {
|
|
378
|
+
action: {
|
|
379
|
+
type: 'string',
|
|
380
|
+
enum: ['list', 'view', 'download'],
|
|
381
|
+
description: 'Action to perform (default: list)',
|
|
382
|
+
},
|
|
383
|
+
messageId: {
|
|
384
|
+
type: 'string',
|
|
385
|
+
description: 'Email message ID (required)',
|
|
386
|
+
},
|
|
387
|
+
attachmentId: {
|
|
388
|
+
type: 'string',
|
|
389
|
+
description: 'Attachment ID (action=view/download, required)',
|
|
390
|
+
},
|
|
391
|
+
savePath: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
description:
|
|
394
|
+
'Directory to save file (action=download, default: current directory)',
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
required: ['messageId'],
|
|
398
|
+
},
|
|
399
|
+
handler: async (args) => {
|
|
400
|
+
const action = args.action || 'list';
|
|
401
|
+
switch (action) {
|
|
402
|
+
case 'view':
|
|
403
|
+
return handleGetAttachmentContent(args);
|
|
404
|
+
case 'download':
|
|
405
|
+
return handleDownloadAttachment(args);
|
|
406
|
+
case 'list':
|
|
407
|
+
default:
|
|
408
|
+
return handleListAttachments(args);
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: 'export',
|
|
414
|
+
description:
|
|
415
|
+
'Export emails. target=message exports one email. target=messages batch-exports. target=conversation exports a thread. target=mime gets raw MIME/EML content.',
|
|
416
|
+
annotations: {
|
|
417
|
+
title: 'Export Emails',
|
|
418
|
+
readOnlyHint: false,
|
|
419
|
+
destructiveHint: false,
|
|
420
|
+
openWorldHint: false,
|
|
421
|
+
},
|
|
422
|
+
inputSchema: {
|
|
423
|
+
type: 'object',
|
|
424
|
+
properties: {
|
|
425
|
+
target: {
|
|
426
|
+
type: 'string',
|
|
427
|
+
enum: ['message', 'messages', 'conversation', 'mime'],
|
|
428
|
+
description: 'Export target (default: message)',
|
|
429
|
+
},
|
|
430
|
+
// Single message export
|
|
431
|
+
id: {
|
|
432
|
+
type: 'string',
|
|
433
|
+
description: 'Email ID (target=message/mime, required)',
|
|
434
|
+
},
|
|
435
|
+
format: {
|
|
436
|
+
type: 'string',
|
|
437
|
+
enum: ['mime', 'eml', 'markdown', 'json', 'mbox', 'html'],
|
|
438
|
+
description:
|
|
439
|
+
'Export format (target=message: mime/eml/markdown/json, target=conversation: eml/mbox/markdown/json/html)',
|
|
440
|
+
},
|
|
441
|
+
savePath: {
|
|
442
|
+
type: 'string',
|
|
443
|
+
description: 'File path or directory (target=message)',
|
|
444
|
+
},
|
|
445
|
+
includeAttachments: {
|
|
446
|
+
type: 'boolean',
|
|
447
|
+
description:
|
|
448
|
+
'Include attachments (default: true for single, false for batch)',
|
|
449
|
+
},
|
|
450
|
+
// Batch export
|
|
451
|
+
emailIds: {
|
|
452
|
+
type: 'array',
|
|
453
|
+
items: { type: 'string' },
|
|
454
|
+
description: 'Email IDs to export (target=messages)',
|
|
455
|
+
},
|
|
456
|
+
searchQuery: {
|
|
457
|
+
type: 'object',
|
|
458
|
+
properties: {
|
|
459
|
+
folder: { type: 'string' },
|
|
460
|
+
from: { type: 'string' },
|
|
461
|
+
subject: { type: 'string' },
|
|
462
|
+
receivedAfter: { type: 'string' },
|
|
463
|
+
receivedBefore: { type: 'string' },
|
|
464
|
+
maxResults: { type: 'number' },
|
|
465
|
+
},
|
|
466
|
+
description:
|
|
467
|
+
'Search query to find emails (target=messages, alternative to emailIds)',
|
|
468
|
+
},
|
|
469
|
+
outputDir: {
|
|
470
|
+
type: 'string',
|
|
471
|
+
description:
|
|
472
|
+
'Output directory (target=messages/conversation, required)',
|
|
473
|
+
},
|
|
474
|
+
// Conversation export
|
|
475
|
+
conversationId: {
|
|
476
|
+
type: 'string',
|
|
477
|
+
description: 'Conversation ID (target=conversation, required)',
|
|
478
|
+
},
|
|
479
|
+
order: {
|
|
480
|
+
type: 'string',
|
|
481
|
+
enum: ['chronological', 'reverse'],
|
|
482
|
+
description:
|
|
483
|
+
'Message order (target=conversation, default: chronological)',
|
|
484
|
+
},
|
|
485
|
+
// MIME params
|
|
486
|
+
headersOnly: {
|
|
487
|
+
type: 'boolean',
|
|
488
|
+
description: 'MIME headers only, no body (target=mime)',
|
|
489
|
+
},
|
|
490
|
+
base64: {
|
|
491
|
+
type: 'boolean',
|
|
492
|
+
description: 'Return base64 encoded (target=mime)',
|
|
493
|
+
},
|
|
494
|
+
maxSize: {
|
|
495
|
+
type: 'number',
|
|
496
|
+
description: 'Max content size in bytes (target=mime, default: 1MB)',
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
required: [],
|
|
500
|
+
},
|
|
501
|
+
handler: async (args) => {
|
|
502
|
+
const target = args.target || 'message';
|
|
503
|
+
switch (target) {
|
|
504
|
+
case 'messages':
|
|
505
|
+
return handleBatchExportEmails(args);
|
|
506
|
+
case 'conversation':
|
|
507
|
+
return handleExportConversation(args);
|
|
508
|
+
case 'mime':
|
|
509
|
+
return handleGetMimeContent(args);
|
|
510
|
+
case 'message':
|
|
511
|
+
default:
|
|
512
|
+
return handleExportEmail(args);
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
];
|
|
517
|
+
|
|
518
|
+
module.exports = {
|
|
519
|
+
emailTools,
|
|
520
|
+
handleListEmails,
|
|
521
|
+
handleSearchEmails,
|
|
522
|
+
handleSearchByMessageId,
|
|
523
|
+
handleReadEmail,
|
|
524
|
+
handleSendEmail,
|
|
525
|
+
handleMarkAsRead,
|
|
526
|
+
handleListAttachments,
|
|
527
|
+
handleDownloadAttachment,
|
|
528
|
+
handleGetAttachmentContent,
|
|
529
|
+
handleExportEmail,
|
|
530
|
+
handleBatchExportEmails,
|
|
531
|
+
handleListEmailsDelta,
|
|
532
|
+
handleGetEmailHeaders,
|
|
533
|
+
handleGetMimeContent,
|
|
534
|
+
handleListConversations,
|
|
535
|
+
handleGetConversation,
|
|
536
|
+
handleExportConversation,
|
|
537
|
+
};
|
package/email/list.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List emails functionality
|
|
3
|
+
*
|
|
4
|
+
* Token-efficient implementation with outputVerbosity support and Markdown formatting.
|
|
5
|
+
*/
|
|
6
|
+
const config = require('../config');
|
|
7
|
+
const {
|
|
8
|
+
callGraphAPI: _callGraphAPI,
|
|
9
|
+
callGraphAPIPaginated,
|
|
10
|
+
} = require('../utils/graph-api');
|
|
11
|
+
const { ensureAuthenticated } = require('../auth');
|
|
12
|
+
const { resolveFolderPath } = require('./folder-utils');
|
|
13
|
+
const {
|
|
14
|
+
formatEmailList,
|
|
15
|
+
VERBOSITY,
|
|
16
|
+
DEFAULT_LIMITS,
|
|
17
|
+
} = require('../utils/response-formatter');
|
|
18
|
+
const { getEmailFields } = require('../utils/field-presets');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maps verbosity level to field preset
|
|
22
|
+
* @param {string} verbosity - minimal, standard, or full
|
|
23
|
+
* @returns {string} - field preset name
|
|
24
|
+
*/
|
|
25
|
+
function getFieldPresetForVerbosity(verbosity) {
|
|
26
|
+
switch (verbosity) {
|
|
27
|
+
case VERBOSITY.MINIMAL:
|
|
28
|
+
return 'list'; // id, subject, from, receivedDateTime, isRead
|
|
29
|
+
case VERBOSITY.FULL:
|
|
30
|
+
return 'search'; // Includes toRecipients, bodyPreview, hasAttachments, importance
|
|
31
|
+
case VERBOSITY.STANDARD:
|
|
32
|
+
default:
|
|
33
|
+
return 'list'; // Standard uses list preset but formats with more detail
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* List emails handler
|
|
39
|
+
* @param {object} args - Tool arguments
|
|
40
|
+
* @param {string} [args.folder] - Folder to list (default: inbox)
|
|
41
|
+
* @param {number} [args.count] - Number of emails (default: 25, max: 50)
|
|
42
|
+
* @param {string} [args.outputVerbosity] - minimal, standard, or full (default: standard)
|
|
43
|
+
* @returns {object} - MCP response with Markdown formatted content
|
|
44
|
+
*/
|
|
45
|
+
async function handleListEmails(args) {
|
|
46
|
+
const folder = args.folder || 'inbox';
|
|
47
|
+
const requestedCount = args.count || DEFAULT_LIMITS.listEmails; // Default 25 (was 10)
|
|
48
|
+
const verbosity = args.outputVerbosity || VERBOSITY.STANDARD;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Get access token
|
|
52
|
+
const accessToken = await ensureAuthenticated();
|
|
53
|
+
|
|
54
|
+
// Resolve the folder path
|
|
55
|
+
const endpoint = await resolveFolderPath(accessToken, folder);
|
|
56
|
+
|
|
57
|
+
// Select fields based on verbosity level
|
|
58
|
+
const fieldPreset = getFieldPresetForVerbosity(verbosity);
|
|
59
|
+
const selectFields = getEmailFields(fieldPreset);
|
|
60
|
+
|
|
61
|
+
// Add query parameters
|
|
62
|
+
const queryParams = {
|
|
63
|
+
$top: Math.min(config.MAX_RESULT_COUNT, requestedCount),
|
|
64
|
+
$orderby: 'receivedDateTime desc',
|
|
65
|
+
$select: selectFields,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Make API call with pagination support
|
|
69
|
+
const response = await callGraphAPIPaginated(
|
|
70
|
+
accessToken,
|
|
71
|
+
'GET',
|
|
72
|
+
endpoint,
|
|
73
|
+
queryParams,
|
|
74
|
+
requestedCount
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (!response.value || response.value.length === 0) {
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: `No emails found in ${folder}.`,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Build response metadata
|
|
89
|
+
const meta = {
|
|
90
|
+
returned: response.value.length,
|
|
91
|
+
totalAvailable: response['@odata.count'] || null,
|
|
92
|
+
hasMore: !!response['@odata.nextLink'],
|
|
93
|
+
verbosity: verbosity,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Format results using response-formatter (returns Markdown)
|
|
97
|
+
const formattedOutput = formatEmailList(
|
|
98
|
+
response.value,
|
|
99
|
+
folder,
|
|
100
|
+
verbosity,
|
|
101
|
+
meta
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: 'text',
|
|
108
|
+
text: formattedOutput,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
_meta: meta,
|
|
112
|
+
};
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error.message === 'Authentication required') {
|
|
115
|
+
return {
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: 'text',
|
|
119
|
+
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: 'text',
|
|
129
|
+
text: `Error listing emails: ${error.message}`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = handleListEmails;
|