@littlebearapps/outlook-assistant 3.4.1 → 3.5.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 CHANGED
@@ -20,3 +20,6 @@ USE_TEST_MODE=false
20
20
 
21
21
  # Restrict sending to specific domains/addresses (comma-separated)
22
22
  # OUTLOOK_ALLOWED_RECIPIENTS=mycompany.com,partner@example.com
23
+
24
+ # Optional: Enable immutable IDs (IDs persist through folder moves)
25
+ # OUTLOOK_IMMUTABLE_IDS=true
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="docs/assets/outlook-assistant-logo-full.svg" height="200" alt="Outlook Assistant" />
2
+ <img src="https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/docs/assets/outlook-assistant-logo-full.png" height="200" alt="Outlook Assistant" />
3
3
  </p>
4
4
 
5
5
  <h1 align="center">Outlook Assistant</h1>
@@ -11,13 +11,9 @@
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/@littlebearapps/outlook-assistant"><img src="https://img.shields.io/npm/v/@littlebearapps/outlook-assistant" alt="npm version" /></a>
13
13
  <a href="https://www.npmjs.com/package/@littlebearapps/outlook-assistant"><img src="https://img.shields.io/npm/dm/@littlebearapps/outlook-assistant" alt="npm downloads" /></a>
14
- <a href="https://github.com/littlebearapps/outlook-assistant/stargazers"><img src="https://img.shields.io/github/stars/littlebearapps/outlook-assistant" alt="GitHub stars" /></a>
15
- <a href="https://github.com/littlebearapps/outlook-assistant/commits/main"><img src="https://img.shields.io/github/last-commit/littlebearapps/outlook-assistant" alt="Last commit" /></a>
16
14
  <a href="https://github.com/littlebearapps/outlook-assistant/actions/workflows/ci.yml"><img src="https://github.com/littlebearapps/outlook-assistant/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
17
15
  <a href="https://github.com/littlebearapps/outlook-assistant/actions/workflows/codeql.yml"><img src="https://github.com/littlebearapps/outlook-assistant/actions/workflows/codeql.yml/badge.svg" alt="CodeQL" /></a>
18
- <a href="https://github.com/littlebearapps/outlook-assistant/issues"><img src="https://img.shields.io/github/issues/littlebearapps/outlook-assistant" alt="Open issues" /></a>
19
16
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
20
- <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen" alt="Node.js" /></a>
21
17
  </p>
22
18
 
23
19
  Outlook Assistant connects AI assistants to your Microsoft Outlook account through the [Model Context Protocol](https://modelcontextprotocol.io/). Ask your AI assistant to search your inbox, send emails, schedule meetings, manage contacts, and configure mailbox settings — without leaving the conversation. Works with Claude, Cursor, Windsurf, and any MCP-compatible client.
@@ -37,7 +33,7 @@ Outlook Assistant connects AI assistants to your Microsoft Outlook account throu
37
33
  ### What you can do
38
34
 
39
35
  - 📨 **Search and read emails** — find messages by sender, subject, date, or keywords; read full threads with conversation grouping; batch flag, move, export, or categorise multiple emails at once
40
- - 🛡️ **Send emails with safety controls** — dry-run preview, session rate limiting, and recipient allowlist to prevent mistakes
36
+ - 🛡️ **Send emails with safety controls** — dry-run preview, pre-send mail tips (out-of-office, mailbox full, delivery restrictions), session rate limiting, and recipient allowlist to prevent mistakes
41
37
  - 📅 **Manage your calendar** — view upcoming events, schedule meetings with attendees, decline or cancel invitations
42
38
  - 📦 **Export emails** — save to Markdown, EML, MBOX, JSON, or HTML for archiving, analysis, or migration; export search results or entire threads in one call
43
39
  - 🔍 **Investigate email headers** — check DKIM, SPF, and DMARC authentication; trace delivery chains; analyse spam scores — useful for phishing investigation and compliance
@@ -64,7 +60,7 @@ Outlook Assistant connects AI assistants to your Microsoft Outlook account throu
64
60
 
65
61
  | Module | Tools | What You Can Do |
66
62
  |--------|------:|-----------------|
67
- | **Email** | 6 | `search-emails` (list/search/delta/conversations), `read-email` (content + forensic headers), `send-email` (with dry-run), `update-email` (read status, flags), `attachments`, `export` |
63
+ | **Email** | 7 | `search-emails` (list/search/delta/conversations), `read-email` (content + forensic headers), `send-email` (with dry-run + mail tips), `update-email` (read status, flags), `attachments`, `export`, `get-mail-tips` |
68
64
  | **Calendar** | 3 | `list-events`, `create-event`, `manage-event` (decline/cancel/delete) |
69
65
  | **Contacts** | 2 | `manage-contact` (list/search/get/create/update/delete), `search-people` |
70
66
  | **Categories** | 3 | `manage-category` (CRUD), `apply-category`, `manage-focused-inbox` |
@@ -74,7 +70,7 @@ Outlook Assistant connects AI assistants to your Microsoft Outlook account throu
74
70
  | **Advanced** | 2 | `access-shared-mailbox`, `find-meeting-rooms` |
75
71
  | **Auth** | 1 | `auth` (status/authenticate/about) |
76
72
 
77
- **20 tools total** — consolidated from 55 for optimal AI performance. See the [Tools Reference](docs/quickrefs/tools-reference.md) for complete parameter details.
73
+ **21 tools total** — consolidated from 55 for optimal AI performance. See the [Tools Reference](docs/quickrefs/tools-reference.md) for complete parameter details.
78
74
 
79
75
  ### Export Formats
80
76
 
@@ -115,6 +111,7 @@ Outlook Assistant works with both personal and work/school Microsoft accounts, b
115
111
  - **Email forensics** — full header analysis (DKIM, SPF, DMARC, delivery chain, spam scores) built in as a first-class feature — useful for phishing investigation, compliance, and security review.
116
112
  - **Delta sync** — incremental inbox monitoring returns only what changed since your last check, with tokens for continuous polling. Designed for agent workflows that need to watch a mailbox.
117
113
  - **Batch operations** — flag, move, export, or categorise multiple emails in a single call. Search-driven export lets you batch-export results without collecting IDs manually.
114
+ - **Pre-send intelligence** — check recipients for out-of-office, full mailbox, delivery restrictions, and moderation status before sending — no other Outlook MCP server offers this.
118
115
  - **Compound automation** — rules, categories, folders, and Focused Inbox work together. Set up complete inbox management through your AI assistant in one conversation.
119
116
 
120
117
  ## Safety & Token Efficiency
@@ -124,11 +121,12 @@ Outlook Assistant is designed with safety-first principles for AI-driven email a
124
121
  **Destructive action safeguards** — Every tool carries [MCP annotations](https://modelcontextprotocol.io/docs/concepts/tools#annotations) (`readOnlyHint`, `destructiveHint`, `idempotentHint`) so AI clients can auto-approve safe reads and prompt for confirmation on destructive operations like sending email or deleting events.
125
122
 
126
123
  **Send-email protections** — The `send-email` tool includes:
124
+ - **Pre-send mail tips** (`checkRecipients: true`) — check recipients for out-of-office, mailbox full, delivery restrictions before sending
127
125
  - **Dry-run mode** (`dryRun: true`) — preview composed emails without sending
128
126
  - **Session rate limiting** — configurable via `OUTLOOK_MAX_EMAILS_PER_SESSION` (default: unlimited)
129
127
  - **Recipient allowlist** — restrict sending to approved addresses/domains via `OUTLOOK_ALLOWED_RECIPIENTS`
130
128
 
131
- **Token-optimised architecture** — Tools are consolidated using the STRAP (Single Tool, Resource, Action Pattern) approach. 20 tools instead of 55 reduces per-turn overhead by ~11,000 tokens (~64%), keeping more of the AI's context window available for your actual conversation. Fewer tools also means the AI selects the right tool more accurately — research shows tool selection degrades beyond ~40 tools.
129
+ **Token-optimised architecture** — Tools are consolidated using the STRAP (Single Tool, Resource, Action Pattern) approach. 21 tools instead of 55 reduces per-turn overhead by ~11,000 tokens (~64%), keeping more of the AI's context window available for your actual conversation. Fewer tools also means the AI selects the right tool more accurately — research shows tool selection degrades beyond ~40 tools.
132
130
 
133
131
  > **Important**: These safeguards are defence-in-depth measures that reduce risk, but they are not a guarantee against unintended actions. AI-driven access to your email is inherently sensitive — always review tool calls before approving, particularly for sends and deletes. No automated guardrail is foolproof, and you remain responsible for actions taken through your mailbox.
134
132
 
@@ -191,6 +189,10 @@ Then set environment variables in your `.env` or shell.
191
189
  <details>
192
190
  <summary><strong>Cursor</strong> (<code>.cursor/mcp.json</code>)</summary>
193
191
 
192
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=Outlook%20Assistant&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBsaXR0bGViZWFyYXBwcy9vdXRsb29rLWFzc2lzdGFudCJdLCJlbnYiOnsiT1VUTE9PS19DTElFTlRfSUQiOiIiLCJPVVRMT09LX0NMSUVOVF9TRUNSRVQiOiIifX0=)
193
+
194
+ Or add manually to `.cursor/mcp.json`:
195
+
194
196
  ```json
195
197
  {
196
198
  "mcpServers": {
@@ -359,11 +361,12 @@ This starts a local server on port 3333 to handle the OAuth callback.
359
361
 
360
362
  ```
361
363
  outlook-assistant/
362
- ├── index.js # Main entry point (20 tools)
364
+ ├── index.js # Main entry point (21 tools)
363
365
  ├── config.js # Configuration settings
364
366
  ├── outlook-auth-server.js # OAuth server (port 3333)
365
367
  ├── auth/ # Authentication module (1 tool)
366
- ├── email/ # Email module (6 tools)
368
+ ├── email/ # Email module (7 tools)
369
+ │ ├── mail-tips.js # Pre-send recipient validation
367
370
  │ ├── headers.js # Email header retrieval
368
371
  │ ├── mime.js # Raw MIME/EML content
369
372
  │ ├── conversations.js # Thread listing/export
@@ -377,7 +380,7 @@ outlook-assistant/
377
380
  ├── rules/ # Rules module (1 tool)
378
381
  ├── advanced/ # Advanced module (2 tools)
379
382
  └── utils/
380
- ├── graph-api.js # Microsoft Graph API client
383
+ ├── graph-api.js # Microsoft Graph API client (includes $batch)
381
384
  ├── safety.js # Rate limiting, recipient allowlist, dry-run
382
385
  ├── odata-helpers.js # OData query building
383
386
  ├── field-presets.js # Token-efficient field selections
@@ -444,9 +447,9 @@ USE_TEST_MODE=true npm start
444
447
  |-------|-------------|
445
448
  | [Getting Started](docs/how-to/getting-started/connect-outlook-to-claude.md) | Install, configure, and authenticate — start here |
446
449
  | [Azure Setup Guide](docs/guides/azure-setup.md) | Azure account creation, app registration, permissions, and secrets |
447
- | [How-To Guides](docs/how-to/index.md) | 27 practical guides for email, calendar, contacts, and settings |
450
+ | [How-To Guides](docs/how-to/index.md) | 28 practical guides for email, calendar, contacts, and settings |
448
451
  | [Troubleshooting & FAQ](docs/how-to/getting-started/verify-your-connection.md#common-connection-problems) | Common problems, re-authentication, and frequently asked questions |
449
- | [Tools Reference](docs/quickrefs/tools-reference.md) | All 20 tools with parameters |
452
+ | [Tools Reference](docs/quickrefs/tools-reference.md) | All 21 tools with parameters |
450
453
  | [AI Agent Guide](docs/how-to/ai-agents/using-outlook-assistant-in-agents.md) | Tool selection and workflow patterns for AI agents |
451
454
 
452
455
  Full documentation: [docs/](docs/README.md)
package/config.js CHANGED
@@ -90,6 +90,9 @@ module.exports = {
90
90
  // Search defaults (reduced for token efficiency)
91
91
  DEFAULT_SEARCH_RESULTS: DEFAULT_LIMITS.searchEmails,
92
92
 
93
+ // Immutable IDs (opt-in: IDs persist through folder moves)
94
+ USE_IMMUTABLE_IDS: process.env.OUTLOOK_IMMUTABLE_IDS === 'true',
95
+
93
96
  // Timezone
94
97
  DEFAULT_TIMEZONE: 'Australia/Melbourne', // Updated for Nathan's timezone
95
98
  };
package/email/export.js CHANGED
@@ -281,10 +281,8 @@ async function handleBatchExportEmails(args) {
281
281
  }
282
282
 
283
283
  const csvContent = formatEmailsAsCSV(emails);
284
- const csvPath = path.join(
285
- outputDir,
286
- `batch_export_${new Date().toISOString().slice(0, 10)}.csv`
287
- );
284
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
285
+ const csvPath = path.join(outputDir, `batch_export_${timestamp}.csv`);
288
286
  fs.writeFileSync(csvPath, csvContent, 'utf8');
289
287
  const totalBytes = Buffer.byteLength(csvContent, 'utf8');
290
288
 
package/email/index.js CHANGED
@@ -22,6 +22,7 @@ const {
22
22
  handleGetConversation,
23
23
  handleExportConversation,
24
24
  } = require('./conversations');
25
+ const { handleGetMailTips } = require('./mail-tips');
25
26
 
26
27
  // Import flag handlers from advanced module
27
28
  const { handleSetMessageFlag, handleClearMessageFlag } = require('../advanced');
@@ -278,6 +279,11 @@ const emailTools = [
278
279
  description:
279
280
  'Preview email without sending (default: false). Returns composed email for review.',
280
281
  },
282
+ checkRecipients: {
283
+ type: 'boolean',
284
+ description:
285
+ 'Check recipients for out-of-office, mailbox full, delivery restrictions before sending (default: false). Combine with dryRun=true for pre-send review.',
286
+ },
281
287
  },
282
288
  required: ['to', 'subject', 'body'],
283
289
  },
@@ -513,6 +519,42 @@ const emailTools = [
513
519
  }
514
520
  },
515
521
  },
522
+ {
523
+ name: 'get-mail-tips',
524
+ description:
525
+ 'Check recipients before sending: out-of-office status, mailbox full, external recipients, delivery restrictions, moderation, group member counts, and max message size. No competitor offers this.',
526
+ annotations: {
527
+ title: 'Mail Tips',
528
+ readOnlyHint: true,
529
+ openWorldHint: false,
530
+ },
531
+ inputSchema: {
532
+ type: 'object',
533
+ properties: {
534
+ recipients: {
535
+ oneOf: [
536
+ {
537
+ type: 'array',
538
+ items: { type: 'string' },
539
+ description: 'Array of email addresses to check',
540
+ },
541
+ {
542
+ type: 'string',
543
+ description: 'Comma-separated email addresses to check',
544
+ },
545
+ ],
546
+ description: 'Email addresses to check for mail tips',
547
+ },
548
+ tipTypes: {
549
+ type: 'string',
550
+ description:
551
+ 'Comma-separated tip types to request (default: all). Options: automaticReplies, mailboxFullStatus, customMailTip, externalMemberCount, totalMemberCount, maxMessageSize, deliveryRestriction, moderationStatus, recipientScope, recipientSuggestions',
552
+ },
553
+ },
554
+ required: ['recipients'],
555
+ },
556
+ handler: handleGetMailTips,
557
+ },
516
558
  ];
517
559
 
518
560
  module.exports = {
@@ -534,4 +576,5 @@ module.exports = {
534
576
  handleListConversations,
535
577
  handleGetConversation,
536
578
  handleExportConversation,
579
+ handleGetMailTips,
537
580
  };
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Mail tips functionality — pre-send validation via Graph API
3
+ *
4
+ * Checks recipients for out-of-office, mailbox full, external,
5
+ * delivery restrictions, and more before sending.
6
+ */
7
+ const { callGraphAPI } = require('../utils/graph-api');
8
+ const { ensureAuthenticated } = require('../auth');
9
+
10
+ /**
11
+ * All available mail tip types from Microsoft Graph API
12
+ */
13
+ const MAIL_TIP_TYPES = [
14
+ 'automaticReplies',
15
+ 'mailboxFullStatus',
16
+ 'customMailTip',
17
+ 'externalMemberCount',
18
+ 'totalMemberCount',
19
+ 'maxMessageSize',
20
+ 'deliveryRestriction',
21
+ 'moderationStatus',
22
+ 'recipientScope',
23
+ 'recipientSuggestions',
24
+ ];
25
+
26
+ /**
27
+ * Format mail tips into readable markdown
28
+ * @param {Array} mailTips - Array of mail tip objects from Graph API
29
+ * @returns {string} - Formatted markdown output
30
+ */
31
+ function formatMailTips(mailTips) {
32
+ const lines = [];
33
+ let warningCount = 0;
34
+
35
+ for (const tip of mailTips) {
36
+ const email = tip.emailAddress?.address || 'Unknown';
37
+ const tipLines = [];
38
+ const warnings = [];
39
+
40
+ // Out-of-office / automatic replies
41
+ if (
42
+ tip.automaticReplies?.message &&
43
+ tip.automaticReplies.message.trim() !== ''
44
+ ) {
45
+ warnings.push('Out of Office');
46
+ const reply = tip.automaticReplies;
47
+ tipLines.push(
48
+ ` **Out of Office**: ${reply.message.replace(/\n/g, ' ').substring(0, 200)}`
49
+ );
50
+ if (reply.scheduledStartTime || reply.scheduledEndTime) {
51
+ const start = reply.scheduledStartTime?.dateTime || '';
52
+ const end = reply.scheduledEndTime?.dateTime || '';
53
+ if (start || end) {
54
+ tipLines.push(` *Schedule*: ${start} → ${end}`);
55
+ }
56
+ }
57
+ }
58
+
59
+ // Mailbox full
60
+ if (tip.mailboxFullStatus) {
61
+ warnings.push('Mailbox Full');
62
+ tipLines.push(
63
+ ` **Mailbox Full**: Recipient's mailbox is full — delivery may fail`
64
+ );
65
+ }
66
+
67
+ // Custom mail tip (admin-configured)
68
+ if (tip.customMailTip) {
69
+ warnings.push('Custom Tip');
70
+ tipLines.push(` **Notice**: ${tip.customMailTip}`);
71
+ }
72
+
73
+ // Delivery restriction
74
+ if (tip.deliveryRestriction) {
75
+ const restriction = tip.deliveryRestriction;
76
+ if (restriction.isDeliveryRestricted) {
77
+ warnings.push('Delivery Restricted');
78
+ tipLines.push(
79
+ ` **Delivery Restricted**: ${restriction.message || 'Cannot deliver to this recipient'}`
80
+ );
81
+ }
82
+ }
83
+
84
+ // Moderation status
85
+ if (tip.moderationStatus && tip.moderationStatus !== 'notModerated') {
86
+ warnings.push('Moderated');
87
+ tipLines.push(
88
+ ` **Moderated**: Messages to this recipient require approval`
89
+ );
90
+ }
91
+
92
+ // Recipient scope (external)
93
+ if (tip.recipientScope === 'external') {
94
+ tipLines.push(` **External**: Recipient is outside your organisation`);
95
+ }
96
+
97
+ // Max message size
98
+ if (tip.maxMessageSize && tip.maxMessageSize > 0) {
99
+ const sizeMB = (tip.maxMessageSize / (1024 * 1024)).toFixed(1);
100
+ tipLines.push(` *Max message size*: ${sizeMB} MB`);
101
+ }
102
+
103
+ // Group member counts
104
+ if (tip.totalMemberCount > 0) {
105
+ tipLines.push(
106
+ ` *Group members*: ${tip.totalMemberCount} total (${tip.externalMemberCount || 0} external)`
107
+ );
108
+ }
109
+
110
+ // Build section for this recipient
111
+ const statusIcon = warnings.length > 0 ? '⚠' : '✓';
112
+ lines.push(`### ${statusIcon} ${email}`);
113
+
114
+ if (warnings.length > 0) {
115
+ lines.push(`**Warnings**: ${warnings.join(', ')}`);
116
+ warningCount += warnings.length;
117
+ }
118
+
119
+ if (tipLines.length > 0) {
120
+ lines.push(tipLines.join('\n'));
121
+ } else {
122
+ lines.push(' No issues detected');
123
+ }
124
+ lines.push('');
125
+ }
126
+
127
+ return { formatted: lines.join('\n'), warningCount };
128
+ }
129
+
130
+ /**
131
+ * Get mail tips for specified recipients
132
+ * @param {object} args - Tool arguments
133
+ * @returns {object} - MCP response
134
+ */
135
+ async function handleGetMailTips(args) {
136
+ const { recipients, tipTypes } = args;
137
+
138
+ if (!recipients || recipients.length === 0) {
139
+ return {
140
+ content: [
141
+ {
142
+ type: 'text',
143
+ text: 'At least one recipient email address is required.',
144
+ },
145
+ ],
146
+ };
147
+ }
148
+
149
+ try {
150
+ const accessToken = await ensureAuthenticated();
151
+
152
+ const requestBody = {
153
+ EmailAddresses: Array.isArray(recipients)
154
+ ? recipients
155
+ : recipients.split(',').map((e) => e.trim()),
156
+ MailTipsOptions: tipTypes || MAIL_TIP_TYPES.join(','),
157
+ };
158
+
159
+ const response = await callGraphAPI(
160
+ accessToken,
161
+ 'POST',
162
+ 'me/getMailTips',
163
+ requestBody
164
+ );
165
+
166
+ const mailTips = response.value || [];
167
+
168
+ if (mailTips.length === 0) {
169
+ return {
170
+ content: [
171
+ {
172
+ type: 'text',
173
+ text: 'No mail tips returned for the specified recipients.',
174
+ },
175
+ ],
176
+ };
177
+ }
178
+
179
+ const { formatted, warningCount } = formatMailTips(mailTips);
180
+
181
+ let header = `# Mail Tips\n\n`;
182
+ header += `**Recipients checked**: ${mailTips.length}\n`;
183
+ header += `**Warnings**: ${warningCount}\n\n`;
184
+
185
+ return {
186
+ content: [{ type: 'text', text: header + formatted }],
187
+ _meta: {
188
+ recipientCount: mailTips.length,
189
+ warningCount,
190
+ },
191
+ };
192
+ } catch (error) {
193
+ if (error.message === 'Authentication required') {
194
+ return {
195
+ content: [
196
+ {
197
+ type: 'text',
198
+ text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
199
+ },
200
+ ],
201
+ };
202
+ }
203
+ return {
204
+ content: [
205
+ { type: 'text', text: `Error getting mail tips: ${error.message}` },
206
+ ],
207
+ };
208
+ }
209
+ }
210
+
211
+ module.exports = { handleGetMailTips, formatMailTips, MAIL_TIP_TYPES };
package/email/send.js CHANGED
@@ -9,6 +9,7 @@ const {
9
9
  checkRecipientAllowlist,
10
10
  formatDryRunPreview,
11
11
  } = require('../utils/safety');
12
+ const { handleGetMailTips } = require('./mail-tips');
12
13
 
13
14
  /**
14
15
  * Send email handler
@@ -25,6 +26,7 @@ async function handleSendEmail(args) {
25
26
  importance = 'normal',
26
27
  saveToSentItems = true,
27
28
  dryRun = false,
29
+ checkRecipients = false,
28
30
  } = args;
29
31
 
30
32
  // Validate required parameters
@@ -99,6 +101,48 @@ async function handleSendEmail(args) {
99
101
  const allowlistError = checkRecipientAllowlist(allRecipients);
100
102
  if (allowlistError) return allowlistError;
101
103
 
104
+ // Pre-send mail tips check
105
+ if (checkRecipients) {
106
+ const allAddresses = allRecipients.map((r) => r.emailAddress.address);
107
+ const tipsResult = await handleGetMailTips({
108
+ recipients: allAddresses,
109
+ });
110
+
111
+ // If there are warnings, prepend them to the response
112
+ if (tipsResult._meta?.warningCount > 0 && dryRun) {
113
+ // In dry-run mode, include mail tips in the preview
114
+ const tipsText = tipsResult.content[0]?.text || '';
115
+ const preview = formatDryRunPreview({
116
+ message: {
117
+ subject,
118
+ body: {
119
+ contentType:
120
+ /<(html|div|p|h[1-6]|br|table|ul|ol|li|span|a\s|img|strong|em|b|i)\b/i.test(
121
+ body
122
+ )
123
+ ? 'html'
124
+ : 'text',
125
+ content: body,
126
+ },
127
+ toRecipients,
128
+ ccRecipients: ccRecipients.length > 0 ? ccRecipients : undefined,
129
+ bccRecipients: bccRecipients.length > 0 ? bccRecipients : undefined,
130
+ importance,
131
+ },
132
+ saveToSentItems,
133
+ });
134
+ return {
135
+ content: [
136
+ {
137
+ type: 'text',
138
+ text: tipsText + '\n\n---\n\n' + preview.content[0].text,
139
+ },
140
+ ],
141
+ _meta: { mailTips: tipsResult._meta },
142
+ };
143
+ }
144
+ }
145
+
102
146
  // Prepare email object
103
147
  const emailObject = {
104
148
  message: {
package/llms.txt CHANGED
@@ -11,12 +11,12 @@ Built by [Little Bear Apps](https://littlebearapps.com).
11
11
  - **License**: MIT
12
12
  - **Node.js**: >= 18.0.0
13
13
  - **Authentication**: OAuth 2.0 with Microsoft Graph API (requires Azure app registration)
14
- - **Tools**: 20 consolidated tools across 9 modules (reduced from 55 for optimal AI performance)
14
+ - **Tools**: 21 tools across 9 modules (reduced from 55 for optimal AI performance)
15
15
 
16
16
  ## Why Outlook Assistant?
17
17
 
18
18
  - Read, search, send, and export emails directly from Claude instead of switching apps
19
- - 6 email tools covering search, conversations, attachments, and bulk export
19
+ - 7 email tools covering search, conversations, attachments, bulk export, and pre-send mail tips
20
20
  - Manage calendar events, contacts, rules, categories, and mailbox settings in one place
21
21
  - Export to multiple formats: MIME/EML, MBOX, Markdown, JSON, HTML
22
22
 
@@ -26,13 +26,14 @@ Built by [Little Bear Apps](https://littlebearapps.com).
26
26
  - **Email forensics**: Built-in header analysis for DKIM, SPF, DMARC authentication, delivery chains, and spam scores — useful for phishing investigation and compliance
27
27
  - **Delta sync**: Incremental inbox monitoring — returns only new, modified, and deleted emails since last check, with tokens for continuous polling
28
28
  - **Batch operations**: Flag, move, export, or categorise multiple emails in a single tool call; search-driven export for batch archiving without collecting IDs
29
+ - **Pre-send intelligence**: Check recipients for out-of-office, mailbox full, delivery restrictions, and moderation before sending — no other Outlook MCP server offers this
29
30
  - **Compound automation**: Rules + categories + folders + Focused Inbox for complete inbox management in one conversation
30
31
 
31
32
  ## Safety & Token Efficiency
32
33
 
33
- - **MCP safety annotations** on all 20 tools — AI clients auto-approve reads and prompt for destructive operations
34
- - **Send-email protections**: dry-run preview, session rate limiting, recipient allowlist
35
- - **Token-optimised**: 20 tools instead of 55 saves ~11,000 tokens per turn (~64% reduction), improving AI accuracy and context efficiency
34
+ - **MCP safety annotations** on all 21 tools — AI clients auto-approve reads and prompt for destructive operations
35
+ - **Send-email protections**: pre-send mail tips, dry-run preview, session rate limiting, recipient allowlist
36
+ - **Token-optimised**: 21 tools instead of 55 saves ~11,000 tokens per turn (~64% reduction), improving AI accuracy and context efficiency
36
37
  - These safeguards reduce risk but are not foolproof — always review actions before approving
37
38
 
38
39
  ## Quick Start
@@ -57,7 +58,7 @@ Requires an Azure app registration with Microsoft Graph delegated permissions. S
57
58
  ## Tool Categories
58
59
 
59
60
  - **Authentication (1 tool)**: `auth` — OAuth flow, status, about
60
- - **Email (6 tools)**: `search-emails`, `read-email`, `send-email`, `update-email`, `attachments`, `export`
61
+ - **Email (7 tools)**: `search-emails`, `read-email`, `send-email`, `update-email`, `attachments`, `export`, `get-mail-tips`
61
62
  - **Calendar (3 tools)**: `list-events`, `create-event`, `manage-event`
62
63
  - **Contacts (2 tools)**: `manage-contact`, `search-people`
63
64
  - **Folders (1 tool)**: `folders` — list, create, move, stats
@@ -69,7 +70,7 @@ Requires an Azure app registration with Microsoft Graph delegated permissions. S
69
70
  ## Documentation
70
71
 
71
72
  - [README](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/README.md): Full documentation including setup, Azure configuration, and usage
72
- - [Tools Reference](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/docs/quickrefs/tools-reference.md): All 20 tools with parameters and safety annotations
73
+ - [Tools Reference](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/docs/quickrefs/tools-reference.md): All 21 tools with parameters and safety annotations
73
74
  - [CLAUDE.md](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/CLAUDE.md): Quick reference for development
74
75
  - [CONTRIBUTING](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/CONTRIBUTING.md): Contribution guidelines
75
76
  - [CHANGELOG](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/CHANGELOG.md): Version history
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@littlebearapps/outlook-assistant",
3
- "version": "3.4.1",
3
+ "version": "3.5.0",
4
4
  "mcpName": "io.github.littlebearapps/outlook-assistant",
5
- "description": "Outlook Assistant — MCP server with 20 tools for email, calendar, contacts, and settings via Microsoft Graph API",
5
+ "description": "Outlook Assistant — MCP server with 21 tools for email, calendar, contacts, and settings via Microsoft Graph API",
6
6
  "main": "index.js",
7
7
  "bin": {
8
8
  "outlook-assistant": "./index.js"
@@ -12,6 +12,7 @@ const mockData = require('./mock-data');
12
12
  * @param {string} path - API endpoint path
13
13
  * @param {object} data - Data to send for POST/PUT requests
14
14
  * @param {object} queryParams - Query parameters
15
+ * @param {object} extraHeaders - Additional headers (e.g. Prefer for immutable IDs)
15
16
  * @returns {Promise<object>} - The API response
16
17
  */
17
18
  async function callGraphAPI(
@@ -19,7 +20,8 @@ async function callGraphAPI(
19
20
  method,
20
21
  path,
21
22
  data = null,
22
- queryParams = {}
23
+ queryParams = {},
24
+ extraHeaders = {}
23
25
  ) {
24
26
  // For test tokens, we'll simulate the API call
25
27
  if (config.USE_TEST_MODE && accessToken.startsWith('test_access_token_')) {
@@ -75,12 +77,22 @@ async function callGraphAPI(
75
77
  }
76
78
 
77
79
  return new Promise((resolve, reject) => {
80
+ const headers = {
81
+ Authorization: `Bearer ${accessToken}`,
82
+ 'Content-Type': 'application/json',
83
+ };
84
+
85
+ // Add immutable IDs header when enabled globally
86
+ if (config.USE_IMMUTABLE_IDS) {
87
+ headers.Prefer = 'IdType="ImmutableId"';
88
+ }
89
+
90
+ // Merge any extra headers (caller overrides take precedence)
91
+ Object.assign(headers, extraHeaders);
92
+
78
93
  const options = {
79
94
  method: method,
80
- headers: {
81
- Authorization: `Bearer ${accessToken}`,
82
- 'Content-Type': 'application/json',
83
- },
95
+ headers,
84
96
  };
85
97
 
86
98
  const req = https.request(finalUrl, options, (res) => {
@@ -202,6 +214,58 @@ async function callGraphAPIPaginated(
202
214
  }
203
215
  }
204
216
 
217
+ /**
218
+ * Sends multiple Graph API requests in a single batch call ($batch).
219
+ * Supports up to 20 requests per batch (Graph API limit).
220
+ * @param {string} accessToken - The access token for authentication
221
+ * @param {Array<{id: string, method: string, url: string, body?: object, headers?: object}>} requests - Batch requests
222
+ * @returns {Promise<Array<{id: string, status: number, body: object}>>} - Array of responses
223
+ */
224
+ async function callGraphAPIBatch(accessToken, requests) {
225
+ if (!Array.isArray(requests) || requests.length === 0) {
226
+ throw new Error('Batch requests must be a non-empty array');
227
+ }
228
+
229
+ if (requests.length > 20) {
230
+ throw new Error('Batch requests cannot exceed 20 (Graph API limit)');
231
+ }
232
+
233
+ // Test mode
234
+ if (config.USE_TEST_MODE && accessToken.startsWith('test_access_token_')) {
235
+ return requests.map((req) => ({
236
+ id: req.id,
237
+ status: 200,
238
+ body: mockData.simulateGraphAPIResponse(
239
+ req.method,
240
+ req.url,
241
+ req.body || null,
242
+ {}
243
+ ),
244
+ }));
245
+ }
246
+
247
+ const batchPayload = {
248
+ requests: requests.map((req) => ({
249
+ id: req.id,
250
+ method: req.method,
251
+ url: req.url.startsWith('/') ? req.url : `/${req.url}`,
252
+ ...(req.body && { body: req.body }),
253
+ ...(req.headers && { headers: req.headers }),
254
+ })),
255
+ };
256
+
257
+ const response = await callGraphAPI(
258
+ accessToken,
259
+ 'POST',
260
+ '$batch',
261
+ batchPayload
262
+ );
263
+
264
+ return (response.responses || []).sort(
265
+ (a, b) => parseInt(a.id) - parseInt(b.id)
266
+ );
267
+ }
268
+
205
269
  /**
206
270
  * Calls Graph API to get raw MIME content (for email export)
207
271
  * @param {string} accessToken - The access token for authentication
@@ -264,5 +328,6 @@ async function callGraphAPIRaw(accessToken, emailId) {
264
328
  module.exports = {
265
329
  callGraphAPI,
266
330
  callGraphAPIPaginated,
331
+ callGraphAPIBatch,
267
332
  callGraphAPIRaw,
268
333
  };