@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.
Files changed (52) hide show
  1. package/.env.example +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +422 -0
  4. package/advanced/index.js +652 -0
  5. package/auth/index.js +32 -0
  6. package/auth/oauth-server.js +233 -0
  7. package/auth/token-manager.js +105 -0
  8. package/auth/token-storage.js +359 -0
  9. package/auth/tools.js +159 -0
  10. package/calendar/accept.js +72 -0
  11. package/calendar/cancel.js +72 -0
  12. package/calendar/create.js +115 -0
  13. package/calendar/decline.js +72 -0
  14. package/calendar/delete.js +67 -0
  15. package/calendar/index.js +130 -0
  16. package/calendar/list.js +108 -0
  17. package/categories/index.js +955 -0
  18. package/config.js +95 -0
  19. package/contacts/index.js +754 -0
  20. package/email/attachments.js +365 -0
  21. package/email/conversations.js +666 -0
  22. package/email/delta.js +210 -0
  23. package/email/export.js +572 -0
  24. package/email/folder-utils.js +192 -0
  25. package/email/headers.js +344 -0
  26. package/email/index.js +537 -0
  27. package/email/list.js +136 -0
  28. package/email/mark-as-read.js +114 -0
  29. package/email/mime.js +286 -0
  30. package/email/read.js +161 -0
  31. package/email/search.js +628 -0
  32. package/email/send.js +169 -0
  33. package/folder/create.js +137 -0
  34. package/folder/delete.js +108 -0
  35. package/folder/index.js +112 -0
  36. package/folder/list.js +289 -0
  37. package/folder/move.js +186 -0
  38. package/folder/stats.js +322 -0
  39. package/index.js +162 -0
  40. package/llms.txt +76 -0
  41. package/outlook-auth-server.js +384 -0
  42. package/package.json +97 -0
  43. package/rules/create.js +273 -0
  44. package/rules/index.js +276 -0
  45. package/rules/list.js +216 -0
  46. package/settings/index.js +678 -0
  47. package/utils/field-presets.js +311 -0
  48. package/utils/graph-api.js +268 -0
  49. package/utils/mock-data.js +154 -0
  50. package/utils/odata-helpers.js +33 -0
  51. package/utils/response-formatter.js +457 -0
  52. package/utils/safety.js +123 -0
package/folder/list.js ADDED
@@ -0,0 +1,289 @@
1
+ /**
2
+ * List folders functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+
7
+ /**
8
+ * List folders handler
9
+ * @param {object} args - Tool arguments
10
+ * @returns {object} - MCP response
11
+ */
12
+ async function handleListFolders(args) {
13
+ const includeItemCounts = args.includeItemCounts === true;
14
+ const includeChildren = args.includeChildren === true;
15
+
16
+ try {
17
+ // Get access token
18
+ const accessToken = await ensureAuthenticated();
19
+
20
+ // Get all mail folders
21
+ const folders = await getAllFoldersHierarchy(
22
+ accessToken,
23
+ includeItemCounts
24
+ );
25
+
26
+ // If including children, format as hierarchy
27
+ if (includeChildren) {
28
+ return {
29
+ content: [
30
+ {
31
+ type: 'text',
32
+ text: formatFolderHierarchy(folders, includeItemCounts),
33
+ },
34
+ ],
35
+ };
36
+ } else {
37
+ // Otherwise, format as flat list
38
+ return {
39
+ content: [
40
+ {
41
+ type: 'text',
42
+ text: formatFolderList(folders, includeItemCounts),
43
+ },
44
+ ],
45
+ };
46
+ }
47
+ } catch (error) {
48
+ if (error.message === 'Authentication required') {
49
+ return {
50
+ content: [
51
+ {
52
+ type: 'text',
53
+ text: "Authentication required. Please use the 'authenticate' tool first.",
54
+ },
55
+ ],
56
+ };
57
+ }
58
+
59
+ return {
60
+ content: [
61
+ {
62
+ type: 'text',
63
+ text: `Error listing folders: ${error.message}`,
64
+ },
65
+ ],
66
+ };
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Get all mail folders with hierarchy information
72
+ * @param {string} accessToken - Access token
73
+ * @param {boolean} includeItemCounts - Include item counts in response
74
+ * @returns {Promise<Array>} - Array of folder objects with hierarchy
75
+ */
76
+ async function getAllFoldersHierarchy(accessToken, includeItemCounts) {
77
+ try {
78
+ // Determine select fields based on whether to include counts
79
+ const selectFields = includeItemCounts
80
+ ? 'id,displayName,parentFolderId,childFolderCount,totalItemCount,unreadItemCount'
81
+ : 'id,displayName,parentFolderId,childFolderCount';
82
+
83
+ // Get all mail folders
84
+ const response = await callGraphAPI(
85
+ accessToken,
86
+ 'GET',
87
+ 'me/mailFolders',
88
+ null,
89
+ {
90
+ $top: 100,
91
+ $select: selectFields,
92
+ }
93
+ );
94
+
95
+ if (!response.value) {
96
+ return [];
97
+ }
98
+
99
+ // Get child folders for folders with children
100
+ const foldersWithChildren = response.value.filter(
101
+ (f) => f.childFolderCount > 0
102
+ );
103
+
104
+ const childFolderPromises = foldersWithChildren.map(async (folder) => {
105
+ try {
106
+ const childResponse = await callGraphAPI(
107
+ accessToken,
108
+ 'GET',
109
+ `me/mailFolders/${folder.id}/childFolders`,
110
+ null,
111
+ { $select: selectFields }
112
+ );
113
+
114
+ // Add parent folder info to each child
115
+ const childFolders = childResponse.value || [];
116
+ childFolders.forEach((child) => {
117
+ child.parentFolder = folder.displayName;
118
+ });
119
+
120
+ return childFolders;
121
+ } catch (error) {
122
+ console.error(
123
+ `Error getting child folders for "${folder.displayName}": ${error.message}`
124
+ );
125
+ return [];
126
+ }
127
+ });
128
+
129
+ const childFolders = await Promise.all(childFolderPromises);
130
+ const allChildFolders = childFolders.flat();
131
+
132
+ // Add top-level flag to parent folders
133
+ const topLevelFolders = response.value.map((folder) => ({
134
+ ...folder,
135
+ isTopLevel: true,
136
+ }));
137
+
138
+ // Combine all folders
139
+ return [...topLevelFolders, ...allChildFolders];
140
+ } catch (error) {
141
+ console.error(`Error getting all folders: ${error.message}`);
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Format folders as a flat list
148
+ * @param {Array} folders - Array of folder objects
149
+ * @param {boolean} includeItemCounts - Whether to include item counts
150
+ * @returns {string} - Formatted list
151
+ */
152
+ function formatFolderList(folders, includeItemCounts) {
153
+ if (!folders || folders.length === 0) {
154
+ return 'No folders found.';
155
+ }
156
+
157
+ // Sort folders alphabetically, with well-known folders first
158
+ const wellKnownFolderNames = [
159
+ 'Inbox',
160
+ 'Drafts',
161
+ 'Sent Items',
162
+ 'Deleted Items',
163
+ 'Junk Email',
164
+ 'Archive',
165
+ ];
166
+
167
+ const sortedFolders = [...folders].sort((a, b) => {
168
+ // Well-known folders come first
169
+ const aIsWellKnown = wellKnownFolderNames.includes(a.displayName);
170
+ const bIsWellKnown = wellKnownFolderNames.includes(b.displayName);
171
+
172
+ if (aIsWellKnown && !bIsWellKnown) return -1;
173
+ if (!aIsWellKnown && bIsWellKnown) return 1;
174
+
175
+ if (aIsWellKnown && bIsWellKnown) {
176
+ // Sort well-known folders by their index in the array
177
+ return (
178
+ wellKnownFolderNames.indexOf(a.displayName) -
179
+ wellKnownFolderNames.indexOf(b.displayName)
180
+ );
181
+ }
182
+
183
+ // Sort other folders alphabetically
184
+ return a.displayName.localeCompare(b.displayName);
185
+ });
186
+
187
+ // Format each folder
188
+ const folderLines = sortedFolders.map((folder) => {
189
+ let folderInfo = folder.displayName;
190
+
191
+ // Add parent folder info if available
192
+ if (folder.parentFolder) {
193
+ folderInfo += ` (in ${folder.parentFolder})`;
194
+ }
195
+
196
+ // Add item counts if requested
197
+ if (includeItemCounts) {
198
+ const unreadCount = folder.unreadItemCount || 0;
199
+ const totalCount = folder.totalItemCount || 0;
200
+ folderInfo += ` - ${totalCount} items`;
201
+
202
+ if (unreadCount > 0) {
203
+ folderInfo += ` (${unreadCount} unread)`;
204
+ }
205
+ }
206
+
207
+ return folderInfo;
208
+ });
209
+
210
+ return `Found ${folders.length} folders:\n\n${folderLines.join('\n')}`;
211
+ }
212
+
213
+ /**
214
+ * Format folders as a hierarchical tree
215
+ * @param {Array} folders - Array of folder objects
216
+ * @param {boolean} includeItemCounts - Whether to include item counts
217
+ * @returns {string} - Formatted hierarchy
218
+ */
219
+ function formatFolderHierarchy(folders, includeItemCounts) {
220
+ if (!folders || folders.length === 0) {
221
+ return 'No folders found.';
222
+ }
223
+
224
+ // Build folder hierarchy
225
+ const folderMap = new Map();
226
+ const rootFolders = [];
227
+
228
+ // First pass: create map of all folders
229
+ folders.forEach((folder) => {
230
+ folderMap.set(folder.id, {
231
+ ...folder,
232
+ children: [],
233
+ });
234
+
235
+ if (folder.isTopLevel) {
236
+ rootFolders.push(folder.id);
237
+ }
238
+ });
239
+
240
+ // Second pass: build hierarchy
241
+ folders.forEach((folder) => {
242
+ if (!folder.isTopLevel && folder.parentFolderId) {
243
+ const parent = folderMap.get(folder.parentFolderId);
244
+ if (parent) {
245
+ parent.children.push(folder.id);
246
+ } else {
247
+ // Fallback for orphaned folders
248
+ rootFolders.push(folder.id);
249
+ }
250
+ }
251
+ });
252
+
253
+ // Format hierarchy recursively
254
+ function formatSubtree(folderId, level = 0) {
255
+ const folder = folderMap.get(folderId);
256
+ if (!folder) return '';
257
+
258
+ const indent = ' '.repeat(level);
259
+ let line = `${indent}${folder.displayName}`;
260
+
261
+ // Add item counts if requested
262
+ if (includeItemCounts) {
263
+ const unreadCount = folder.unreadItemCount || 0;
264
+ const totalCount = folder.totalItemCount || 0;
265
+ line += ` - ${totalCount} items`;
266
+
267
+ if (unreadCount > 0) {
268
+ line += ` (${unreadCount} unread)`;
269
+ }
270
+ }
271
+
272
+ // Add children
273
+ const childLines = folder.children
274
+ .map((childId) => formatSubtree(childId, level + 1))
275
+ .filter((line) => line.length > 0)
276
+ .join('\n');
277
+
278
+ return childLines.length > 0 ? `${line}\n${childLines}` : line;
279
+ }
280
+
281
+ // Format all root folders
282
+ const formattedHierarchy = rootFolders
283
+ .map((folderId) => formatSubtree(folderId))
284
+ .join('\n');
285
+
286
+ return `Folder Hierarchy:\n\n${formattedHierarchy}`;
287
+ }
288
+
289
+ module.exports = handleListFolders;
package/folder/move.js ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Move emails functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+ const { getFolderIdByName } = require('../email/folder-utils');
7
+
8
+ /**
9
+ * Move emails handler
10
+ * @param {object} args - Tool arguments
11
+ * @returns {object} - MCP response
12
+ */
13
+ async function handleMoveEmails(args) {
14
+ const emailIds = args.emailIds || '';
15
+ const targetFolder = args.targetFolder || '';
16
+ const sourceFolder = args.sourceFolder || '';
17
+
18
+ if (!emailIds) {
19
+ return {
20
+ content: [
21
+ {
22
+ type: 'text',
23
+ text: 'Email IDs are required. Please provide a comma-separated list of email IDs to move.',
24
+ },
25
+ ],
26
+ };
27
+ }
28
+
29
+ if (!targetFolder) {
30
+ return {
31
+ content: [
32
+ {
33
+ type: 'text',
34
+ text: 'Target folder name is required.',
35
+ },
36
+ ],
37
+ };
38
+ }
39
+
40
+ try {
41
+ // Get access token
42
+ const accessToken = await ensureAuthenticated();
43
+
44
+ // Parse email IDs
45
+ const ids = emailIds
46
+ .split(',')
47
+ .map((id) => id.trim())
48
+ .filter((id) => id);
49
+
50
+ if (ids.length === 0) {
51
+ return {
52
+ content: [
53
+ {
54
+ type: 'text',
55
+ text: 'No valid email IDs provided.',
56
+ },
57
+ ],
58
+ };
59
+ }
60
+
61
+ // Move emails
62
+ const result = await moveEmailsToFolder(
63
+ accessToken,
64
+ ids,
65
+ targetFolder,
66
+ sourceFolder
67
+ );
68
+
69
+ return {
70
+ content: [
71
+ {
72
+ type: 'text',
73
+ text: result.message,
74
+ },
75
+ ],
76
+ };
77
+ } catch (error) {
78
+ if (error.message === 'Authentication required') {
79
+ return {
80
+ content: [
81
+ {
82
+ type: 'text',
83
+ text: "Authentication required. Please use the 'authenticate' tool first.",
84
+ },
85
+ ],
86
+ };
87
+ }
88
+
89
+ return {
90
+ content: [
91
+ {
92
+ type: 'text',
93
+ text: `Error moving emails: ${error.message}`,
94
+ },
95
+ ],
96
+ };
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Move emails to a folder
102
+ * @param {string} accessToken - Access token
103
+ * @param {Array<string>} emailIds - Array of email IDs to move
104
+ * @param {string} targetFolderName - Name of the target folder
105
+ * @param {string} sourceFolderName - Name of the source folder (optional)
106
+ * @returns {Promise<object>} - Result object with status and message
107
+ */
108
+ async function moveEmailsToFolder(
109
+ accessToken,
110
+ emailIds,
111
+ targetFolderName,
112
+ _sourceFolderName
113
+ ) {
114
+ try {
115
+ // Get the target folder ID
116
+ const targetFolderId = await getFolderIdByName(
117
+ accessToken,
118
+ targetFolderName
119
+ );
120
+ if (!targetFolderId) {
121
+ return {
122
+ success: false,
123
+ message: `Target folder "${targetFolderName}" not found. Please specify a valid folder name.`,
124
+ };
125
+ }
126
+
127
+ // Track successful and failed moves
128
+ const results = {
129
+ successful: [],
130
+ failed: [],
131
+ };
132
+
133
+ // Process each email one by one to handle errors independently
134
+ for (const emailId of emailIds) {
135
+ try {
136
+ // Move the email
137
+ await callGraphAPI(accessToken, 'POST', `me/messages/${emailId}/move`, {
138
+ destinationId: targetFolderId,
139
+ });
140
+
141
+ results.successful.push(emailId);
142
+ } catch (error) {
143
+ console.error(`Error moving email ${emailId}: ${error.message}`);
144
+ results.failed.push({
145
+ id: emailId,
146
+ error: error.message,
147
+ });
148
+ }
149
+ }
150
+
151
+ // Generate result message
152
+ let message = '';
153
+
154
+ if (results.successful.length > 0) {
155
+ message += `Successfully moved ${results.successful.length} email(s) to "${targetFolderName}".`;
156
+ }
157
+
158
+ if (results.failed.length > 0) {
159
+ if (message) message += '\n\n';
160
+ message += `Failed to move ${results.failed.length} email(s). Errors:`;
161
+
162
+ // Show first few errors with details
163
+ const maxErrors = Math.min(results.failed.length, 3);
164
+ for (let i = 0; i < maxErrors; i++) {
165
+ const failure = results.failed[i];
166
+ message += `\n- Email ${i + 1}: ${failure.error}`;
167
+ }
168
+
169
+ // If there are more errors, just mention the count
170
+ if (results.failed.length > maxErrors) {
171
+ message += `\n...and ${results.failed.length - maxErrors} more.`;
172
+ }
173
+ }
174
+
175
+ return {
176
+ success: results.successful.length > 0,
177
+ message,
178
+ results,
179
+ };
180
+ } catch (error) {
181
+ console.error(`Error in moveEmailsToFolder: ${error.message}`);
182
+ throw error;
183
+ }
184
+ }
185
+
186
+ module.exports = handleMoveEmails;