@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,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachment handling for Outlook Assistant server
|
|
3
|
+
* Provides tools to list and download email attachments via Microsoft Graph API
|
|
4
|
+
*/
|
|
5
|
+
const _https = require('https'); // Reserved for future use
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const _config = require('../config'); // Reserved for future use
|
|
9
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
10
|
+
const { ensureAuthenticated } = require('../auth');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* List attachments for a specific email
|
|
14
|
+
* @param {object} args - Tool arguments
|
|
15
|
+
* @param {string} args.messageId - The ID of the email message
|
|
16
|
+
* @returns {object} - MCP response with attachment list
|
|
17
|
+
*/
|
|
18
|
+
async function handleListAttachments(args) {
|
|
19
|
+
const messageId = args.messageId;
|
|
20
|
+
|
|
21
|
+
if (!messageId) {
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: 'text',
|
|
26
|
+
text: 'Error: messageId is required',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const accessToken = await ensureAuthenticated();
|
|
34
|
+
|
|
35
|
+
// Call Graph API to get attachments
|
|
36
|
+
const endpoint = `/me/messages/${messageId}/attachments`;
|
|
37
|
+
const params = {
|
|
38
|
+
$select: 'id,name,contentType,size,isInline',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
console.error(`Fetching attachments for message: ${messageId}`);
|
|
42
|
+
const response = await callGraphAPI(
|
|
43
|
+
accessToken,
|
|
44
|
+
'GET',
|
|
45
|
+
endpoint,
|
|
46
|
+
null,
|
|
47
|
+
params
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (!response.value || response.value.length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: 'text',
|
|
55
|
+
text: 'No attachments found for this email.',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Format attachment list
|
|
62
|
+
const attachmentList = response.value
|
|
63
|
+
.map((att, index) => {
|
|
64
|
+
const sizeKB = (att.size / 1024).toFixed(1);
|
|
65
|
+
const inline = att.isInline ? ' [inline]' : '';
|
|
66
|
+
return `${index + 1}. ${att.name}${inline}\n Type: ${att.contentType}\n Size: ${sizeKB} KB\n ID: ${att.id}`;
|
|
67
|
+
})
|
|
68
|
+
.join('\n\n');
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: `Found ${response.value.length} attachment(s):\n\n${attachmentList}`,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (
|
|
80
|
+
error.message === 'Authentication required' ||
|
|
81
|
+
error.message === 'UNAUTHORIZED'
|
|
82
|
+
) {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: 'text',
|
|
87
|
+
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: 'text',
|
|
97
|
+
text: `Error listing attachments: ${error.message}`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Download a specific attachment
|
|
106
|
+
* @param {object} args - Tool arguments
|
|
107
|
+
* @param {string} args.messageId - The ID of the email message
|
|
108
|
+
* @param {string} args.attachmentId - The ID of the attachment
|
|
109
|
+
* @param {string} args.savePath - Optional path to save the file (defaults to current directory)
|
|
110
|
+
* @returns {object} - MCP response with download result
|
|
111
|
+
*/
|
|
112
|
+
async function handleDownloadAttachment(args) {
|
|
113
|
+
const { messageId, attachmentId, savePath } = args;
|
|
114
|
+
|
|
115
|
+
if (!messageId || !attachmentId) {
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: 'Error: Both messageId and attachmentId are required',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const accessToken = await ensureAuthenticated();
|
|
128
|
+
|
|
129
|
+
// First, get attachment metadata to get the filename and content
|
|
130
|
+
const metadataEndpoint = `/me/messages/${messageId}/attachments/${attachmentId}`;
|
|
131
|
+
console.error(`Fetching attachment metadata: ${attachmentId}`);
|
|
132
|
+
|
|
133
|
+
const metadata = await callGraphAPI(
|
|
134
|
+
accessToken,
|
|
135
|
+
'GET',
|
|
136
|
+
metadataEndpoint,
|
|
137
|
+
null,
|
|
138
|
+
{}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!metadata) {
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: 'text',
|
|
146
|
+
text: 'Error: Attachment not found',
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const filename = metadata.name || 'attachment';
|
|
153
|
+
const contentType = metadata.contentType || 'application/octet-stream';
|
|
154
|
+
|
|
155
|
+
// For file attachments, the content is base64 encoded in contentBytes
|
|
156
|
+
if (metadata['@odata.type'] === '#microsoft.graph.fileAttachment') {
|
|
157
|
+
const contentBytes = metadata.contentBytes;
|
|
158
|
+
|
|
159
|
+
if (!contentBytes) {
|
|
160
|
+
return {
|
|
161
|
+
content: [
|
|
162
|
+
{
|
|
163
|
+
type: 'text',
|
|
164
|
+
text: 'Error: No content found in attachment',
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Determine save location
|
|
171
|
+
const outputDir = savePath || process.cwd();
|
|
172
|
+
const outputPath = path.join(outputDir, filename);
|
|
173
|
+
|
|
174
|
+
// Decode base64 and save to file
|
|
175
|
+
const buffer = Buffer.from(contentBytes, 'base64');
|
|
176
|
+
fs.writeFileSync(outputPath, buffer);
|
|
177
|
+
|
|
178
|
+
const sizeKB = (buffer.length / 1024).toFixed(1);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: `Successfully downloaded attachment:\n\nFilename: ${filename}\nType: ${contentType}\nSize: ${sizeKB} KB\nSaved to: ${outputPath}`,
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
} else if (metadata['@odata.type'] === '#microsoft.graph.itemAttachment') {
|
|
189
|
+
// Item attachments (embedded emails, calendar items) need different handling
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: 'text',
|
|
194
|
+
text: `This is an embedded item attachment (${metadata.name}). Item attachments cannot be downloaded as files directly. They contain embedded Outlook items like emails or calendar events.`,
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
} else if (
|
|
199
|
+
metadata['@odata.type'] === '#microsoft.graph.referenceAttachment'
|
|
200
|
+
) {
|
|
201
|
+
// Reference attachments are links to cloud files
|
|
202
|
+
return {
|
|
203
|
+
content: [
|
|
204
|
+
{
|
|
205
|
+
type: 'text',
|
|
206
|
+
text: `This is a reference attachment (cloud link):\n\nName: ${metadata.name}\nThis attachment is a link to a file stored in the cloud and cannot be downloaded directly.`,
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
};
|
|
210
|
+
} else {
|
|
211
|
+
return {
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: 'text',
|
|
215
|
+
text: `Unknown attachment type: ${metadata['@odata.type']}`,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (
|
|
222
|
+
error.message === 'Authentication required' ||
|
|
223
|
+
error.message === 'UNAUTHORIZED'
|
|
224
|
+
) {
|
|
225
|
+
return {
|
|
226
|
+
content: [
|
|
227
|
+
{
|
|
228
|
+
type: 'text',
|
|
229
|
+
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: 'text',
|
|
239
|
+
text: `Error downloading attachment: ${error.message}`,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get attachment content as base64 (useful for viewing in Claude)
|
|
248
|
+
* @param {object} args - Tool arguments
|
|
249
|
+
* @param {string} args.messageId - The ID of the email message
|
|
250
|
+
* @param {string} args.attachmentId - The ID of the attachment
|
|
251
|
+
* @returns {object} - MCP response with attachment content
|
|
252
|
+
*/
|
|
253
|
+
async function handleGetAttachmentContent(args) {
|
|
254
|
+
const { messageId, attachmentId } = args;
|
|
255
|
+
|
|
256
|
+
if (!messageId || !attachmentId) {
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: 'text',
|
|
261
|
+
text: 'Error: Both messageId and attachmentId are required',
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const accessToken = await ensureAuthenticated();
|
|
269
|
+
|
|
270
|
+
const endpoint = `/me/messages/${messageId}/attachments/${attachmentId}`;
|
|
271
|
+
console.error(`Fetching attachment content: ${attachmentId}`);
|
|
272
|
+
|
|
273
|
+
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, {});
|
|
274
|
+
|
|
275
|
+
if (!response) {
|
|
276
|
+
return {
|
|
277
|
+
content: [
|
|
278
|
+
{
|
|
279
|
+
type: 'text',
|
|
280
|
+
text: 'Error: Attachment not found',
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const filename = response.name || 'attachment';
|
|
287
|
+
const contentType = response.contentType || 'application/octet-stream';
|
|
288
|
+
const sizeBytes = response.size || 0;
|
|
289
|
+
const sizeKB = (sizeBytes / 1024).toFixed(1);
|
|
290
|
+
|
|
291
|
+
// For file attachments, return metadata and indicate content is available
|
|
292
|
+
if (response['@odata.type'] === '#microsoft.graph.fileAttachment') {
|
|
293
|
+
// Check if it's a text-based file we can display
|
|
294
|
+
const textTypes = [
|
|
295
|
+
'text/',
|
|
296
|
+
'application/json',
|
|
297
|
+
'application/xml',
|
|
298
|
+
'application/javascript',
|
|
299
|
+
];
|
|
300
|
+
const isText = textTypes.some((t) => contentType.startsWith(t));
|
|
301
|
+
|
|
302
|
+
if (isText && response.contentBytes) {
|
|
303
|
+
const content = Buffer.from(response.contentBytes, 'base64').toString(
|
|
304
|
+
'utf-8'
|
|
305
|
+
);
|
|
306
|
+
return {
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: 'text',
|
|
310
|
+
text: `Attachment: ${filename}\nType: ${contentType}\nSize: ${sizeKB} KB\n\n--- Content ---\n${content}`,
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// For binary files, just return metadata
|
|
317
|
+
return {
|
|
318
|
+
content: [
|
|
319
|
+
{
|
|
320
|
+
type: 'text',
|
|
321
|
+
text: `Attachment: ${filename}\nType: ${contentType}\nSize: ${sizeKB} KB\n\nThis is a binary file. Use 'download-attachment' to save it to disk.`,
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
content: [
|
|
329
|
+
{
|
|
330
|
+
type: 'text',
|
|
331
|
+
text: `Attachment: ${filename}\nType: ${response['@odata.type']}\nSize: ${sizeKB} KB`,
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
};
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (
|
|
337
|
+
error.message === 'Authentication required' ||
|
|
338
|
+
error.message === 'UNAUTHORIZED'
|
|
339
|
+
) {
|
|
340
|
+
return {
|
|
341
|
+
content: [
|
|
342
|
+
{
|
|
343
|
+
type: 'text',
|
|
344
|
+
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
content: [
|
|
352
|
+
{
|
|
353
|
+
type: 'text',
|
|
354
|
+
text: `Error getting attachment content: ${error.message}`,
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
module.exports = {
|
|
362
|
+
handleListAttachments,
|
|
363
|
+
handleDownloadAttachment,
|
|
364
|
+
handleGetAttachmentContent,
|
|
365
|
+
};
|