@littlebearapps/outlook-assistant 3.4.1 → 3.5.1

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/config.js CHANGED
@@ -54,6 +54,10 @@ module.exports = {
54
54
  ],
55
55
  tokenStorePath: path.join(homeDir, '.outlook-assistant-tokens.json'),
56
56
  authServerUrl: 'http://localhost:3333',
57
+ deviceCodeEndpoint:
58
+ 'https://login.microsoftonline.com/common/oauth2/v2.0/devicecode',
59
+ tokenEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
60
+ defaultAuthMethod: process.env.OUTLOOK_AUTH_METHOD || 'device-code',
57
61
  },
58
62
 
59
63
  // Microsoft Graph API
@@ -90,6 +94,9 @@ module.exports = {
90
94
  // Search defaults (reduced for token efficiency)
91
95
  DEFAULT_SEARCH_RESULTS: DEFAULT_LIMITS.searchEmails,
92
96
 
97
+ // Immutable IDs (opt-in: IDs persist through folder moves)
98
+ USE_IMMUTABLE_IDS: process.env.OUTLOOK_IMMUTABLE_IDS === 'true',
99
+
93
100
  // Timezone
94
101
  DEFAULT_TIMEZONE: 'Australia/Melbourne', // Updated for Nathan's timezone
95
102
  };
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: {
@@ -0,0 +1,90 @@
1
+ # Installing Outlook Assistant
2
+
3
+ ## Quick Install (npx — no global install needed)
4
+
5
+ Add to your MCP client configuration:
6
+
7
+ ```json
8
+ {
9
+ "mcpServers": {
10
+ "outlook": {
11
+ "command": "npx",
12
+ "args": ["-y", "@littlebearapps/outlook-assistant"],
13
+ "env": {
14
+ "OUTLOOK_CLIENT_ID": "<user-must-provide>",
15
+ "OUTLOOK_CLIENT_SECRET": "<user-must-provide>"
16
+ }
17
+ }
18
+ }
19
+ }
20
+ ```
21
+
22
+ ## Prerequisites
23
+
24
+ 1. **Node.js 18+** must be installed
25
+ 2. **Azure app registration** is required for authentication (free tier works)
26
+
27
+ ## Getting the Client ID and Secret
28
+
29
+ Users must create an Azure app registration to get credentials:
30
+
31
+ 1. Go to https://portal.azure.com/ and sign in
32
+ 2. Search for "App registrations" → click "New registration"
33
+ 3. Name: "Outlook Assistant" (or any name)
34
+ 4. Supported account types: "Accounts in any organizational directory and personal Microsoft accounts"
35
+ 5. Redirect URI: platform "Web", URI `http://localhost:3333/auth/callback`
36
+ 6. Click "Register"
37
+ 7. Copy the **Application (client) ID** → this is `OUTLOOK_CLIENT_ID`
38
+
39
+ ### Create a client secret:
40
+ 1. Go to "Certificates & secrets" → "New client secret"
41
+ 2. Add a description, select expiration, click "Add"
42
+ 3. **Copy the Value immediately** (not the Secret ID) → this is `OUTLOOK_CLIENT_SECRET`
43
+
44
+ ### Add API permissions:
45
+ 1. Go to "API permissions" → "Add a permission" → "Microsoft Graph" → "Delegated permissions"
46
+ 2. Add: `offline_access`, `User.Read`, `Mail.Read`, `Mail.ReadWrite`, `Mail.Send`, `Calendars.Read`, `Calendars.ReadWrite`, `Contacts.Read`, `Contacts.ReadWrite`, `People.Read`, `MailboxSettings.ReadWrite`
47
+ 3. Click "Add permissions"
48
+
49
+ ## First-Time Authentication
50
+
51
+ After configuring the MCP server:
52
+
53
+ 1. Start the auth server: `npx @littlebearapps/outlook-assistant-auth` (or run `npm run auth-server` from source)
54
+ 2. Use the `auth` tool with `action=authenticate` to get an OAuth URL
55
+ 3. Open the URL in a browser, sign in with your Microsoft account
56
+ 4. Grant permissions — tokens are saved to `~/.outlook-assistant-tokens.json` and refresh automatically
57
+
58
+ **Note**: The auth server needs `OUTLOOK_CLIENT_ID` and `OUTLOOK_CLIENT_SECRET` as environment variables. If running it separately from the MCP server, export them in your shell or create a `.env` file.
59
+
60
+ ## Configuration Files by Client
61
+
62
+ ### Claude Desktop
63
+ File: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
64
+
65
+ ### Claude Code
66
+ ```bash
67
+ claude mcp add outlook -- npx @littlebearapps/outlook-assistant
68
+ ```
69
+
70
+ ### Cursor
71
+ File: `.cursor/mcp.json` in your project root
72
+
73
+ ### Windsurf
74
+ File: `~/.codeium/windsurf/mcp_config.json`
75
+
76
+ ## Verify Installation
77
+
78
+ After authentication, test with:
79
+ - `auth` tool with `action=status` — should show "authenticated"
80
+ - `search-emails` with no parameters — should list recent inbox emails
81
+
82
+ ## Troubleshooting
83
+
84
+ | Problem | Solution |
85
+ |---------|----------|
86
+ | "Invalid client secret" (AADSTS7000215) | Use the secret **Value**, not the Secret ID |
87
+ | Auth URL doesn't work | Start the auth server first |
88
+ | "EADDRINUSE :3333" | Run `npx kill-port 3333` then restart auth server |
89
+ | Empty API responses | Run `auth` tool with `action=status` to check token |
90
+ | Search returns no results (personal account) | Use `from`, `subject`, `to` filters instead of `query` |
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.1",
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"
@@ -70,7 +70,8 @@
70
70
  ".env.example",
71
71
  "README.md",
72
72
  "LICENSE",
73
- "llms.txt"
73
+ "llms.txt",
74
+ "llms-install.md"
74
75
  ],
75
76
  "dependencies": {
76
77
  "@modelcontextprotocol/sdk": "^1.27.1",