@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/email/send.js ADDED
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Send email functionality
3
+ */
4
+ const _config = require('../config'); // Reserved for future use
5
+ const { callGraphAPI } = require('../utils/graph-api');
6
+ const { ensureAuthenticated } = require('../auth');
7
+ const {
8
+ checkRateLimit,
9
+ checkRecipientAllowlist,
10
+ formatDryRunPreview,
11
+ } = require('../utils/safety');
12
+
13
+ /**
14
+ * Send email handler
15
+ * @param {object} args - Tool arguments
16
+ * @returns {object} - MCP response
17
+ */
18
+ async function handleSendEmail(args) {
19
+ const {
20
+ to,
21
+ cc,
22
+ bcc,
23
+ subject,
24
+ body,
25
+ importance = 'normal',
26
+ saveToSentItems = true,
27
+ dryRun = false,
28
+ } = args;
29
+
30
+ // Validate required parameters
31
+ if (!to) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: 'text',
36
+ text: 'Recipient (to) is required.',
37
+ },
38
+ ],
39
+ };
40
+ }
41
+
42
+ if (!subject) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: 'text',
47
+ text: 'Subject is required.',
48
+ },
49
+ ],
50
+ };
51
+ }
52
+
53
+ if (!body) {
54
+ return {
55
+ content: [
56
+ {
57
+ type: 'text',
58
+ text: 'Body content is required.',
59
+ },
60
+ ],
61
+ };
62
+ }
63
+
64
+ try {
65
+ // Format recipients
66
+ const toRecipients = to.split(',').map((email) => {
67
+ email = email.trim();
68
+ return {
69
+ emailAddress: {
70
+ address: email,
71
+ },
72
+ };
73
+ });
74
+
75
+ const ccRecipients = cc
76
+ ? cc.split(',').map((email) => {
77
+ email = email.trim();
78
+ return {
79
+ emailAddress: {
80
+ address: email,
81
+ },
82
+ };
83
+ })
84
+ : [];
85
+
86
+ const bccRecipients = bcc
87
+ ? bcc.split(',').map((email) => {
88
+ email = email.trim();
89
+ return {
90
+ emailAddress: {
91
+ address: email,
92
+ },
93
+ };
94
+ })
95
+ : [];
96
+
97
+ // Check recipient allowlist (all recipients combined)
98
+ const allRecipients = [...toRecipients, ...ccRecipients, ...bccRecipients];
99
+ const allowlistError = checkRecipientAllowlist(allRecipients);
100
+ if (allowlistError) return allowlistError;
101
+
102
+ // Prepare email object
103
+ const emailObject = {
104
+ message: {
105
+ subject,
106
+ body: {
107
+ contentType:
108
+ /<(html|div|p|h[1-6]|br|table|ul|ol|li|span|a\s|img|strong|em|b|i)\b/i.test(
109
+ body
110
+ )
111
+ ? 'html'
112
+ : 'text',
113
+ content: body,
114
+ },
115
+ toRecipients,
116
+ ccRecipients: ccRecipients.length > 0 ? ccRecipients : undefined,
117
+ bccRecipients: bccRecipients.length > 0 ? bccRecipients : undefined,
118
+ importance,
119
+ },
120
+ saveToSentItems,
121
+ };
122
+
123
+ // Dry-run mode: return preview without sending
124
+ if (dryRun) {
125
+ return formatDryRunPreview(emailObject);
126
+ }
127
+
128
+ // Check rate limit (only for actual sends, not dry runs)
129
+ const rateLimitError = checkRateLimit('send-email');
130
+ if (rateLimitError) return rateLimitError;
131
+
132
+ // Get access token
133
+ const accessToken = await ensureAuthenticated();
134
+
135
+ // Make API call to send email
136
+ await callGraphAPI(accessToken, 'POST', 'me/sendMail', emailObject);
137
+
138
+ return {
139
+ content: [
140
+ {
141
+ type: 'text',
142
+ text: `Email sent successfully!\n\nSubject: ${subject}\nRecipients: ${toRecipients.length}${ccRecipients.length > 0 ? ` + ${ccRecipients.length} CC` : ''}${bccRecipients.length > 0 ? ` + ${bccRecipients.length} BCC` : ''}\nMessage Length: ${body.length} characters`,
143
+ },
144
+ ],
145
+ };
146
+ } catch (error) {
147
+ if (error.message === 'Authentication required') {
148
+ return {
149
+ content: [
150
+ {
151
+ type: 'text',
152
+ text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
153
+ },
154
+ ],
155
+ };
156
+ }
157
+
158
+ return {
159
+ content: [
160
+ {
161
+ type: 'text',
162
+ text: `Error sending email: ${error.message}`,
163
+ },
164
+ ],
165
+ };
166
+ }
167
+ }
168
+
169
+ module.exports = handleSendEmail;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Create folder functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+ const { getFolderIdByName } = require('../email/folder-utils');
7
+
8
+ /**
9
+ * Create folder handler
10
+ * @param {object} args - Tool arguments
11
+ * @returns {object} - MCP response
12
+ */
13
+ async function handleCreateFolder(args) {
14
+ const folderName = args.name;
15
+ const parentFolder = args.parentFolder || '';
16
+
17
+ if (!folderName) {
18
+ return {
19
+ content: [
20
+ {
21
+ type: 'text',
22
+ text: 'Folder name is required.',
23
+ },
24
+ ],
25
+ };
26
+ }
27
+
28
+ try {
29
+ // Get access token
30
+ const accessToken = await ensureAuthenticated();
31
+
32
+ // Create folder with appropriate parent
33
+ const result = await createMailFolder(
34
+ accessToken,
35
+ folderName,
36
+ parentFolder
37
+ );
38
+
39
+ return {
40
+ content: [
41
+ {
42
+ type: 'text',
43
+ text: result.message,
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 creating folder: ${error.message}`,
64
+ },
65
+ ],
66
+ };
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Create a new mail folder
72
+ * @param {string} accessToken - Access token
73
+ * @param {string} folderName - Name of the folder to create
74
+ * @param {string} parentFolderName - Name of the parent folder (optional)
75
+ * @returns {Promise<object>} - Result object with status and message
76
+ */
77
+ async function createMailFolder(accessToken, folderName, parentFolderName) {
78
+ try {
79
+ // Check if a folder with this name already exists
80
+ const existingFolder = await getFolderIdByName(accessToken, folderName);
81
+ if (existingFolder) {
82
+ return {
83
+ success: false,
84
+ message: `A folder named "${folderName}" already exists.`,
85
+ };
86
+ }
87
+
88
+ // If parent folder specified, find its ID
89
+ let endpoint = 'me/mailFolders';
90
+ if (parentFolderName) {
91
+ const parentId = await getFolderIdByName(accessToken, parentFolderName);
92
+ if (!parentId) {
93
+ return {
94
+ success: false,
95
+ message: `Parent folder "${parentFolderName}" not found. Please specify a valid parent folder or leave it blank to create at the root level.`,
96
+ };
97
+ }
98
+
99
+ endpoint = `me/mailFolders/${parentId}/childFolders`;
100
+ }
101
+
102
+ // Create the folder
103
+ const folderData = {
104
+ displayName: folderName,
105
+ };
106
+
107
+ const response = await callGraphAPI(
108
+ accessToken,
109
+ 'POST',
110
+ endpoint,
111
+ folderData
112
+ );
113
+
114
+ if (response && response.id) {
115
+ const locationInfo = parentFolderName
116
+ ? `inside "${parentFolderName}"`
117
+ : 'at the root level';
118
+
119
+ return {
120
+ success: true,
121
+ message: `Successfully created folder "${folderName}" ${locationInfo}.`,
122
+ folderId: response.id,
123
+ };
124
+ } else {
125
+ return {
126
+ success: false,
127
+ message:
128
+ "Failed to create folder. The server didn't return a folder ID.",
129
+ };
130
+ }
131
+ } catch (error) {
132
+ console.error(`Error creating folder "${folderName}": ${error.message}`);
133
+ throw error;
134
+ }
135
+ }
136
+
137
+ module.exports = handleCreateFolder;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Delete folder functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+ const { getFolderIdByName } = require('../email/folder-utils');
7
+
8
+ /**
9
+ * Protected folder names that cannot be deleted
10
+ */
11
+ const PROTECTED_FOLDERS = [
12
+ 'inbox',
13
+ 'drafts',
14
+ 'sentitems',
15
+ 'deleteditems',
16
+ 'junkemail',
17
+ 'archive',
18
+ 'outbox',
19
+ ];
20
+
21
+ /**
22
+ * Delete folder handler
23
+ * @param {object} args - Tool arguments
24
+ * @param {string} [args.folderId] - Folder ID to delete
25
+ * @param {string} [args.folderName] - Folder name to delete (resolved to ID)
26
+ * @returns {object} - MCP response
27
+ */
28
+ async function handleDeleteFolder(args) {
29
+ const { folderId, folderName } = args;
30
+
31
+ if (!folderId && !folderName) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: 'text',
36
+ text: 'Either folderId or folderName is required.',
37
+ },
38
+ ],
39
+ };
40
+ }
41
+
42
+ // Guard against deleting protected folders
43
+ if (folderName && PROTECTED_FOLDERS.includes(folderName.toLowerCase())) {
44
+ return {
45
+ content: [
46
+ {
47
+ type: 'text',
48
+ text: `Cannot delete protected folder "${folderName}". Protected folders: Inbox, Drafts, Sent Items, Deleted Items, Junk Email, Archive, Outbox.`,
49
+ },
50
+ ],
51
+ };
52
+ }
53
+
54
+ try {
55
+ const accessToken = await ensureAuthenticated();
56
+
57
+ let resolvedId = folderId;
58
+
59
+ // Resolve folder name to ID if needed
60
+ if (!resolvedId && folderName) {
61
+ resolvedId = await getFolderIdByName(accessToken, folderName);
62
+ if (!resolvedId) {
63
+ return {
64
+ content: [
65
+ {
66
+ type: 'text',
67
+ text: `Folder "${folderName}" not found. Use folders (action=list) to see available folders.`,
68
+ },
69
+ ],
70
+ };
71
+ }
72
+ }
73
+
74
+ // Delete the folder
75
+ await callGraphAPI(accessToken, 'DELETE', `me/mailFolders/${resolvedId}`);
76
+
77
+ const displayName = folderName || resolvedId;
78
+ return {
79
+ content: [
80
+ {
81
+ type: 'text',
82
+ text: `Folder "${displayName}" deleted successfully.`,
83
+ },
84
+ ],
85
+ };
86
+ } catch (error) {
87
+ if (error.message === 'Authentication required') {
88
+ return {
89
+ content: [
90
+ {
91
+ type: 'text',
92
+ text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
93
+ },
94
+ ],
95
+ };
96
+ }
97
+ return {
98
+ content: [
99
+ {
100
+ type: 'text',
101
+ text: `Error deleting folder: ${error.message}`,
102
+ },
103
+ ],
104
+ };
105
+ }
106
+ }
107
+
108
+ module.exports = handleDeleteFolder;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Folder management module for Outlook Assistant server
3
+ */
4
+ const handleListFolders = require('./list');
5
+ const handleCreateFolder = require('./create');
6
+ const handleMoveEmails = require('./move');
7
+ const handleGetFolderStats = require('./stats');
8
+ const handleDeleteFolder = require('./delete');
9
+
10
+ // Consolidated folder tool definition
11
+ const folderTools = [
12
+ {
13
+ name: 'folders',
14
+ description:
15
+ 'Manage mail folders. action=list (default) lists folders. action=create creates a folder. action=move moves emails between folders. action=stats gets folder counts for pagination planning. action=delete removes a folder.',
16
+ annotations: {
17
+ title: 'Mail Folders',
18
+ readOnlyHint: false,
19
+ destructiveHint: true,
20
+ openWorldHint: false,
21
+ },
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ action: {
26
+ type: 'string',
27
+ enum: ['list', 'create', 'move', 'stats', 'delete'],
28
+ description: 'Action to perform (default: list)',
29
+ },
30
+ // list params
31
+ includeItemCounts: {
32
+ type: 'boolean',
33
+ description: 'Include counts of total and unread items (action=list)',
34
+ },
35
+ includeChildren: {
36
+ type: 'boolean',
37
+ description: 'Include child folders in hierarchy (action=list)',
38
+ },
39
+ // create params
40
+ name: {
41
+ type: 'string',
42
+ description: 'Name of the folder to create (action=create, required)',
43
+ },
44
+ parentFolder: {
45
+ type: 'string',
46
+ description: 'Parent folder name, default is root (action=create)',
47
+ },
48
+ // move params
49
+ emailIds: {
50
+ type: 'string',
51
+ description:
52
+ 'Comma-separated list of email IDs to move (action=move, required)',
53
+ },
54
+ targetFolder: {
55
+ type: 'string',
56
+ description: 'Folder name to move emails to (action=move, required)',
57
+ },
58
+ sourceFolder: {
59
+ type: 'string',
60
+ description: 'Source folder name, default is inbox (action=move)',
61
+ },
62
+ // stats params
63
+ folder: {
64
+ type: 'string',
65
+ description:
66
+ 'Folder name (inbox, sent, drafts, etc.). Default: inbox (action=stats)',
67
+ },
68
+ outputVerbosity: {
69
+ type: 'string',
70
+ enum: ['minimal', 'standard', 'full'],
71
+ description: 'Output detail level (action=stats, default: standard)',
72
+ },
73
+ // delete params
74
+ folderId: {
75
+ type: 'string',
76
+ description: 'Folder ID to delete (action=delete)',
77
+ },
78
+ folderName: {
79
+ type: 'string',
80
+ description:
81
+ 'Folder name to delete — resolved to ID (action=delete). Cannot delete protected folders (Inbox, Drafts, Sent, etc.)',
82
+ },
83
+ },
84
+ required: [],
85
+ },
86
+ handler: async (args) => {
87
+ const action = args.action || 'list';
88
+ switch (action) {
89
+ case 'create':
90
+ return handleCreateFolder(args);
91
+ case 'move':
92
+ return handleMoveEmails(args);
93
+ case 'stats':
94
+ return handleGetFolderStats(args);
95
+ case 'delete':
96
+ return handleDeleteFolder(args);
97
+ case 'list':
98
+ default:
99
+ return handleListFolders(args);
100
+ }
101
+ },
102
+ },
103
+ ];
104
+
105
+ module.exports = {
106
+ folderTools,
107
+ handleListFolders,
108
+ handleCreateFolder,
109
+ handleMoveEmails,
110
+ handleGetFolderStats,
111
+ handleDeleteFolder,
112
+ };