@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/auth/tools.js ADDED
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Authentication-related tools for the Outlook Assistant server
3
+ */
4
+ const config = require('../config');
5
+ const tokenManager = require('./token-manager');
6
+
7
+ /**
8
+ * About tool handler
9
+ * @returns {object} - MCP response
10
+ */
11
+ async function handleAbout() {
12
+ const scopes = config.AUTH_CONFIG.scopes.filter(
13
+ (s) => s !== 'offline_access'
14
+ );
15
+ const testMode = config.USE_TEST_MODE ? 'Enabled' : 'Disabled';
16
+ const rateLimit =
17
+ process.env.OUTLOOK_MAX_EMAILS_PER_SESSION || 'Unlimited (no limit set)';
18
+ const allowlist =
19
+ process.env.OUTLOOK_ALLOWED_RECIPIENTS || 'None (all recipients allowed)';
20
+
21
+ const lines = [
22
+ `# Outlook Assistant Server v${config.SERVER_VERSION}\n`,
23
+ `Provides access to Microsoft Outlook email, calendar, and contacts through Microsoft Graph API.\n`,
24
+ `## Diagnostics\n`,
25
+ `| Setting | Value |`,
26
+ `|---------|-------|`,
27
+ `| Tools | 20 across 9 modules |`,
28
+ `| Modules | auth, email, calendar, folder, rules, contacts, categories, settings, advanced |`,
29
+ `| Timezone | ${config.DEFAULT_TIMEZONE} |`,
30
+ `| Test Mode | ${testMode} |`,
31
+ `| Rate Limit | ${rateLimit} |`,
32
+ `| Recipient Allowlist | ${allowlist} |`,
33
+ `| Scopes | ${scopes.length} configured |`,
34
+ ``,
35
+ `**Scopes**: ${scopes.join(', ')}`,
36
+ ];
37
+
38
+ return {
39
+ content: [
40
+ {
41
+ type: 'text',
42
+ text: lines.join('\n'),
43
+ },
44
+ ],
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Authentication tool handler
50
+ * @param {object} args - Tool arguments
51
+ * @returns {object} - MCP response
52
+ */
53
+ async function handleAuthenticate(args) {
54
+ const _force = args && args.force === true;
55
+
56
+ // For test mode, create a test token
57
+ if (config.USE_TEST_MODE) {
58
+ // Create a test token with a 1-hour expiry
59
+ tokenManager.createTestTokens();
60
+
61
+ return {
62
+ content: [
63
+ {
64
+ type: 'text',
65
+ text: 'Successfully authenticated with Microsoft Graph API (test mode)',
66
+ },
67
+ ],
68
+ };
69
+ }
70
+
71
+ // For real authentication, generate an auth URL and instruct the user to visit it
72
+ const authUrl = `${config.AUTH_CONFIG.authServerUrl}/auth?client_id=${config.AUTH_CONFIG.clientId}`;
73
+
74
+ return {
75
+ content: [
76
+ {
77
+ type: 'text',
78
+ text: `Authentication required. Please visit the following URL to authenticate with Microsoft: ${authUrl}\n\nAfter authentication, you will be redirected back to this application.`,
79
+ },
80
+ ],
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Check authentication status tool handler
86
+ * @returns {object} - MCP response
87
+ */
88
+ async function handleCheckAuthStatus() {
89
+ console.error('[CHECK-AUTH-STATUS] Starting authentication status check');
90
+
91
+ const tokens = tokenManager.loadTokenCache();
92
+
93
+ console.error(`[CHECK-AUTH-STATUS] Tokens loaded: ${tokens ? 'YES' : 'NO'}`);
94
+
95
+ if (!tokens || !tokens.access_token) {
96
+ console.error('[CHECK-AUTH-STATUS] No valid access token found');
97
+ return {
98
+ content: [{ type: 'text', text: 'Not authenticated' }],
99
+ };
100
+ }
101
+
102
+ console.error('[CHECK-AUTH-STATUS] Access token present');
103
+ console.error(`[CHECK-AUTH-STATUS] Token expires at: ${tokens.expires_at}`);
104
+ console.error(`[CHECK-AUTH-STATUS] Current time: ${Date.now()}`);
105
+
106
+ return {
107
+ content: [{ type: 'text', text: 'Authenticated and ready' }],
108
+ };
109
+ }
110
+
111
+ // Tool definitions
112
+ const authTools = [
113
+ {
114
+ name: 'auth',
115
+ description:
116
+ 'Manage authentication with Microsoft Graph API. action=status (default) checks auth state, action=authenticate starts OAuth flow, action=about shows server info.',
117
+ annotations: {
118
+ title: 'Authentication',
119
+ readOnlyHint: false,
120
+ destructiveHint: false,
121
+ openWorldHint: false,
122
+ },
123
+ inputSchema: {
124
+ type: 'object',
125
+ properties: {
126
+ action: {
127
+ type: 'string',
128
+ enum: ['status', 'authenticate', 'about'],
129
+ description: 'Action to perform (default: status)',
130
+ },
131
+ force: {
132
+ type: 'boolean',
133
+ description:
134
+ 'Force re-authentication even if already authenticated (action=authenticate only)',
135
+ },
136
+ },
137
+ required: [],
138
+ },
139
+ handler: async (args) => {
140
+ const action = args.action || 'status';
141
+ switch (action) {
142
+ case 'authenticate':
143
+ return handleAuthenticate(args);
144
+ case 'about':
145
+ return handleAbout();
146
+ case 'status':
147
+ default:
148
+ return handleCheckAuthStatus();
149
+ }
150
+ },
151
+ },
152
+ ];
153
+
154
+ module.exports = {
155
+ authTools,
156
+ handleAbout,
157
+ handleAuthenticate,
158
+ handleCheckAuthStatus,
159
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Accept event functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+
7
+ /**
8
+ * Accept event handler
9
+ * @param {object} args - Tool arguments
10
+ * @returns {object} - MCP response
11
+ */
12
+ async function handleAcceptEvent(args) {
13
+ const { eventId, comment } = args;
14
+
15
+ if (!eventId) {
16
+ return {
17
+ content: [
18
+ {
19
+ type: 'text',
20
+ text: 'Event ID is required to accept an event.',
21
+ },
22
+ ],
23
+ };
24
+ }
25
+
26
+ try {
27
+ // Get access token
28
+ const accessToken = await ensureAuthenticated();
29
+
30
+ // Build API endpoint
31
+ const endpoint = `me/events/${eventId}/accept`;
32
+
33
+ // Request body
34
+ const body = {
35
+ comment: comment || 'Accepted via API',
36
+ };
37
+
38
+ // Make API call
39
+ await callGraphAPI(accessToken, 'POST', endpoint, body);
40
+
41
+ return {
42
+ content: [
43
+ {
44
+ type: 'text',
45
+ text: `Event with ID ${eventId} has been successfully accepted.`,
46
+ },
47
+ ],
48
+ };
49
+ } catch (error) {
50
+ if (error.message === 'Authentication required') {
51
+ return {
52
+ content: [
53
+ {
54
+ type: 'text',
55
+ text: "Authentication required. Please use the 'authenticate' tool first.",
56
+ },
57
+ ],
58
+ };
59
+ }
60
+
61
+ return {
62
+ content: [
63
+ {
64
+ type: 'text',
65
+ text: `Error accepting event: ${error.message}`,
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ }
71
+
72
+ module.exports = handleAcceptEvent;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Cancel event functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+
7
+ /**
8
+ * Cancel event handler
9
+ * @param {object} args - Tool arguments
10
+ * @returns {object} - MCP response
11
+ */
12
+ async function handleCancelEvent(args) {
13
+ const { eventId, comment } = args;
14
+
15
+ if (!eventId) {
16
+ return {
17
+ content: [
18
+ {
19
+ type: 'text',
20
+ text: 'Event ID is required to cancel an event.',
21
+ },
22
+ ],
23
+ };
24
+ }
25
+
26
+ try {
27
+ // Get access token
28
+ const accessToken = await ensureAuthenticated();
29
+
30
+ // Build API endpoint
31
+ const endpoint = `me/events/${eventId}/cancel`;
32
+
33
+ // Request body
34
+ const body = {
35
+ comment: comment || 'Cancelled via API',
36
+ };
37
+
38
+ // Make API call
39
+ await callGraphAPI(accessToken, 'POST', endpoint, body);
40
+
41
+ return {
42
+ content: [
43
+ {
44
+ type: 'text',
45
+ text: `Event with ID ${eventId} has been successfully cancelled.`,
46
+ },
47
+ ],
48
+ };
49
+ } catch (error) {
50
+ if (error.message === 'Authentication required') {
51
+ return {
52
+ content: [
53
+ {
54
+ type: 'text',
55
+ text: "Authentication required. Please use the 'authenticate' tool first.",
56
+ },
57
+ ],
58
+ };
59
+ }
60
+
61
+ return {
62
+ content: [
63
+ {
64
+ type: 'text',
65
+ text: `Error cancelling event: ${error.message}`,
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ }
71
+
72
+ module.exports = handleCancelEvent;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Create event functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+ const { DEFAULT_TIMEZONE } = require('../config');
7
+
8
+ /**
9
+ * Create event handler
10
+ * @param {object} args - Tool arguments
11
+ * @returns {object} - MCP response
12
+ */
13
+ async function handleCreateEvent(args) {
14
+ const { subject, start, end, attendees, body } = args;
15
+
16
+ if (!subject || !start || !end) {
17
+ return {
18
+ content: [
19
+ {
20
+ type: 'text',
21
+ text: 'Subject, start, and end times are required to create an event.',
22
+ },
23
+ ],
24
+ };
25
+ }
26
+
27
+ try {
28
+ // Get access token
29
+ const accessToken = await ensureAuthenticated();
30
+
31
+ // Build API endpoint
32
+ const endpoint = `me/events`;
33
+
34
+ // Request body
35
+ const bodyContent = {
36
+ subject,
37
+ start: {
38
+ dateTime: start.dateTime || start,
39
+ timeZone: start.timeZone || DEFAULT_TIMEZONE,
40
+ },
41
+ end: {
42
+ dateTime: end.dateTime || end,
43
+ timeZone: end.timeZone || DEFAULT_TIMEZONE,
44
+ },
45
+ attendees: attendees?.map((email) => ({
46
+ emailAddress: { address: email },
47
+ type: 'required',
48
+ })),
49
+ body: { contentType: 'HTML', content: body || '' },
50
+ };
51
+
52
+ // Make API call
53
+ const response = await callGraphAPI(
54
+ accessToken,
55
+ 'POST',
56
+ endpoint,
57
+ bodyContent
58
+ );
59
+
60
+ const output = [`Event '${subject}' has been successfully created.`];
61
+ if (response.id) {
62
+ output.push(`**ID**: \`${response.id}\``);
63
+ }
64
+ if (response.start) {
65
+ output.push(
66
+ `**Start**: ${response.start.dateTime} (${response.start.timeZone})`
67
+ );
68
+ }
69
+ if (response.end) {
70
+ output.push(
71
+ `**End**: ${response.end.dateTime} (${response.end.timeZone})`
72
+ );
73
+ }
74
+ if (response.webLink) {
75
+ output.push(`**Link**: ${response.webLink}`);
76
+ }
77
+
78
+ return {
79
+ content: [
80
+ {
81
+ type: 'text',
82
+ text: output.join('\n'),
83
+ },
84
+ ],
85
+ _meta: {
86
+ eventId: response.id,
87
+ subject: response.subject,
88
+ start: response.start,
89
+ end: response.end,
90
+ },
91
+ };
92
+ } catch (error) {
93
+ if (error.message === 'Authentication required') {
94
+ return {
95
+ content: [
96
+ {
97
+ type: 'text',
98
+ text: "Authentication required. Please use the 'authenticate' tool first.",
99
+ },
100
+ ],
101
+ };
102
+ }
103
+
104
+ return {
105
+ content: [
106
+ {
107
+ type: 'text',
108
+ text: `Error creating event: ${error.message}`,
109
+ },
110
+ ],
111
+ };
112
+ }
113
+ }
114
+
115
+ module.exports = handleCreateEvent;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Decline event functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+
7
+ /**
8
+ * Decline event handler
9
+ * @param {object} args - Tool arguments
10
+ * @returns {object} - MCP response
11
+ */
12
+ async function handleDeclineEvent(args) {
13
+ const { eventId, comment } = args;
14
+
15
+ if (!eventId) {
16
+ return {
17
+ content: [
18
+ {
19
+ type: 'text',
20
+ text: 'Event ID is required to decline an event.',
21
+ },
22
+ ],
23
+ };
24
+ }
25
+
26
+ try {
27
+ // Get access token
28
+ const accessToken = await ensureAuthenticated();
29
+
30
+ // Build API endpoint
31
+ const endpoint = `me/events/${eventId}/decline`;
32
+
33
+ // Request body
34
+ const body = {
35
+ comment: comment || 'Declined via API',
36
+ };
37
+
38
+ // Make API call
39
+ await callGraphAPI(accessToken, 'POST', endpoint, body);
40
+
41
+ return {
42
+ content: [
43
+ {
44
+ type: 'text',
45
+ text: `Event with ID ${eventId} has been successfully declined.`,
46
+ },
47
+ ],
48
+ };
49
+ } catch (error) {
50
+ if (error.message === 'Authentication required') {
51
+ return {
52
+ content: [
53
+ {
54
+ type: 'text',
55
+ text: "Authentication required. Please use the 'authenticate' tool first.",
56
+ },
57
+ ],
58
+ };
59
+ }
60
+
61
+ return {
62
+ content: [
63
+ {
64
+ type: 'text',
65
+ text: `Error declining event: ${error.message}`,
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ }
71
+
72
+ module.exports = handleDeclineEvent;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Delete event functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+
7
+ /**
8
+ * Delete event handler
9
+ * @param {object} args - Tool arguments
10
+ * @returns {object} - MCP response
11
+ */
12
+ async function handleDeleteEvent(args) {
13
+ const { eventId } = args;
14
+
15
+ if (!eventId) {
16
+ return {
17
+ content: [
18
+ {
19
+ type: 'text',
20
+ text: 'Event ID is required to delete an event.',
21
+ },
22
+ ],
23
+ };
24
+ }
25
+
26
+ try {
27
+ // Get access token
28
+ const accessToken = await ensureAuthenticated();
29
+
30
+ // Build API endpoint
31
+ const endpoint = `me/events/${eventId}`;
32
+
33
+ // Make API call
34
+ await callGraphAPI(accessToken, 'DELETE', endpoint);
35
+
36
+ return {
37
+ content: [
38
+ {
39
+ type: 'text',
40
+ text: `Event with ID ${eventId} has been successfully deleted.`,
41
+ },
42
+ ],
43
+ };
44
+ } catch (error) {
45
+ if (error.message === 'Authentication required') {
46
+ return {
47
+ content: [
48
+ {
49
+ type: 'text',
50
+ text: "Authentication required. Please use the 'authenticate' tool first.",
51
+ },
52
+ ],
53
+ };
54
+ }
55
+
56
+ return {
57
+ content: [
58
+ {
59
+ type: 'text',
60
+ text: `Error deleting event: ${error.message}`,
61
+ },
62
+ ],
63
+ };
64
+ }
65
+ }
66
+
67
+ module.exports = handleDeleteEvent;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Calendar module for Outlook Assistant server
3
+ */
4
+ const handleListEvents = require('./list');
5
+ const handleDeclineEvent = require('./decline');
6
+ const handleCreateEvent = require('./create');
7
+ const handleCancelEvent = require('./cancel');
8
+ const handleDeleteEvent = require('./delete');
9
+
10
+ // Calendar tool definitions (consolidated: 5 → 3)
11
+ const calendarTools = [
12
+ {
13
+ name: 'list-events',
14
+ description: 'Lists upcoming events from your calendar',
15
+ annotations: {
16
+ title: 'List Calendar Events',
17
+ readOnlyHint: true,
18
+ openWorldHint: false,
19
+ },
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {
23
+ count: {
24
+ type: 'number',
25
+ description: 'Number of events to retrieve (default: 10, max: 50)',
26
+ },
27
+ },
28
+ required: [],
29
+ },
30
+ handler: handleListEvents,
31
+ },
32
+ {
33
+ name: 'create-event',
34
+ description: 'Creates a new calendar event',
35
+ annotations: {
36
+ title: 'Create Calendar Event',
37
+ readOnlyHint: false,
38
+ destructiveHint: false,
39
+ openWorldHint: false,
40
+ },
41
+ inputSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ subject: {
45
+ type: 'string',
46
+ description: 'The subject of the event',
47
+ },
48
+ start: {
49
+ type: 'string',
50
+ description: 'The start time of the event in ISO 8601 format',
51
+ },
52
+ end: {
53
+ type: 'string',
54
+ description: 'The end time of the event in ISO 8601 format',
55
+ },
56
+ attendees: {
57
+ type: 'array',
58
+ items: {
59
+ type: 'string',
60
+ },
61
+ description: 'List of attendee email addresses',
62
+ },
63
+ body: {
64
+ type: 'string',
65
+ description: 'Optional body content for the event',
66
+ },
67
+ },
68
+ required: ['subject', 'start', 'end'],
69
+ },
70
+ handler: handleCreateEvent,
71
+ },
72
+ {
73
+ name: 'manage-event',
74
+ description:
75
+ 'Manage an existing calendar event. action=decline declines an invitation. action=cancel cancels an event you organised. action=delete permanently removes an event.',
76
+ annotations: {
77
+ title: 'Manage Calendar Event',
78
+ readOnlyHint: false,
79
+ destructiveHint: true,
80
+ openWorldHint: false,
81
+ },
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ action: {
86
+ type: 'string',
87
+ enum: ['decline', 'cancel', 'delete'],
88
+ description: 'Action to perform (required)',
89
+ },
90
+ eventId: {
91
+ type: 'string',
92
+ description: 'The ID of the event',
93
+ },
94
+ comment: {
95
+ type: 'string',
96
+ description: 'Optional comment for declining or cancelling the event',
97
+ },
98
+ },
99
+ required: ['action', 'eventId'],
100
+ },
101
+ handler: async (args) => {
102
+ switch (args.action) {
103
+ case 'decline':
104
+ return handleDeclineEvent(args);
105
+ case 'cancel':
106
+ return handleCancelEvent(args);
107
+ case 'delete':
108
+ return handleDeleteEvent(args);
109
+ default:
110
+ return {
111
+ content: [
112
+ {
113
+ type: 'text',
114
+ text: "Invalid action. Use 'decline', 'cancel', or 'delete'.",
115
+ },
116
+ ],
117
+ };
118
+ }
119
+ },
120
+ },
121
+ ];
122
+
123
+ module.exports = {
124
+ calendarTools,
125
+ handleListEvents,
126
+ handleDeclineEvent,
127
+ handleCreateEvent,
128
+ handleCancelEvent,
129
+ handleDeleteEvent,
130
+ };