@littlebearapps/outlook-assistant 3.5.2 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/advanced/index.js +5 -5
- package/auth/device-code.js +3 -1
- package/auth/oauth-server.js +4 -4
- package/auth/token-manager.js +2 -2
- package/calendar/list.js +3 -3
- package/categories/index.js +4 -4
- package/contacts/index.js +11 -18
- package/email/conversations.js +19 -17
- package/email/delta.js +1 -1
- package/email/draft.js +482 -0
- package/email/folder-utils.js +1 -1
- package/email/headers.js +10 -10
- package/email/index.js +80 -0
- package/email/list.js +1 -1
- package/email/mime.js +13 -12
- package/email/search.js +3 -2
- package/email/send.js +1 -1
- package/folder/list.js +1 -1
- package/llms.txt +8 -7
- package/package.json +2 -2
- package/rules/create.js +85 -161
- package/rules/index.js +162 -29
- package/rules/list.js +144 -62
- package/rules/rule-builder.js +284 -0
- package/rules/update.js +213 -0
- package/settings/index.js +10 -22
- package/utils/field-presets.js +20 -0
- package/utils/graph-api.js +9 -1
- package/utils/response-formatter.js +8 -6
- package/utils/safety.js +113 -0
package/email/draft.js
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draft email functionality
|
|
3
|
+
*
|
|
4
|
+
* Supports creating, updating, sending, and deleting drafts,
|
|
5
|
+
* plus creating reply/reply-all/forward drafts from existing messages.
|
|
6
|
+
*/
|
|
7
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
8
|
+
const { ensureAuthenticated } = require('../auth');
|
|
9
|
+
const {
|
|
10
|
+
checkRateLimit,
|
|
11
|
+
checkRecipientAllowlist,
|
|
12
|
+
formatDryRunPreview,
|
|
13
|
+
} = require('../utils/safety');
|
|
14
|
+
const { handleGetMailTips } = require('./mail-tips');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Format comma-separated email string into Graph API recipient objects
|
|
18
|
+
* @param {string} recipientString - Comma-separated email addresses
|
|
19
|
+
* @returns {Array<{emailAddress: {address: string}}>}
|
|
20
|
+
*/
|
|
21
|
+
function formatRecipients(recipientString) {
|
|
22
|
+
if (!recipientString) return [];
|
|
23
|
+
return recipientString.split(',').map((email) => ({
|
|
24
|
+
emailAddress: { address: email.trim() },
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Auto-detect HTML vs plain text body
|
|
30
|
+
* @param {string} body - Email body content
|
|
31
|
+
* @returns {'html'|'text'}
|
|
32
|
+
*/
|
|
33
|
+
function detectContentType(body) {
|
|
34
|
+
if (!body) return 'text';
|
|
35
|
+
return /<(html|div|p|h[1-6]|br|table|ul|ol|li|span|a\s|img|strong|em|b|i)\b/i.test(
|
|
36
|
+
body
|
|
37
|
+
)
|
|
38
|
+
? 'html'
|
|
39
|
+
: 'text';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build a message object from draft parameters
|
|
44
|
+
* @param {object} args - Draft parameters
|
|
45
|
+
* @returns {object} - Graph API message object
|
|
46
|
+
*/
|
|
47
|
+
function buildMessageObject(args) {
|
|
48
|
+
const { to, cc, bcc, subject, body, importance } = args;
|
|
49
|
+
const message = {};
|
|
50
|
+
|
|
51
|
+
if (subject !== undefined) message.subject = subject;
|
|
52
|
+
if (body !== undefined) {
|
|
53
|
+
message.body = {
|
|
54
|
+
contentType: detectContentType(body),
|
|
55
|
+
content: body,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (importance) message.importance = importance;
|
|
59
|
+
|
|
60
|
+
const toRecipients = formatRecipients(to);
|
|
61
|
+
const ccRecipients = formatRecipients(cc);
|
|
62
|
+
const bccRecipients = formatRecipients(bcc);
|
|
63
|
+
|
|
64
|
+
if (toRecipients.length > 0) message.toRecipients = toRecipients;
|
|
65
|
+
if (ccRecipients.length > 0) message.ccRecipients = ccRecipients;
|
|
66
|
+
if (bccRecipients.length > 0) message.bccRecipients = bccRecipients;
|
|
67
|
+
|
|
68
|
+
return message;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Format a draft response with key details
|
|
73
|
+
* @param {object} draft - Graph API message response
|
|
74
|
+
* @param {string} actionLabel - Human-readable action (e.g. "created", "updated")
|
|
75
|
+
* @returns {object} - MCP response
|
|
76
|
+
*/
|
|
77
|
+
function formatDraftResponse(draft, actionLabel) {
|
|
78
|
+
const to = (draft.toRecipients || [])
|
|
79
|
+
.map((r) => r.emailAddress?.address)
|
|
80
|
+
.join(', ');
|
|
81
|
+
|
|
82
|
+
let text = `Draft ${actionLabel}.\n\n`;
|
|
83
|
+
text += `**ID**: \`${draft.id}\`\n`;
|
|
84
|
+
if (draft.subject) text += `**Subject**: ${draft.subject}\n`;
|
|
85
|
+
if (to) text += `**To**: ${to}\n`;
|
|
86
|
+
if (draft.lastModifiedDateTime) {
|
|
87
|
+
text += `**Modified**: ${draft.lastModifiedDateTime}\n`;
|
|
88
|
+
}
|
|
89
|
+
if (draft.hasAttachments) text += `**Attachments**: yes\n`;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: 'text', text }],
|
|
93
|
+
_meta: { draftId: draft.id },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Draft handler — routes to action-specific logic
|
|
99
|
+
* @param {object} args - Tool arguments
|
|
100
|
+
* @returns {object} - MCP response
|
|
101
|
+
*/
|
|
102
|
+
async function handleDraft(args) {
|
|
103
|
+
const { action } = args;
|
|
104
|
+
|
|
105
|
+
if (!action) {
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: 'text',
|
|
110
|
+
text: "Action is required. Use 'create', 'update', 'send', 'delete', 'reply', 'reply-all', or 'forward'.",
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
switch (action) {
|
|
117
|
+
case 'create':
|
|
118
|
+
return handleCreateDraft(args);
|
|
119
|
+
case 'update':
|
|
120
|
+
return handleUpdateDraft(args);
|
|
121
|
+
case 'send':
|
|
122
|
+
return handleSendDraft(args);
|
|
123
|
+
case 'delete':
|
|
124
|
+
return handleDeleteDraft(args);
|
|
125
|
+
case 'reply':
|
|
126
|
+
return handleReplyDraft(args, 'createReply');
|
|
127
|
+
case 'reply-all':
|
|
128
|
+
return handleReplyDraft(args, 'createReplyAll');
|
|
129
|
+
case 'forward':
|
|
130
|
+
return handleForwardDraft(args);
|
|
131
|
+
default:
|
|
132
|
+
return {
|
|
133
|
+
content: [
|
|
134
|
+
{
|
|
135
|
+
type: 'text',
|
|
136
|
+
text: `Invalid action '${action}'. Use 'create', 'update', 'send', 'delete', 'reply', 'reply-all', or 'forward'.`,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a new draft
|
|
145
|
+
*/
|
|
146
|
+
async function handleCreateDraft(args) {
|
|
147
|
+
const { dryRun = false, checkRecipients: doCheckRecipients = false } = args;
|
|
148
|
+
const message = buildMessageObject(args);
|
|
149
|
+
|
|
150
|
+
// Check recipient allowlist if recipients specified
|
|
151
|
+
const allRecipients = [
|
|
152
|
+
...(message.toRecipients || []),
|
|
153
|
+
...(message.ccRecipients || []),
|
|
154
|
+
...(message.bccRecipients || []),
|
|
155
|
+
];
|
|
156
|
+
if (allRecipients.length > 0) {
|
|
157
|
+
const allowlistError = checkRecipientAllowlist(allRecipients);
|
|
158
|
+
if (allowlistError) return allowlistError;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Pre-save recipient validation via mail-tips
|
|
162
|
+
if (doCheckRecipients && allRecipients.length > 0) {
|
|
163
|
+
const allAddresses = allRecipients.map((r) => r.emailAddress.address);
|
|
164
|
+
const tipsResult = await handleGetMailTips({ recipients: allAddresses });
|
|
165
|
+
const tipsText = tipsResult.content[0]?.text || '';
|
|
166
|
+
|
|
167
|
+
if (dryRun) {
|
|
168
|
+
const preview = formatDryRunPreview({ message, saveToSentItems: true });
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: 'text',
|
|
173
|
+
text: `${tipsText}\n\n---\n\n${preview.content[0].text.replace(
|
|
174
|
+
'Email NOT sent',
|
|
175
|
+
'Draft NOT saved'
|
|
176
|
+
)}`,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
_meta: { mailTips: tipsResult._meta },
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Dry-run mode: preview without saving
|
|
185
|
+
if (dryRun) {
|
|
186
|
+
const preview = formatDryRunPreview({ message, saveToSentItems: true });
|
|
187
|
+
return {
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
type: 'text',
|
|
191
|
+
text: preview.content[0].text.replace(
|
|
192
|
+
'Email NOT sent',
|
|
193
|
+
'Draft NOT saved'
|
|
194
|
+
),
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Rate limit check
|
|
201
|
+
const rateLimitError = checkRateLimit('draft');
|
|
202
|
+
if (rateLimitError) return rateLimitError;
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const accessToken = await ensureAuthenticated();
|
|
206
|
+
const draft = await callGraphAPI(
|
|
207
|
+
accessToken,
|
|
208
|
+
'POST',
|
|
209
|
+
'me/messages',
|
|
210
|
+
message
|
|
211
|
+
);
|
|
212
|
+
return formatDraftResponse(draft, 'created');
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return handleError('creating draft', error);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Update an existing draft
|
|
220
|
+
*/
|
|
221
|
+
async function handleUpdateDraft(args) {
|
|
222
|
+
const { id } = args;
|
|
223
|
+
|
|
224
|
+
if (!id) {
|
|
225
|
+
return {
|
|
226
|
+
content: [
|
|
227
|
+
{ type: 'text', text: 'Draft ID (id) is required for update.' },
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const message = buildMessageObject(args);
|
|
233
|
+
|
|
234
|
+
// Check recipient allowlist if recipients changed
|
|
235
|
+
const allRecipients = [
|
|
236
|
+
...(message.toRecipients || []),
|
|
237
|
+
...(message.ccRecipients || []),
|
|
238
|
+
...(message.bccRecipients || []),
|
|
239
|
+
];
|
|
240
|
+
if (allRecipients.length > 0) {
|
|
241
|
+
const allowlistError = checkRecipientAllowlist(allRecipients);
|
|
242
|
+
if (allowlistError) return allowlistError;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Rate limit check
|
|
246
|
+
const rateLimitError = checkRateLimit('draft');
|
|
247
|
+
if (rateLimitError) return rateLimitError;
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const accessToken = await ensureAuthenticated();
|
|
251
|
+
const draft = await callGraphAPI(
|
|
252
|
+
accessToken,
|
|
253
|
+
'PATCH',
|
|
254
|
+
`me/messages/${id}`,
|
|
255
|
+
message
|
|
256
|
+
);
|
|
257
|
+
return formatDraftResponse(draft, 'updated');
|
|
258
|
+
} catch (error) {
|
|
259
|
+
return handleError('updating draft', error);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Send an existing draft
|
|
265
|
+
*/
|
|
266
|
+
async function handleSendDraft(args) {
|
|
267
|
+
const { id } = args;
|
|
268
|
+
|
|
269
|
+
if (!id) {
|
|
270
|
+
return {
|
|
271
|
+
content: [{ type: 'text', text: 'Draft ID (id) is required for send.' }],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Rate limit via send-email counter (shares limit with direct sends)
|
|
276
|
+
const rateLimitError = checkRateLimit('send-email');
|
|
277
|
+
if (rateLimitError) return rateLimitError;
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const accessToken = await ensureAuthenticated();
|
|
281
|
+
await callGraphAPI(accessToken, 'POST', `me/messages/${id}/send`);
|
|
282
|
+
return {
|
|
283
|
+
content: [
|
|
284
|
+
{
|
|
285
|
+
type: 'text',
|
|
286
|
+
text: `Draft sent successfully.\n\n**Note**: The draft ID \`${id}\` is no longer valid — the message has been moved to Sent Items with a new ID.`,
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
};
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return handleError('sending draft', error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Delete a draft
|
|
297
|
+
*/
|
|
298
|
+
async function handleDeleteDraft(args) {
|
|
299
|
+
const { id } = args;
|
|
300
|
+
|
|
301
|
+
if (!id) {
|
|
302
|
+
return {
|
|
303
|
+
content: [
|
|
304
|
+
{ type: 'text', text: 'Draft ID (id) is required for delete.' },
|
|
305
|
+
],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const accessToken = await ensureAuthenticated();
|
|
311
|
+
await callGraphAPI(accessToken, 'DELETE', `me/messages/${id}`);
|
|
312
|
+
return {
|
|
313
|
+
content: [
|
|
314
|
+
{
|
|
315
|
+
type: 'text',
|
|
316
|
+
text: `Draft \`${id}\` deleted.`,
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
};
|
|
320
|
+
} catch (error) {
|
|
321
|
+
return handleError('deleting draft', error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Create a reply or reply-all draft from an existing message
|
|
327
|
+
*/
|
|
328
|
+
async function handleReplyDraft(args, endpoint) {
|
|
329
|
+
const { id, body, comment } = args;
|
|
330
|
+
|
|
331
|
+
if (!id) {
|
|
332
|
+
return {
|
|
333
|
+
content: [
|
|
334
|
+
{
|
|
335
|
+
type: 'text',
|
|
336
|
+
text: `Message ID (id) is required for ${endpoint === 'createReplyAll' ? 'reply-all' : 'reply'}.`,
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (comment && body) {
|
|
343
|
+
return {
|
|
344
|
+
content: [
|
|
345
|
+
{
|
|
346
|
+
type: 'text',
|
|
347
|
+
text: 'Cannot use both comment and body. Use comment for a short prepended note, or body for full HTML/text content.',
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const requestBody = {};
|
|
354
|
+
if (comment) {
|
|
355
|
+
requestBody.comment = comment;
|
|
356
|
+
} else if (body) {
|
|
357
|
+
requestBody.message = {
|
|
358
|
+
body: {
|
|
359
|
+
contentType: detectContentType(body),
|
|
360
|
+
content: body,
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
const accessToken = await ensureAuthenticated();
|
|
367
|
+
const draft = await callGraphAPI(
|
|
368
|
+
accessToken,
|
|
369
|
+
'POST',
|
|
370
|
+
`me/messages/${id}/${endpoint}`,
|
|
371
|
+
Object.keys(requestBody).length > 0 ? requestBody : null
|
|
372
|
+
);
|
|
373
|
+
const label =
|
|
374
|
+
endpoint === 'createReplyAll'
|
|
375
|
+
? 'reply-all draft created'
|
|
376
|
+
: 'reply draft created';
|
|
377
|
+
return formatDraftResponse(draft, label);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
return handleError(
|
|
380
|
+
`creating ${endpoint === 'createReplyAll' ? 'reply-all' : 'reply'} draft`,
|
|
381
|
+
error
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Create a forward draft from an existing message
|
|
388
|
+
*/
|
|
389
|
+
async function handleForwardDraft(args) {
|
|
390
|
+
const { id, to, body, comment } = args;
|
|
391
|
+
|
|
392
|
+
if (!id) {
|
|
393
|
+
return {
|
|
394
|
+
content: [
|
|
395
|
+
{ type: 'text', text: 'Message ID (id) is required for forward.' },
|
|
396
|
+
],
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (!to) {
|
|
401
|
+
return {
|
|
402
|
+
content: [
|
|
403
|
+
{
|
|
404
|
+
type: 'text',
|
|
405
|
+
text: 'Forward recipient (to) is required for forward.',
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (comment && body) {
|
|
412
|
+
return {
|
|
413
|
+
content: [
|
|
414
|
+
{
|
|
415
|
+
type: 'text',
|
|
416
|
+
text: 'Cannot use both comment and body. Use comment for a short prepended note, or body for full HTML/text content.',
|
|
417
|
+
},
|
|
418
|
+
],
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const toRecipients = formatRecipients(to);
|
|
423
|
+
|
|
424
|
+
// Check recipient allowlist
|
|
425
|
+
const allowlistError = checkRecipientAllowlist(toRecipients);
|
|
426
|
+
if (allowlistError) return allowlistError;
|
|
427
|
+
|
|
428
|
+
const requestBody = {
|
|
429
|
+
toRecipients,
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if (comment) {
|
|
433
|
+
requestBody.comment = comment;
|
|
434
|
+
} else if (body) {
|
|
435
|
+
requestBody.message = {
|
|
436
|
+
body: {
|
|
437
|
+
contentType: detectContentType(body),
|
|
438
|
+
content: body,
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
const accessToken = await ensureAuthenticated();
|
|
445
|
+
const draft = await callGraphAPI(
|
|
446
|
+
accessToken,
|
|
447
|
+
'POST',
|
|
448
|
+
`me/messages/${id}/createForward`,
|
|
449
|
+
requestBody
|
|
450
|
+
);
|
|
451
|
+
return formatDraftResponse(draft, 'forward draft created');
|
|
452
|
+
} catch (error) {
|
|
453
|
+
return handleError('creating forward draft', error);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Standard error handler
|
|
459
|
+
*/
|
|
460
|
+
function handleError(actionLabel, error) {
|
|
461
|
+
if (error.message === 'Authentication required') {
|
|
462
|
+
return {
|
|
463
|
+
content: [
|
|
464
|
+
{
|
|
465
|
+
type: 'text',
|
|
466
|
+
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
content: [
|
|
474
|
+
{
|
|
475
|
+
type: 'text',
|
|
476
|
+
text: `Error ${actionLabel}: ${error.message}`,
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
module.exports = handleDraft;
|
package/email/folder-utils.js
CHANGED
|
@@ -54,7 +54,7 @@ const WELL_KNOWN_FOLDERS = {
|
|
|
54
54
|
async function resolveFolderPath(accessToken, folderName) {
|
|
55
55
|
// Default to inbox if no folder specified
|
|
56
56
|
if (!folderName) {
|
|
57
|
-
return WELL_KNOWN_FOLDERS
|
|
57
|
+
return WELL_KNOWN_FOLDERS.inbox;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// Check if it's a well-known folder (case-insensitive)
|
package/email/headers.js
CHANGED
|
@@ -49,7 +49,7 @@ function formatHeaders(headers, options = {}) {
|
|
|
49
49
|
return '*No headers available*';
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
const output = [];
|
|
53
53
|
|
|
54
54
|
if (groupByType) {
|
|
55
55
|
// Group headers by category
|
|
@@ -65,7 +65,7 @@ function formatHeaders(headers, options = {}) {
|
|
|
65
65
|
headers.forEach((h) => {
|
|
66
66
|
const name = h.name;
|
|
67
67
|
if (['Message-ID', 'In-Reply-To', 'References'].includes(name)) {
|
|
68
|
-
groups
|
|
68
|
+
groups.Threading.push(h);
|
|
69
69
|
} else if (
|
|
70
70
|
[
|
|
71
71
|
'Authentication-Results',
|
|
@@ -74,9 +74,9 @@ function formatHeaders(headers, options = {}) {
|
|
|
74
74
|
'Received-SPF',
|
|
75
75
|
].includes(name)
|
|
76
76
|
) {
|
|
77
|
-
groups
|
|
77
|
+
groups.Authentication.push(h);
|
|
78
78
|
} else if (name === 'Received' || name.startsWith('X-MS-Exchange')) {
|
|
79
|
-
groups
|
|
79
|
+
groups.Delivery.push(h);
|
|
80
80
|
} else if (
|
|
81
81
|
name.includes('Antispam') ||
|
|
82
82
|
name.includes('SCL') ||
|
|
@@ -88,9 +88,9 @@ function formatHeaders(headers, options = {}) {
|
|
|
88
88
|
name
|
|
89
89
|
)
|
|
90
90
|
) {
|
|
91
|
-
groups
|
|
91
|
+
groups.Content.push(h);
|
|
92
92
|
} else {
|
|
93
|
-
groups
|
|
93
|
+
groups.Other.push(h);
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
|
|
@@ -100,7 +100,7 @@ function formatHeaders(headers, options = {}) {
|
|
|
100
100
|
groupHeaders.forEach((h) => {
|
|
101
101
|
// Truncate very long values
|
|
102
102
|
const value =
|
|
103
|
-
h.value.length > 500 ? h.value.substring(0, 500)
|
|
103
|
+
h.value.length > 500 ? `${h.value.substring(0, 500)}...` : h.value;
|
|
104
104
|
output.push(`**${h.name}**: \`${value}\``);
|
|
105
105
|
});
|
|
106
106
|
}
|
|
@@ -123,7 +123,7 @@ function formatHeaders(headers, options = {}) {
|
|
|
123
123
|
output.push('## Key Headers\n');
|
|
124
124
|
importantHeaders.forEach((h) => {
|
|
125
125
|
const value =
|
|
126
|
-
h.value.length > 300 ? h.value.substring(0, 300)
|
|
126
|
+
h.value.length > 300 ? `${h.value.substring(0, 300)}...` : h.value;
|
|
127
127
|
output.push(`**${h.name}**:`);
|
|
128
128
|
output.push('```');
|
|
129
129
|
output.push(value);
|
|
@@ -135,7 +135,7 @@ function formatHeaders(headers, options = {}) {
|
|
|
135
135
|
output.push('\n## All Other Headers\n');
|
|
136
136
|
otherHeaders.forEach((h) => {
|
|
137
137
|
const value =
|
|
138
|
-
h.value.length > 200 ? h.value.substring(0, 200)
|
|
138
|
+
h.value.length > 200 ? `${h.value.substring(0, 200)}...` : h.value;
|
|
139
139
|
output.push(`- **${h.name}**: \`${value}\``);
|
|
140
140
|
});
|
|
141
141
|
}
|
|
@@ -249,7 +249,7 @@ async function handleGetEmailHeaders(args) {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
// Build formatted output
|
|
252
|
-
|
|
252
|
+
const output = [];
|
|
253
253
|
output.push(`# Email Headers\n`);
|
|
254
254
|
output.push(`**Subject**: ${email.subject || '(no subject)'}`);
|
|
255
255
|
output.push(
|
package/email/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const {
|
|
|
23
23
|
handleExportConversation,
|
|
24
24
|
} = require('./conversations');
|
|
25
25
|
const { handleGetMailTips } = require('./mail-tips');
|
|
26
|
+
const handleDraft = require('./draft');
|
|
26
27
|
|
|
27
28
|
// Import flag handlers from advanced module
|
|
28
29
|
const { handleSetMessageFlag, handleClearMessageFlag } = require('../advanced');
|
|
@@ -289,6 +290,84 @@ const emailTools = [
|
|
|
289
290
|
},
|
|
290
291
|
handler: handleSendEmail,
|
|
291
292
|
},
|
|
293
|
+
{
|
|
294
|
+
name: 'draft',
|
|
295
|
+
description:
|
|
296
|
+
'Manage email drafts. action=create saves a new draft. action=update edits an existing draft. action=send sends a draft. action=delete removes a draft. action=reply/reply-all/forward creates a reply or forward draft from an existing message.',
|
|
297
|
+
annotations: {
|
|
298
|
+
title: 'Draft Operations',
|
|
299
|
+
readOnlyHint: false,
|
|
300
|
+
destructiveHint: true,
|
|
301
|
+
idempotentHint: false,
|
|
302
|
+
openWorldHint: true,
|
|
303
|
+
},
|
|
304
|
+
inputSchema: {
|
|
305
|
+
type: 'object',
|
|
306
|
+
properties: {
|
|
307
|
+
action: {
|
|
308
|
+
type: 'string',
|
|
309
|
+
enum: [
|
|
310
|
+
'create',
|
|
311
|
+
'update',
|
|
312
|
+
'send',
|
|
313
|
+
'delete',
|
|
314
|
+
'reply',
|
|
315
|
+
'reply-all',
|
|
316
|
+
'forward',
|
|
317
|
+
],
|
|
318
|
+
description: 'Action to perform (required)',
|
|
319
|
+
},
|
|
320
|
+
id: {
|
|
321
|
+
type: 'string',
|
|
322
|
+
description:
|
|
323
|
+
'Draft or message ID. Required for update/send/delete/reply/reply-all/forward.',
|
|
324
|
+
},
|
|
325
|
+
to: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
description:
|
|
328
|
+
'Comma-separated recipient email addresses (optional for create/update, required for forward)',
|
|
329
|
+
},
|
|
330
|
+
cc: {
|
|
331
|
+
type: 'string',
|
|
332
|
+
description: 'Comma-separated CC email addresses',
|
|
333
|
+
},
|
|
334
|
+
bcc: {
|
|
335
|
+
type: 'string',
|
|
336
|
+
description: 'Comma-separated BCC email addresses',
|
|
337
|
+
},
|
|
338
|
+
subject: {
|
|
339
|
+
type: 'string',
|
|
340
|
+
description: 'Email subject',
|
|
341
|
+
},
|
|
342
|
+
body: {
|
|
343
|
+
type: 'string',
|
|
344
|
+
description: 'Email body (plain text or HTML)',
|
|
345
|
+
},
|
|
346
|
+
importance: {
|
|
347
|
+
type: 'string',
|
|
348
|
+
enum: ['normal', 'high', 'low'],
|
|
349
|
+
description: 'Email importance (default: normal)',
|
|
350
|
+
},
|
|
351
|
+
comment: {
|
|
352
|
+
type: 'string',
|
|
353
|
+
description:
|
|
354
|
+
'Comment text for reply/forward (prepended to original message). Cannot combine with body.',
|
|
355
|
+
},
|
|
356
|
+
dryRun: {
|
|
357
|
+
type: 'boolean',
|
|
358
|
+
description:
|
|
359
|
+
'Preview draft without saving (action=create only, default: false)',
|
|
360
|
+
},
|
|
361
|
+
checkRecipients: {
|
|
362
|
+
type: 'boolean',
|
|
363
|
+
description:
|
|
364
|
+
'Check recipients for out-of-office, delivery restrictions before saving (action=create, default: false)',
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
required: ['action'],
|
|
368
|
+
},
|
|
369
|
+
handler: handleDraft,
|
|
370
|
+
},
|
|
292
371
|
{
|
|
293
372
|
name: 'update-email',
|
|
294
373
|
description:
|
|
@@ -559,6 +638,7 @@ const emailTools = [
|
|
|
559
638
|
|
|
560
639
|
module.exports = {
|
|
561
640
|
emailTools,
|
|
641
|
+
handleDraft,
|
|
562
642
|
handleListEmails,
|
|
563
643
|
handleSearchEmails,
|
|
564
644
|
handleSearchByMessageId,
|
package/email/list.js
CHANGED
|
@@ -89,7 +89,7 @@ async function handleListEmails(args) {
|
|
|
89
89
|
const meta = {
|
|
90
90
|
returned: response.value.length,
|
|
91
91
|
totalAvailable: response['@odata.count'] || null,
|
|
92
|
-
hasMore:
|
|
92
|
+
hasMore: Boolean(response['@odata.nextLink']),
|
|
93
93
|
verbosity: verbosity,
|
|
94
94
|
};
|
|
95
95
|
|