@littlebearapps/outlook-assistant 3.3.0 → 3.4.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/README.md CHANGED
@@ -2,14 +2,20 @@
2
2
  <img src="docs/assets/outlook-assistant-logo-full.svg" height="200" alt="Outlook Assistant" />
3
3
  </p>
4
4
 
5
+ <h1 align="center">Outlook Assistant</h1>
6
+
5
7
  <p align="center">
6
- <strong>Let your AI assistant read, search, send, and manage your Outlook email, calendar, and contacts — all from the conversation.</strong>
8
+ <strong>MCP server for Outlook email, calendar, and contacts — let your AI assistant manage your inbox directly from the conversation.</strong>
7
9
  </p>
8
10
 
9
11
  <p align="center">
10
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>
11
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>
12
16
  <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
+ <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>
13
19
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
14
20
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen" alt="Node.js" /></a>
15
21
  </p>
@@ -18,19 +24,29 @@ Outlook Assistant connects AI assistants to your Microsoft Outlook account throu
18
24
 
19
25
  **Works with personal Outlook.com and work/school Microsoft 365 accounts.**
20
26
 
27
+ <div align="center">
28
+ <br />
29
+ <a href="docs/demo/outlook-assistant-demo.mp4">
30
+ <img src="docs/demo/outlook-assistant-demo.gif" alt="Outlook Assistant Demo — searching emails, reading, and drafting a reply" width="720" style="border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.12);" />
31
+ </a>
32
+ <br />
33
+ <sub>Search inbox → read &amp; summarise → draft a reply — all from the conversation</sub>
34
+ <br /><br />
35
+ </div>
36
+
21
37
  ### What you can do
22
38
 
23
- - **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
24
- - **Send emails with safety controls** — dry-run preview, session rate limiting, and recipient allowlist to prevent mistakes
25
- - **Manage your calendar** — view upcoming events, schedule meetings with attendees, decline or cancel invitations
26
- - **Export emails** — save to Markdown, EML, MBOX, JSON, or HTML for archiving, analysis, or migration; export search results or entire threads in one call
27
- - **Investigate email headers** — check DKIM, SPF, and DMARC authentication; trace delivery chains; analyse spam scores — useful for phishing investigation and compliance
28
- - **Organise your inbox** — create folders, set up inbox rules, colour-code with categories, manage Focused Inbox — all work together for complete inbox automation
29
- - **Track inbox changes** — delta sync detects new, modified, and deleted emails since your last check, with tokens for incremental polling
30
- - **Manage contacts** — search your contact book and organisational directory, create and update contact records
31
- - **Configure settings** — set out-of-office auto-replies, working hours, and time zone
32
- - **Access shared mailboxes** — read team inboxes and service accounts (Microsoft 365)
33
- - **Find meeting rooms** — search by building, floor, capacity, AV equipment, and wheelchair accessibility (Microsoft 365)
39
+ - 📨 **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
41
+ - 📅 **Manage your calendar** — view upcoming events, schedule meetings with attendees, decline or cancel invitations
42
+ - 📦 **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
+ - 🔍 **Investigate email headers** — check DKIM, SPF, and DMARC authentication; trace delivery chains; analyse spam scores — useful for phishing investigation and compliance
44
+ - 🗂️ **Organise your inbox** — create folders, set up inbox rules, colour-code with categories, manage Focused Inbox — all work together for complete inbox automation
45
+ - 🔄 **Track inbox changes** — delta sync detects new, modified, and deleted emails since your last check, with tokens for incremental polling
46
+ - 👥 **Manage contacts** — search your contact book and organisational directory, create and update contact records
47
+ - ⚙️ **Configure settings** — set out-of-office auto-replies, working hours, and time zone
48
+ - 📬 **Access shared mailboxes** — read team inboxes and service accounts (Microsoft 365)
49
+ - 🏢 **Find meeting rooms** — search by building, floor, capacity, AV equipment, and wheelchair accessibility (Microsoft 365)
34
50
 
35
51
  ### Why Outlook Assistant?
36
52
 
@@ -69,6 +85,7 @@ Outlook Assistant connects AI assistants to your Microsoft Outlook account throu
69
85
  | `markdown` | `.md` | Pasting into documents, feeding into AI workflows |
70
86
  | `json` | `.json` | Data analysis, pipeline processing, compliance reporting |
71
87
  | `html` | `.html` | Visual archival with formatting intact |
88
+ | `csv` | `.csv` | Spreadsheet import, bulk metadata analysis, compliance audits |
72
89
 
73
90
  Export individual emails, search results, or entire conversation threads — use `target=messages` with a search query to batch-export without manually collecting IDs.
74
91
 
@@ -140,7 +157,10 @@ You need a Microsoft Azure app registration to authenticate. See the **[Azure Se
140
157
 
141
158
  ### 3. Configure Your MCP Client
142
159
 
143
- Add to your MCP client config. For Claude Desktop (`claude_desktop_config.json`):
160
+ Add to your MCP client config:
161
+
162
+ <details>
163
+ <summary><strong>Claude Desktop</strong> (<code>claude_desktop_config.json</code>)</summary>
144
164
 
145
165
  ```json
146
166
  {
@@ -156,6 +176,55 @@ Add to your MCP client config. For Claude Desktop (`claude_desktop_config.json`)
156
176
  }
157
177
  }
158
178
  ```
179
+ </details>
180
+
181
+ <details>
182
+ <summary><strong>Claude Code</strong> (CLI)</summary>
183
+
184
+ ```bash
185
+ claude mcp add outlook -- npx @littlebearapps/outlook-assistant
186
+ ```
187
+
188
+ Then set environment variables in your `.env` or shell.
189
+ </details>
190
+
191
+ <details>
192
+ <summary><strong>Cursor</strong> (<code>.cursor/mcp.json</code>)</summary>
193
+
194
+ ```json
195
+ {
196
+ "mcpServers": {
197
+ "outlook": {
198
+ "command": "npx",
199
+ "args": ["@littlebearapps/outlook-assistant"],
200
+ "env": {
201
+ "OUTLOOK_CLIENT_ID": "your-application-client-id",
202
+ "OUTLOOK_CLIENT_SECRET": "your-client-secret-VALUE"
203
+ }
204
+ }
205
+ }
206
+ }
207
+ ```
208
+ </details>
209
+
210
+ <details>
211
+ <summary><strong>Windsurf</strong> (<code>~/.codeium/windsurf/mcp_config.json</code>)</summary>
212
+
213
+ ```json
214
+ {
215
+ "mcpServers": {
216
+ "outlook": {
217
+ "command": "npx",
218
+ "args": ["@littlebearapps/outlook-assistant"],
219
+ "env": {
220
+ "OUTLOOK_CLIENT_ID": "your-application-client-id",
221
+ "OUTLOOK_CLIENT_SECRET": "your-client-secret-VALUE"
222
+ }
223
+ }
224
+ }
225
+ }
226
+ ```
227
+ </details>
159
228
 
160
229
  ### 4. Authenticate
161
230
 
@@ -248,24 +317,9 @@ USE_TEST_MODE=false
248
317
 
249
318
  ### MCP Client Configuration
250
319
 
251
- Add to your MCP client config (example for Claude Desktop):
252
-
253
- ```json
254
- {
255
- "mcpServers": {
256
- "outlook": {
257
- "command": "npx",
258
- "args": ["@littlebearapps/outlook-assistant"],
259
- "env": {
260
- "OUTLOOK_CLIENT_ID": "your-application-client-id",
261
- "OUTLOOK_CLIENT_SECRET": "your-client-secret-VALUE"
262
- }
263
- }
264
- }
265
- }
266
- ```
320
+ See [Quick Start — Configure Your MCP Client](#3-configure-your-mcp-client) above for Claude Desktop, Claude Code, Cursor, and Windsurf configs.
267
321
 
268
- Or if installed from source:
322
+ If installed from source, use `node` instead of `npx`:
269
323
 
270
324
  ```json
271
325
  {
@@ -417,6 +471,10 @@ For security concerns, please see our [Security Policy](SECURITY.md). Do not ope
417
471
 
418
472
  See [CHANGELOG.md](CHANGELOG.md) for version history.
419
473
 
474
+ ## Listed On
475
+
476
+ <a href="https://glama.ai/mcp/servers/littlebearapps/outlook-assistant"><img width="190" height="100" src="https://glama.ai/mcp/servers/littlebearapps/outlook-assistant/badge" alt="Outlook Assistant on Glama" /></a>
477
+
420
478
  ## About
421
479
 
422
480
  Built and maintained by [Little Bear Apps](https://littlebearapps.com). Outlook Assistant is open source under the [MIT License](LICENSE).
@@ -14,6 +14,7 @@ const { ensureAuthenticated } = require('../auth');
14
14
  const { getEmailFields } = require('../utils/field-presets');
15
15
  const {
16
16
  formatEmailContent,
17
+ formatEmailsAsCSV,
17
18
  VERBOSITY,
18
19
  } = require('../utils/response-formatter');
19
20
 
@@ -329,7 +330,7 @@ async function handleExportConversation(args) {
329
330
  };
330
331
  }
331
332
 
332
- const validFormats = ['eml', 'mbox', 'markdown', 'json', 'html'];
333
+ const validFormats = ['eml', 'mbox', 'markdown', 'json', 'html', 'csv'];
333
334
  if (!validFormats.includes(format)) {
334
335
  return {
335
336
  content: [
@@ -602,6 +603,16 @@ async function handleExportConversation(args) {
602
603
  exportedFiles.push(htmlPath);
603
604
  break;
604
605
  }
606
+
607
+ case 'csv': {
608
+ // Export as CSV
609
+ const csvPath = path.join(resolvedDir, `${filenameBase}.csv`);
610
+ const csvContent = formatEmailsAsCSV(messages);
611
+ fs.writeFileSync(csvPath, csvContent, 'utf8');
612
+ exportStats.bytes = Buffer.byteLength(csvContent, 'utf8');
613
+ exportedFiles.push(csvPath);
614
+ break;
615
+ }
605
616
  }
606
617
 
607
618
  // Format result
package/email/export.js CHANGED
@@ -11,6 +11,7 @@ const { callGraphAPI, callGraphAPIRaw } = require('../utils/graph-api');
11
11
  const { ensureAuthenticated } = require('../auth');
12
12
  const {
13
13
  formatEmailContent,
14
+ formatEmailsAsCSV,
14
15
  VERBOSITY,
15
16
  } = require('../utils/response-formatter');
16
17
  const { getEmailFields } = require('../utils/field-presets');
@@ -21,6 +22,7 @@ const EXPORT_FORMATS = {
21
22
  EML: 'eml', // Alias for MIME
22
23
  MARKDOWN: 'markdown',
23
24
  JSON: 'json',
25
+ CSV: 'csv',
24
26
  };
25
27
 
26
28
  /**
@@ -111,12 +113,15 @@ async function handleExportEmail(args) {
111
113
  } else if (format === EXPORT_FORMATS.JSON) {
112
114
  // JSON export - full email object
113
115
  content = JSON.stringify(email, null, 2);
116
+ } else if (format === EXPORT_FORMATS.CSV) {
117
+ // CSV export - email metadata
118
+ content = formatEmailsAsCSV(email);
114
119
  } else {
115
120
  return {
116
121
  content: [
117
122
  {
118
123
  type: 'text',
119
- text: `Unknown format: ${format}. Supported: mime, eml, markdown, json`,
124
+ text: `Unknown format: ${format}. Supported: ${Object.values(EXPORT_FORMATS).join(', ')}`,
120
125
  },
121
126
  ],
122
127
  };
@@ -254,6 +259,68 @@ async function handleBatchExportEmails(args) {
254
259
  console.error(`Batch export limited to ${maxBatch} emails`);
255
260
  }
256
261
 
262
+ // CSV batch export: aggregate all emails into a single CSV file
263
+ if (format === EXPORT_FORMATS.CSV) {
264
+ const selectFields = getEmailFields('export');
265
+ const emails = [];
266
+ const failed = [];
267
+
268
+ for (const emailId of idsToExport) {
269
+ try {
270
+ const email = await callGraphAPI(
271
+ accessToken,
272
+ 'GET',
273
+ `me/messages/${emailId}`,
274
+ null,
275
+ { $select: selectFields }
276
+ );
277
+ emails.push(email);
278
+ } catch (error) {
279
+ failed.push({ emailId, error: error.message });
280
+ }
281
+ }
282
+
283
+ const csvContent = formatEmailsAsCSV(emails);
284
+ const csvPath = path.join(
285
+ outputDir,
286
+ `batch_export_${new Date().toISOString().slice(0, 10)}.csv`
287
+ );
288
+ fs.writeFileSync(csvPath, csvContent, 'utf8');
289
+ const totalBytes = Buffer.byteLength(csvContent, 'utf8');
290
+
291
+ let resultText = `## Batch Export Complete\n\n`;
292
+ resultText += `| Metric | Value |\n`;
293
+ resultText += `|--------|-------|\n`;
294
+ resultText += `| Total | ${idsToExport.length} |\n`;
295
+ resultText += `| Successful | ${emails.length} |\n`;
296
+ resultText += `| Failed | ${failed.length} |\n`;
297
+ resultText += `| Output File | \`${csvPath}\` |\n`;
298
+ resultText += `| Format | CSV |\n`;
299
+ resultText += `| Total Size | ${(totalBytes / 1024).toFixed(1)} KB |\n`;
300
+
301
+ if (failed.length > 0) {
302
+ resultText += `\n### Failed Exports\n\n`;
303
+ for (const f of failed.slice(0, 10)) {
304
+ resultText += `- ID \`${f.emailId}\`: ${f.error}\n`;
305
+ }
306
+ if (failed.length > 10) {
307
+ resultText += `- ... and ${failed.length - 10} more\n`;
308
+ }
309
+ }
310
+
311
+ return {
312
+ content: [{ type: 'text', text: resultText }],
313
+ _meta: {
314
+ outputDir,
315
+ format,
316
+ total: idsToExport.length,
317
+ successful: emails.length,
318
+ failed: failed.length,
319
+ totalBytes,
320
+ },
321
+ };
322
+ }
323
+
257
324
  // Export emails with concurrency limit (4 concurrent per Graph API limits)
258
325
  const results = await exportWithConcurrency(
259
326
  accessToken,
@@ -559,6 +626,8 @@ function getExtension(format) {
559
626
  return 'eml';
560
627
  case EXPORT_FORMATS.JSON:
561
628
  return 'json';
629
+ case EXPORT_FORMATS.CSV:
630
+ return 'csv';
562
631
  case EXPORT_FORMATS.MARKDOWN:
563
632
  default:
564
633
  return 'md';
package/email/index.js CHANGED
@@ -434,9 +434,9 @@ const emailTools = [
434
434
  },
435
435
  format: {
436
436
  type: 'string',
437
- enum: ['mime', 'eml', 'markdown', 'json', 'mbox', 'html'],
437
+ enum: ['mime', 'eml', 'markdown', 'json', 'mbox', 'html', 'csv'],
438
438
  description:
439
- 'Export format (target=message: mime/eml/markdown/json, target=conversation: eml/mbox/markdown/json/html)',
439
+ 'Export format (target=message: mime/eml/markdown/json/csv, target=conversation: eml/mbox/markdown/json/html/csv)',
440
440
  },
441
441
  savePath: {
442
442
  type: 'string',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlebearapps/outlook-assistant",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "mcpName": "io.github.littlebearapps/outlook-assistant",
5
5
  "description": "Outlook Assistant — MCP server with 20 tools for email, calendar, contacts, and settings via Microsoft Graph API",
6
6
  "main": "index.js",
@@ -17,7 +17,7 @@
17
17
  "lint:fix": "eslint . --fix",
18
18
  "format": "prettier --write .",
19
19
  "format:check": "prettier --check .",
20
- "prepare": "husky"
20
+ "prepare": "husky || true"
21
21
  },
22
22
  "lint-staged": {
23
23
  "*.js": [
@@ -283,6 +283,49 @@ function formatEmailContent(
283
283
  return output;
284
284
  }
285
285
 
286
+ /**
287
+ * Formats one or more emails as a CSV string with a header row.
288
+ * Accepts a single email object or an array of email objects.
289
+ * Only exports metadata columns - no body content.
290
+ * @param {object|object[]} emails - Single email or array of emails from Graph API
291
+ * @returns {string} - CSV string with header row and one row per email
292
+ */
293
+ function formatEmailsAsCSV(emails) {
294
+ const CSV_HEADERS = [
295
+ 'id',
296
+ 'subject',
297
+ 'from',
298
+ 'to',
299
+ 'cc',
300
+ 'receivedDateTime',
301
+ 'isRead',
302
+ 'importance',
303
+ 'hasAttachments',
304
+ ];
305
+
306
+ const emailList = Array.isArray(emails) ? emails : [emails];
307
+
308
+ const rows = emailList.map((email) => {
309
+ const from = formatEmailAddress(email.from?.emailAddress);
310
+ const to = formatRecipients(email.toRecipients);
311
+ const cc = formatRecipients(email.ccRecipients);
312
+
313
+ return [
314
+ email.id || '',
315
+ email.subject || '',
316
+ from,
317
+ to,
318
+ cc,
319
+ email.receivedDateTime || '',
320
+ email.isRead != null ? String(email.isRead) : '',
321
+ email.importance || '',
322
+ email.hasAttachments != null ? String(email.hasAttachments) : '',
323
+ ].map(escapeCSV);
324
+ });
325
+
326
+ return [CSV_HEADERS.join(','), ...rows.map((r) => r.join(','))].join('\n');
327
+ }
328
+
286
329
  /**
287
330
  * Formats email headers for legal/forensic use
288
331
  * @param {Array} headers - Array of header objects
@@ -435,6 +478,26 @@ function stripHtml(html) {
435
478
  .trim();
436
479
  }
437
480
 
481
+ function escapeCSV(value) {
482
+ if (value === null || value === undefined) return '';
483
+ const str = String(value);
484
+ const needsQuoting =
485
+ str.includes(',') ||
486
+ str.includes('"') ||
487
+ str.includes('\n') ||
488
+ str.includes('\r');
489
+ const formulaChars = ['=', '+', '-', '@', '\t', '\r', '\n'];
490
+ const isFormula = formulaChars.some((ch) => str.startsWith(ch));
491
+
492
+ if (isFormula) {
493
+ return '"' + "'" + str.replace(/"/g, '""') + '"';
494
+ }
495
+ if (needsQuoting) {
496
+ return '"' + str.replace(/"/g, '""') + '"';
497
+ }
498
+ return str;
499
+ }
500
+
438
501
  module.exports = {
439
502
  VERBOSITY,
440
503
  DEFAULT_LIMITS,
@@ -445,6 +508,7 @@ module.exports = {
445
508
  formatEmailList,
446
509
  formatEmailListAsTable,
447
510
  formatEmailContent,
511
+ formatEmailsAsCSV,
448
512
  formatEmailHeaders,
449
513
  createResponseMeta,
450
514
  wrapMcpResponse,
@@ -454,4 +518,5 @@ module.exports = {
454
518
  formatRecipients,
455
519
  truncateText,
456
520
  stripHtml,
521
+ escapeCSV,
457
522
  };