@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
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Safety controls for Outlook Assistant Server
3
+ *
4
+ * Provides rate limiting, recipient allowlists, and content safety markers
5
+ * to protect against unintended destructive actions.
6
+ */
7
+
8
+ // Per-tool session counters for rate limiting
9
+ const sessionCounters = {};
10
+
11
+ /**
12
+ * Check rate limit for a tool. Returns null if OK, or an error response if exceeded.
13
+ * @param {string} toolName - The tool name to rate-limit
14
+ * @param {number} [limit] - Override limit (default: from env or 10)
15
+ * @returns {object|null} - MCP error response if limit exceeded, null if OK
16
+ */
17
+ function checkRateLimit(toolName, limit) {
18
+ const envKey = `OUTLOOK_MAX_${toolName.toUpperCase().replace(/-/g, '_')}_PER_SESSION`;
19
+ const maxPerSession =
20
+ limit ||
21
+ parseInt(
22
+ process.env[envKey] || process.env.OUTLOOK_MAX_EMAILS_PER_SESSION || '0',
23
+ 10
24
+ );
25
+
26
+ // 0 means unlimited (disabled)
27
+ if (maxPerSession <= 0) return null;
28
+
29
+ if (!sessionCounters[toolName]) sessionCounters[toolName] = 0;
30
+
31
+ if (sessionCounters[toolName] >= maxPerSession) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: 'text',
36
+ text: `Rate limit reached: ${maxPerSession} ${toolName} operations per session. Restart the server to reset. Configure via ${envKey} environment variable.`,
37
+ },
38
+ ],
39
+ };
40
+ }
41
+
42
+ sessionCounters[toolName]++;
43
+ return null;
44
+ }
45
+
46
+ /**
47
+ * Check recipient allowlist. Returns null if OK, or an error response if blocked.
48
+ * @param {Array<{emailAddress: {address: string}}>} recipients - Graph API recipient objects
49
+ * @returns {object|null} - MCP error response if blocked, null if OK
50
+ */
51
+ function checkRecipientAllowlist(recipients) {
52
+ const allowlistRaw = process.env.OUTLOOK_ALLOWED_RECIPIENTS;
53
+ if (!allowlistRaw) return null; // No allowlist configured — allow all
54
+
55
+ const allowed = allowlistRaw
56
+ .split(',')
57
+ .map((s) => s.trim().toLowerCase())
58
+ .filter(Boolean);
59
+
60
+ if (allowed.length === 0) return null;
61
+
62
+ const blocked = [];
63
+ for (const r of recipients) {
64
+ const addr = (r.emailAddress?.address || '').toLowerCase();
65
+ const isAllowed = allowed.some(
66
+ (rule) =>
67
+ addr === rule || // Exact match
68
+ addr.endsWith(`@${rule}`) // Domain match
69
+ );
70
+ if (!isAllowed) blocked.push(addr);
71
+ }
72
+
73
+ if (blocked.length > 0) {
74
+ return {
75
+ content: [
76
+ {
77
+ type: 'text',
78
+ text: `Recipient not allowed: ${blocked.join(', ')}. Allowed recipients/domains: ${allowed.join(', ')}. Configure via OUTLOOK_ALLOWED_RECIPIENTS environment variable.`,
79
+ },
80
+ ],
81
+ };
82
+ }
83
+
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Format a dry-run preview for send-email
89
+ * @param {object} emailObject - The composed Graph API email object
90
+ * @returns {object} - MCP response with preview
91
+ */
92
+ function formatDryRunPreview(emailObject) {
93
+ const msg = emailObject.message;
94
+ const to = (msg.toRecipients || [])
95
+ .map((r) => r.emailAddress?.address)
96
+ .join(', ');
97
+ const cc = (msg.ccRecipients || [])
98
+ .map((r) => r.emailAddress?.address)
99
+ .join(', ');
100
+ const bcc = (msg.bccRecipients || [])
101
+ .map((r) => r.emailAddress?.address)
102
+ .join(', ');
103
+
104
+ let preview = `DRY RUN — Email NOT sent.\n\n`;
105
+ preview += `To: ${to}\n`;
106
+ if (cc) preview += `CC: ${cc}\n`;
107
+ if (bcc) preview += `BCC: ${bcc}\n`;
108
+ preview += `Subject: ${msg.subject}\n`;
109
+ preview += `Importance: ${msg.importance || 'normal'}\n`;
110
+ preview += `Content-Type: ${msg.body?.contentType || 'text'}\n`;
111
+ preview += `Save to Sent: ${emailObject.saveToSentItems !== false}\n`;
112
+ preview += `\n--- Body ---\n${msg.body?.content || '(empty)'}\n--- End Body ---`;
113
+
114
+ return {
115
+ content: [{ type: 'text', text: preview }],
116
+ };
117
+ }
118
+
119
+ module.exports = {
120
+ checkRateLimit,
121
+ checkRecipientAllowlist,
122
+ formatDryRunPreview,
123
+ };