@littlebearapps/outlook-assistant 3.7.2 → 3.8.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 +19 -0
- package/README.md +47 -21
- package/advanced/index.js +26 -3
- package/auth/tools.js +54 -1
- package/calendar/index.js +126 -4
- package/calendar/update.js +287 -0
- package/categories/index.js +67 -28
- package/config.js +51 -5
- package/contacts/index.js +118 -27
- package/email/attachments.js +13 -3
- package/email/conversations.js +4 -7
- package/email/delta.js +20 -6
- package/email/export.js +26 -3
- package/email/index.js +38 -4
- package/email/list.js +6 -1
- package/email/mail-tips.js +26 -2
- package/email/search.js +82 -25
- package/folder/create.js +6 -1
- package/folder/index.js +10 -1
- package/index.js +33 -0
- package/llms.txt +8 -2
- package/outlook-auth-server.js +13 -4
- package/package.json +2 -2
- package/rules/create.js +5 -1
- package/rules/index.js +19 -1
- package/rules/update.js +11 -3
- package/settings/index.js +70 -2
- package/utils/response-formatter.js +27 -0
- package/utils/schema-coerce.js +248 -0
package/.env.example
CHANGED
|
@@ -28,3 +28,22 @@ USE_TEST_MODE=false
|
|
|
28
28
|
# device-code: No auth server needed, works remotely/headless
|
|
29
29
|
# browser: Traditional OAuth redirect via localhost:3333
|
|
30
30
|
# OUTLOOK_AUTH_METHOD=device-code
|
|
31
|
+
|
|
32
|
+
# Optional: OAuth audience — controls which Microsoft Identity Platform
|
|
33
|
+
# endpoint is used. Must match the Azure app registration's "Supported
|
|
34
|
+
# account types" setting:
|
|
35
|
+
# common — personal AND work/school accounts (default; multi-tenant + personal apps)
|
|
36
|
+
# consumers — personal Microsoft accounts only
|
|
37
|
+
# organizations — work/school accounts only
|
|
38
|
+
# <tenant-guid> — single-tenant
|
|
39
|
+
# Example: OUTLOOK_AUTH_AUDIENCE=consumers (for personal-account-only apps)
|
|
40
|
+
# OUTLOOK_AUTH_AUDIENCE=common
|
|
41
|
+
|
|
42
|
+
# Optional: Default timezone for calendar events when not explicitly
|
|
43
|
+
# specified by the caller. Use any IANA timezone identifier.
|
|
44
|
+
# Default: Australia/Melbourne
|
|
45
|
+
# Examples:
|
|
46
|
+
# OUTLOOK_DEFAULT_TIMEZONE=Europe/London
|
|
47
|
+
# OUTLOOK_DEFAULT_TIMEZONE=America/New_York
|
|
48
|
+
# OUTLOOK_DEFAULT_TIMEZONE=Asia/Tokyo
|
|
49
|
+
# OUTLOOK_DEFAULT_TIMEZONE=Australia/Melbourne
|
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
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>
|
|
15
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>
|
|
16
16
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
|
|
17
|
+
<a href="https://glama.ai/mcp/servers/littlebearapps/outlook-assistant"><img src="https://glama.ai/mcp/servers/littlebearapps/outlook-assistant/badges/score.svg" alt="Glama score" /></a>
|
|
17
18
|
</p>
|
|
18
19
|
|
|
19
20
|
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.
|
|
@@ -22,8 +23,8 @@ Outlook Assistant connects AI assistants to your Microsoft Outlook account throu
|
|
|
22
23
|
|
|
23
24
|
<div align="center">
|
|
24
25
|
<br />
|
|
25
|
-
<a href="docs/demo/outlook-assistant-demo.mp4">
|
|
26
|
-
<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);" />
|
|
26
|
+
<a href="https://github.com/littlebearapps/outlook-assistant/blob/main/docs/demo/outlook-assistant-demo.mp4">
|
|
27
|
+
<img src="https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/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);" />
|
|
27
28
|
</a>
|
|
28
29
|
<br />
|
|
29
30
|
<sub>Search inbox → read & summarise → draft a reply — all from the conversation</sub>
|
|
@@ -36,8 +37,8 @@ Outlook Assistant connects AI assistants to your Microsoft Outlook account throu
|
|
|
36
37
|
- 🛡️ **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
|
|
37
38
|
- ✏️ **Draft emails for review** — create, update, and send drafts; reply and forward as drafts; preview before saving with dry-run mode
|
|
38
39
|
- 📅 **Manage your calendar** — view upcoming events, schedule meetings with attendees, decline or cancel invitations
|
|
39
|
-
- 📦 **Export emails** — save to Markdown, EML,
|
|
40
|
-
- 🔍 **Investigate email headers** —
|
|
40
|
+
- 📦 **Export emails** — save individual messages to Markdown, EML, JSON, or CSV; export full conversation threads to MBOX or HTML; bulk-export search results in one call
|
|
41
|
+
- 🔍 **Investigate email headers** — full raw header access (DKIM, SPF, DMARC, delivery chain, X-Mailer, X-Originating-IP) for phishing investigation and compliance review
|
|
41
42
|
- 🗂️ **Organise your inbox** — create folders, set up inbox rules, colour-code with categories, manage Focused Inbox — all work together for complete inbox automation
|
|
42
43
|
- 🔄 **Track inbox changes** — delta sync detects new, modified, and deleted emails since your last check, with tokens for incremental polling
|
|
43
44
|
- 👥 **Manage contacts** — search your contact book and organisational directory, create and update contact records
|
|
@@ -62,7 +63,7 @@ Outlook Assistant connects AI assistants to your Microsoft Outlook account throu
|
|
|
62
63
|
| Module | Tools | What You Can Do |
|
|
63
64
|
|--------|------:|-----------------|
|
|
64
65
|
| **Email** | 8 | `search-emails` (list/search/delta/conversations), `read-email` (content + forensic headers), `send-email` (with dry-run + mail tips), `draft` (create/update/send/delete/reply/forward), `update-email` (read status, flags), `attachments`, `export`, `get-mail-tips` |
|
|
65
|
-
| **Calendar** | 3 | `list-events`, `create-event`, `manage-event` (decline/cancel/delete) |
|
|
66
|
+
| **Calendar** | 3 | `list-events`, `create-event`, `manage-event` (update/decline/cancel/delete) |
|
|
66
67
|
| **Contacts** | 2 | `manage-contact` (list/search/get/create/update/delete), `search-people` |
|
|
67
68
|
| **Categories** | 3 | `manage-category` (CRUD), `apply-category`, `manage-focused-inbox` |
|
|
68
69
|
| **Settings** | 1 | `mailbox-settings` (get/set auto-replies/set working hours) |
|
|
@@ -75,16 +76,18 @@ Outlook Assistant connects AI assistants to your Microsoft Outlook account throu
|
|
|
75
76
|
|
|
76
77
|
### Export Formats
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|--------|-----------|----------------|
|
|
80
|
-
| `mime` / `eml` | `.eml` | Legal holds, forensic preservation, importing into other mail clients |
|
|
81
|
-
| `mbox` | `.mbox` | Archiving entire conversation threads, migrating between systems |
|
|
82
|
-
| `markdown` | `.md` | Pasting into documents, feeding into AI workflows |
|
|
83
|
-
| `json` | `.json` | Data analysis, pipeline processing, compliance reporting |
|
|
84
|
-
| `html` | `.html` | Visual archival with formatting intact |
|
|
85
|
-
| `csv` | `.csv` | Spreadsheet import, bulk metadata analysis, compliance audits |
|
|
79
|
+
Format support varies by `target`:
|
|
86
80
|
|
|
87
|
-
|
|
81
|
+
| Format | Extension | `target=message` (single) | `target=messages` (batch) | `target=conversation` (thread) |
|
|
82
|
+
|--------|-----------|--------|--------|--------|
|
|
83
|
+
| `mime` / `eml` | `.eml` | ✅ | – | ✅ |
|
|
84
|
+
| `mbox` | `.mbox` | – | – | ✅ |
|
|
85
|
+
| `markdown` | `.md` | ✅ | ✅ | ✅ |
|
|
86
|
+
| `json` | `.json` | ✅ | ✅ | ✅ |
|
|
87
|
+
| `html` | `.html` | – | – | ✅ |
|
|
88
|
+
| `csv` | `.csv` | ✅ | ✅ | ✅ |
|
|
89
|
+
|
|
90
|
+
Export individual emails, search results, or entire conversation threads — use `target=messages` with a search query (or the `query` shortcut) to batch-export without manually collecting IDs.
|
|
88
91
|
|
|
89
92
|
## Account Compatibility
|
|
90
93
|
|
|
@@ -100,7 +103,7 @@ Outlook Assistant works with both personal and work/school Microsoft accounts, b
|
|
|
100
103
|
| Free-text `query` search | Limited — use `subject`, `from`, `to` filters instead | Full KQL support |
|
|
101
104
|
| Categories | Full support | Full support |
|
|
102
105
|
| Mailbox settings | Full support | Full support |
|
|
103
|
-
| Focused Inbox |
|
|
106
|
+
| Focused Inbox | API works (overrides stored) but mail routing not affected | Full support |
|
|
104
107
|
| Shared mailboxes | Not available | Requires `Mail.Read.Shared` |
|
|
105
108
|
| Meeting room search | Not available | Requires `Place.Read.All` + admin consent |
|
|
106
109
|
|
|
@@ -109,7 +112,7 @@ Outlook Assistant works with both personal and work/school Microsoft accounts, b
|
|
|
109
112
|
### What Makes This Different
|
|
110
113
|
|
|
111
114
|
- **Progressive search** — on accounts where Microsoft's `$search` API is limited, Outlook Assistant automatically falls back through up to 4 search strategies to find your emails. Most Graph API wrappers fail silently; this one adapts.
|
|
112
|
-
- **Email forensics** —
|
|
115
|
+
- **Email forensics** — raw header access for DKIM, SPF, DMARC, delivery chain, X-Mailer, X-Originating-IP, and spam scores. Returns the full data so you can investigate phishing, audit compliance, or trace delivery issues. (Auto-verdict is on the v3.8.0 roadmap; today the data is surfaced and analysed in-conversation.)
|
|
113
116
|
- **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.
|
|
114
117
|
- **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.
|
|
115
118
|
- **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.
|
|
@@ -127,6 +130,17 @@ Outlook Assistant is designed with safety-first principles for AI-driven email a
|
|
|
127
130
|
- **Session rate limiting** — configurable via `OUTLOOK_MAX_EMAILS_PER_SESSION` (default: unlimited)
|
|
128
131
|
- **Recipient allowlist** — restrict sending to approved addresses/domains via `OUTLOOK_ALLOWED_RECIPIENTS`
|
|
129
132
|
|
|
133
|
+
> **Recommended setup**: enable both safety belts in your `.mcp.json` from day one. They're off by default; `auth action=about` reports their state and prints a setup hint when unset. See [`.mcp.json.example`](.mcp.json.example) for a copy-paste template.
|
|
134
|
+
>
|
|
135
|
+
> ```json
|
|
136
|
+
> "env": {
|
|
137
|
+
> "OUTLOOK_CLIENT_ID": "…",
|
|
138
|
+
> "OUTLOOK_CLIENT_SECRET": "…",
|
|
139
|
+
> "OUTLOOK_MAX_EMAILS_PER_SESSION": "10",
|
|
140
|
+
> "OUTLOOK_ALLOWED_RECIPIENTS": "your-domain.com,trusted@example.com"
|
|
141
|
+
> }
|
|
142
|
+
> ```
|
|
143
|
+
|
|
130
144
|
**Draft protections** — The `draft` tool shares `send-email` safety controls: dry-run preview, recipient allowlist, mail-tips validation, and rate limiting. The `send` action shares the `send-email` rate limit counter, preventing circumvention via the draft-then-send pathway.
|
|
131
145
|
|
|
132
146
|
**Token-optimised architecture** — Tools are consolidated using the STRAP (Single Tool, Resource, Action Pattern) approach. 22 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.
|
|
@@ -322,6 +336,15 @@ USE_TEST_MODE=false
|
|
|
322
336
|
|
|
323
337
|
> **Note:** The server also accepts `MS_CLIENT_ID` and `MS_CLIENT_SECRET` for backwards compatibility.
|
|
324
338
|
|
|
339
|
+
**Optional overrides** (v3.8.0+) — see [`.env.example`](.env.example) for the full list with commented worked examples:
|
|
340
|
+
|
|
341
|
+
| Variable | Purpose | Default |
|
|
342
|
+
|----------|---------|---------|
|
|
343
|
+
| `OUTLOOK_AUTH_AUDIENCE` | OAuth audience: `common`, `consumers` (personal-only Azure apps), `organizations`, or single-tenant GUID. Fixes `AADSTS9002331` for personal-only app registrations. | `common` |
|
|
344
|
+
| `OUTLOOK_DEFAULT_TIMEZONE` | IANA timezone applied to calendar events when callers don't pass one (e.g. `Europe/London`, `America/New_York`). | `Australia/Melbourne` |
|
|
345
|
+
| `OUTLOOK_MAX_EMAILS_PER_SESSION` | Cap on `send-email` + `draft send` per MCP server lifetime. | unlimited |
|
|
346
|
+
| `OUTLOOK_ALLOWED_RECIPIENTS` | Comma-separated allowlist of domains/addresses for sends, drafts, and rule forwards. | unrestricted |
|
|
347
|
+
|
|
325
348
|
### MCP Client Configuration
|
|
326
349
|
|
|
327
350
|
See [Quick Start — Configure Your MCP Client](#3-configure-your-mcp-client) above for Claude Desktop, Claude Code, Cursor, and Windsurf configs.
|
|
@@ -356,6 +379,8 @@ No auth server needed. Works everywhere, including remote/headless environments.
|
|
|
356
379
|
5. Tokens are saved to `~/.outlook-assistant-tokens.json` and **refresh automatically**
|
|
357
380
|
|
|
358
381
|
> **Prerequisite**: Enable "Allow public client flows" in Azure Portal > your app > Authentication > Advanced settings.
|
|
382
|
+
>
|
|
383
|
+
> **Server restarts** (v3.7.2+): Device code state is persisted to `~/.outlook-assistant-pending-auth.json`, so `device-code-complete` works even if the MCP server restarts between steps 1 and 4 (e.g., Untether/Telegram bridge, Claude Desktop session changes).
|
|
359
384
|
|
|
360
385
|
### Browser Redirect Flow (Alternative)
|
|
361
386
|
|
|
@@ -431,6 +456,10 @@ If using browser flow: start the auth server first with `npm run auth-server`. I
|
|
|
431
456
|
|
|
432
457
|
Enable "Allow public client flows" in Azure Portal > App registrations > Authentication > Advanced settings.
|
|
433
458
|
|
|
459
|
+
### Token refresh fails after ~60 minutes (device code auth)
|
|
460
|
+
|
|
461
|
+
Fixed in v3.7.2. Earlier versions sent `client_secret` in token refresh requests for device-code auth, which Microsoft rejects for public client flows. Update to v3.7.2+ or re-authenticate.
|
|
462
|
+
|
|
434
463
|
### Empty API responses
|
|
435
464
|
|
|
436
465
|
Check authentication status with the `auth` tool (action=status). Tokens may have expired — re-authenticate if needed.
|
|
@@ -467,7 +496,8 @@ USE_TEST_MODE=true npm start
|
|
|
467
496
|
|-------|-------------|
|
|
468
497
|
| [Getting Started](docs/how-to/getting-started/connect-outlook-to-claude.md) | Install, configure, and authenticate — start here |
|
|
469
498
|
| [Azure Setup Guide](docs/guides/azure-setup.md) | Azure account creation, app registration, permissions, and secrets |
|
|
470
|
-
| [How-To Guides](docs/how-to/index.md) |
|
|
499
|
+
| [How-To Guides](docs/how-to/index.md) | 29 practical guides for email, calendar, contacts, and settings |
|
|
500
|
+
| [Roadmap](ROADMAP.md) | Active milestones (v3.7.5, v3.8.0, v3.9.0) and recent releases |
|
|
471
501
|
| [Troubleshooting & FAQ](docs/how-to/getting-started/verify-your-connection.md#common-connection-problems) | Common problems, re-authentication, and frequently asked questions |
|
|
472
502
|
| [Tools Reference](docs/quickrefs/tools-reference.md) | All 22 tools with parameters |
|
|
473
503
|
| [AI Agent Guide](docs/how-to/ai-agents/using-outlook-assistant-in-agents.md) | Tool selection and workflow patterns for AI agents |
|
|
@@ -494,10 +524,6 @@ For security concerns, please see our [Security Policy](SECURITY.md). Do not ope
|
|
|
494
524
|
|
|
495
525
|
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
496
526
|
|
|
497
|
-
## Listed On
|
|
498
|
-
|
|
499
|
-
<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>
|
|
500
|
-
|
|
501
527
|
## About
|
|
502
528
|
|
|
503
529
|
Built and maintained by [Little Bear Apps](https://littlebearapps.com). Outlook Assistant is open source under the [MIT License](LICENSE).
|
package/advanced/index.js
CHANGED
|
@@ -46,7 +46,10 @@ function formatEmail(email, verbosity = 'standard') {
|
|
|
46
46
|
* Requires Mail.Read.Shared permission
|
|
47
47
|
*/
|
|
48
48
|
async function handleAccessSharedMailbox(args) {
|
|
49
|
-
|
|
49
|
+
// F-46: accept `email` as alias for `sharedMailbox`. The original
|
|
50
|
+
// param name is awkward; most callers reach for `email` first.
|
|
51
|
+
const { folder, count, outputVerbosity } = args;
|
|
52
|
+
const sharedMailbox = args.sharedMailbox || args.email;
|
|
50
53
|
|
|
51
54
|
if (!sharedMailbox) {
|
|
52
55
|
return {
|
|
@@ -458,11 +461,24 @@ async function handleFindMeetingRooms(args) {
|
|
|
458
461
|
);
|
|
459
462
|
rooms = roomsResponse.value || [];
|
|
460
463
|
} catch (findRoomsError) {
|
|
464
|
+
// F-47: distinguish "feature not available on personal account"
|
|
465
|
+
// from generic permission errors. Personal Outlook.com accounts
|
|
466
|
+
// surface a 404 here; organizational accounts surface
|
|
467
|
+
// permission errors. Both look similar in Graph but mean very
|
|
468
|
+
// different things to the caller.
|
|
469
|
+
const errMsg = findRoomsError.message || '';
|
|
470
|
+
const isLikelyPersonal =
|
|
471
|
+
errMsg.includes('404') ||
|
|
472
|
+
errMsg.includes('Not Found') ||
|
|
473
|
+
errMsg.includes('NotFound');
|
|
474
|
+
const explanation = isLikelyPersonal
|
|
475
|
+
? 'Meeting room search is M365-only. Personal Outlook.com accounts cannot use this feature — there are no rooms to find. Connect a Microsoft 365 work/school account to enable.'
|
|
476
|
+
: 'This feature requires:\n- Places.Read.All permission\n- Meeting rooms configured in your organization';
|
|
461
477
|
return {
|
|
462
478
|
content: [
|
|
463
479
|
{
|
|
464
480
|
type: 'text',
|
|
465
|
-
text: `Unable to find meeting rooms.\n\n**Note**:
|
|
481
|
+
text: `Unable to find meeting rooms.\n\n**Note**: ${explanation}\n\nError: ${errMsg}`,
|
|
466
482
|
},
|
|
467
483
|
],
|
|
468
484
|
};
|
|
@@ -603,6 +619,11 @@ const advancedTools = [
|
|
|
603
619
|
type: 'string',
|
|
604
620
|
description: 'Email address of the shared mailbox (required)',
|
|
605
621
|
},
|
|
622
|
+
email: {
|
|
623
|
+
type: 'string',
|
|
624
|
+
description:
|
|
625
|
+
'Alias for `sharedMailbox` (more intuitive name for the same value).',
|
|
626
|
+
},
|
|
606
627
|
folder: {
|
|
607
628
|
type: 'string',
|
|
608
629
|
description: 'Folder to read from (default: inbox)',
|
|
@@ -617,7 +638,8 @@ const advancedTools = [
|
|
|
617
638
|
description: 'Output detail level (default: standard)',
|
|
618
639
|
},
|
|
619
640
|
},
|
|
620
|
-
|
|
641
|
+
additionalProperties: false,
|
|
642
|
+
required: [],
|
|
621
643
|
},
|
|
622
644
|
handler: handleAccessSharedMailbox,
|
|
623
645
|
},
|
|
@@ -654,6 +676,7 @@ const advancedTools = [
|
|
|
654
676
|
description: 'Output detail level (default: standard)',
|
|
655
677
|
},
|
|
656
678
|
},
|
|
679
|
+
additionalProperties: false,
|
|
657
680
|
required: [],
|
|
658
681
|
},
|
|
659
682
|
handler: handleFindMeetingRooms,
|
package/auth/tools.js
CHANGED
|
@@ -28,17 +28,40 @@ async function handleAbout() {
|
|
|
28
28
|
(s) => s !== 'offline_access'
|
|
29
29
|
);
|
|
30
30
|
const testMode = config.USE_TEST_MODE ? 'Enabled' : 'Disabled';
|
|
31
|
+
const rateLimitConfigured = Boolean(
|
|
32
|
+
process.env.OUTLOOK_MAX_EMAILS_PER_SESSION
|
|
33
|
+
);
|
|
34
|
+
const allowlistConfigured = Boolean(process.env.OUTLOOK_ALLOWED_RECIPIENTS);
|
|
31
35
|
const rateLimit =
|
|
32
36
|
process.env.OUTLOOK_MAX_EMAILS_PER_SESSION || 'Unlimited (no limit set)';
|
|
33
37
|
const allowlist =
|
|
34
38
|
process.env.OUTLOOK_ALLOWED_RECIPIENTS || 'None (all recipients allowed)';
|
|
35
39
|
|
|
40
|
+
// F-2: surface the authenticated user's email so callers and AI
|
|
41
|
+
// agents can confirm which mailbox is connected. Uses a single
|
|
42
|
+
// GET /me round-trip when a valid token is available; degrades
|
|
43
|
+
// gracefully when not authenticated.
|
|
44
|
+
let identity = 'Not authenticated (run `auth action=authenticate`)';
|
|
45
|
+
try {
|
|
46
|
+
const { ensureAuthenticated } = require('./index');
|
|
47
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
48
|
+
const token = await ensureAuthenticated();
|
|
49
|
+
const me = await callGraphAPI(token, 'GET', 'me', null, {
|
|
50
|
+
$select: 'userPrincipalName,mail,displayName',
|
|
51
|
+
});
|
|
52
|
+
const upn = me.mail || me.userPrincipalName;
|
|
53
|
+
identity = me.displayName ? `${me.displayName} <${upn}>` : upn;
|
|
54
|
+
} catch (_e) {
|
|
55
|
+
// Leave default identity message in place
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
const lines = [
|
|
37
59
|
`# Outlook Assistant Server v${config.SERVER_VERSION}\n`,
|
|
38
60
|
`Provides access to Microsoft Outlook email, calendar, and contacts through Microsoft Graph API.\n`,
|
|
39
61
|
`## Diagnostics\n`,
|
|
40
62
|
`| Setting | Value |`,
|
|
41
63
|
`|---------|-------|`,
|
|
64
|
+
`| Mailbox | ${identity} |`,
|
|
42
65
|
`| Tools | ${_toolCount} across 9 modules |`,
|
|
43
66
|
`| Modules | auth, email, calendar, folder, rules, contacts, categories, settings, advanced |`,
|
|
44
67
|
`| Timezone | ${config.DEFAULT_TIMEZONE} |`,
|
|
@@ -50,6 +73,27 @@ async function handleAbout() {
|
|
|
50
73
|
`**Scopes**: ${scopes.join(', ')}`,
|
|
51
74
|
];
|
|
52
75
|
|
|
76
|
+
// F-1 / F-48: warn when no safety belts are wired up. AI-assisted
|
|
77
|
+
// sending is significantly safer with a session rate limit and a
|
|
78
|
+
// recipient allowlist; both are off by default.
|
|
79
|
+
if (!rateLimitConfigured || !allowlistConfigured) {
|
|
80
|
+
lines.push('');
|
|
81
|
+
lines.push('## ⚠ Safety Belts Not Configured\n');
|
|
82
|
+
lines.push(
|
|
83
|
+
'No rate limit or recipient allowlist is set. For safer AI-assisted sending, add to your `.mcp.json` env block:'
|
|
84
|
+
);
|
|
85
|
+
lines.push('```');
|
|
86
|
+
if (!rateLimitConfigured) {
|
|
87
|
+
lines.push('OUTLOOK_MAX_EMAILS_PER_SESSION=10');
|
|
88
|
+
}
|
|
89
|
+
if (!allowlistConfigured) {
|
|
90
|
+
lines.push(
|
|
91
|
+
'OUTLOOK_ALLOWED_RECIPIENTS=your-domain.com,trusted@example.com'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
lines.push('```');
|
|
95
|
+
}
|
|
96
|
+
|
|
53
97
|
return {
|
|
54
98
|
content: [
|
|
55
99
|
{
|
|
@@ -369,6 +413,7 @@ const authTools = [
|
|
|
369
413
|
'Force re-authentication even if already authenticated (action=authenticate only)',
|
|
370
414
|
},
|
|
371
415
|
},
|
|
416
|
+
additionalProperties: false,
|
|
372
417
|
required: [],
|
|
373
418
|
},
|
|
374
419
|
handler: async (args) => {
|
|
@@ -381,8 +426,16 @@ const authTools = [
|
|
|
381
426
|
case 'about':
|
|
382
427
|
return handleAbout();
|
|
383
428
|
case 'status':
|
|
384
|
-
default:
|
|
385
429
|
return handleCheckAuthStatus();
|
|
430
|
+
default:
|
|
431
|
+
return {
|
|
432
|
+
content: [
|
|
433
|
+
{
|
|
434
|
+
type: 'text',
|
|
435
|
+
text: `Unknown action '${action}'. Valid actions: status, authenticate, device-code-complete, about.`,
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
};
|
|
386
439
|
}
|
|
387
440
|
},
|
|
388
441
|
},
|
package/calendar/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const handleDeclineEvent = require('./decline');
|
|
|
6
6
|
const handleCreateEvent = require('./create');
|
|
7
7
|
const handleCancelEvent = require('./cancel');
|
|
8
8
|
const handleDeleteEvent = require('./delete');
|
|
9
|
+
const handleUpdateEvent = require('./update');
|
|
9
10
|
|
|
10
11
|
// Calendar tool definitions (consolidated: 5 → 3)
|
|
11
12
|
const calendarTools = [
|
|
@@ -25,6 +26,7 @@ const calendarTools = [
|
|
|
25
26
|
description: 'Number of events to retrieve (default: 10, max: 50)',
|
|
26
27
|
},
|
|
27
28
|
},
|
|
29
|
+
additionalProperties: false,
|
|
28
30
|
required: [],
|
|
29
31
|
},
|
|
30
32
|
handler: handleListEvents,
|
|
@@ -65,6 +67,7 @@ const calendarTools = [
|
|
|
65
67
|
description: 'Optional body content for the event',
|
|
66
68
|
},
|
|
67
69
|
},
|
|
70
|
+
additionalProperties: false,
|
|
68
71
|
required: ['subject', 'start', 'end'],
|
|
69
72
|
},
|
|
70
73
|
handler: handleCreateEvent,
|
|
@@ -72,7 +75,7 @@ const calendarTools = [
|
|
|
72
75
|
{
|
|
73
76
|
name: 'manage-event',
|
|
74
77
|
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.',
|
|
78
|
+
'Manage an existing calendar event. action=update edits fields (subject, start, end, attendees, body, location, isOnlineMeeting, sensitivity, showAs, importance, categories, reminderMinutesBeforeStart) without rebuilding the event; pass dryRun=true to preview the PATCH payload. action=decline declines an invitation. action=cancel cancels an event you organised. action=delete permanently removes an event.',
|
|
76
79
|
annotations: {
|
|
77
80
|
title: 'Manage Calendar Event',
|
|
78
81
|
readOnlyHint: false,
|
|
@@ -84,22 +87,140 @@ const calendarTools = [
|
|
|
84
87
|
properties: {
|
|
85
88
|
action: {
|
|
86
89
|
type: 'string',
|
|
87
|
-
enum: ['decline', 'cancel', 'delete'],
|
|
90
|
+
enum: ['update', 'decline', 'cancel', 'delete'],
|
|
88
91
|
description: 'Action to perform (required)',
|
|
89
92
|
},
|
|
90
93
|
eventId: {
|
|
91
94
|
type: 'string',
|
|
92
95
|
description: 'The ID of the event',
|
|
93
96
|
},
|
|
97
|
+
id: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description:
|
|
100
|
+
'Alias for `eventId` (canonical per the v3.7.3 alias pass).',
|
|
101
|
+
},
|
|
94
102
|
comment: {
|
|
95
103
|
type: 'string',
|
|
96
104
|
description: 'Optional comment for declining or cancelling the event',
|
|
97
105
|
},
|
|
106
|
+
subject: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'New subject (action=update only)',
|
|
109
|
+
},
|
|
110
|
+
start: {
|
|
111
|
+
oneOf: [
|
|
112
|
+
{ type: 'string' },
|
|
113
|
+
{
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
dateTime: { type: 'string' },
|
|
117
|
+
timeZone: { type: 'string' },
|
|
118
|
+
},
|
|
119
|
+
required: ['dateTime'],
|
|
120
|
+
additionalProperties: false,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
description:
|
|
124
|
+
'New start time as ISO 8601 string or {dateTime, timeZone} object (action=update only)',
|
|
125
|
+
},
|
|
126
|
+
end: {
|
|
127
|
+
oneOf: [
|
|
128
|
+
{ type: 'string' },
|
|
129
|
+
{
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
dateTime: { type: 'string' },
|
|
133
|
+
timeZone: { type: 'string' },
|
|
134
|
+
},
|
|
135
|
+
required: ['dateTime'],
|
|
136
|
+
additionalProperties: false,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
description:
|
|
140
|
+
'New end time as ISO 8601 string or {dateTime, timeZone} object (action=update only)',
|
|
141
|
+
},
|
|
142
|
+
attendees: {
|
|
143
|
+
type: 'array',
|
|
144
|
+
items: { type: 'string' },
|
|
145
|
+
description:
|
|
146
|
+
'Full replacement attendee list — pass complete desired list, or [] to clear (action=update only)',
|
|
147
|
+
},
|
|
148
|
+
body: {
|
|
149
|
+
type: 'string',
|
|
150
|
+
description: 'New body content (action=update only)',
|
|
151
|
+
},
|
|
152
|
+
location: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
description: 'New location display name (action=update only)',
|
|
155
|
+
},
|
|
156
|
+
isOnlineMeeting: {
|
|
157
|
+
type: 'boolean',
|
|
158
|
+
description: 'Toggle online meeting flag (action=update only)',
|
|
159
|
+
},
|
|
160
|
+
sensitivity: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
enum: ['normal', 'personal', 'private', 'confidential'],
|
|
163
|
+
description: 'Event sensitivity classification (action=update only)',
|
|
164
|
+
},
|
|
165
|
+
showAs: {
|
|
166
|
+
type: 'string',
|
|
167
|
+
enum: [
|
|
168
|
+
'free',
|
|
169
|
+
'tentative',
|
|
170
|
+
'busy',
|
|
171
|
+
'oof',
|
|
172
|
+
'workingElsewhere',
|
|
173
|
+
'unknown',
|
|
174
|
+
],
|
|
175
|
+
description: 'Free/busy status shown to others (action=update only)',
|
|
176
|
+
},
|
|
177
|
+
importance: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
enum: ['low', 'normal', 'high'],
|
|
180
|
+
description: 'Event importance flag (action=update only)',
|
|
181
|
+
},
|
|
182
|
+
categories: {
|
|
183
|
+
type: 'array',
|
|
184
|
+
items: { type: 'string' },
|
|
185
|
+
description:
|
|
186
|
+
'Full replacement category list — pass [] to clear (action=update only)',
|
|
187
|
+
},
|
|
188
|
+
reminderMinutesBeforeStart: {
|
|
189
|
+
type: 'number',
|
|
190
|
+
description:
|
|
191
|
+
'Minutes before start to fire the reminder (action=update only)',
|
|
192
|
+
},
|
|
193
|
+
dryRun: {
|
|
194
|
+
type: 'boolean',
|
|
195
|
+
description:
|
|
196
|
+
'Preview the PATCH without applying it (action=update only). Returns the body that would be sent to Graph.',
|
|
197
|
+
},
|
|
98
198
|
},
|
|
99
|
-
|
|
199
|
+
additionalProperties: false,
|
|
200
|
+
required: ['action'],
|
|
100
201
|
},
|
|
101
202
|
handler: async (args) => {
|
|
203
|
+
// F-37: accept `id` as alias for `eventId` so callers don't have
|
|
204
|
+
// to remember which tool uses which name. Both work; eventId
|
|
205
|
+
// remains the canonical Graph param.
|
|
206
|
+
const normalised = { ...args };
|
|
207
|
+
if (!normalised.eventId && normalised.id) {
|
|
208
|
+
normalised.eventId = normalised.id;
|
|
209
|
+
}
|
|
210
|
+
if (!normalised.eventId) {
|
|
211
|
+
return {
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: 'text',
|
|
215
|
+
text: 'Required parameter `eventId` (or alias `id`) is missing.',
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
args = normalised;
|
|
102
221
|
switch (args.action) {
|
|
222
|
+
case 'update':
|
|
223
|
+
return handleUpdateEvent(args);
|
|
103
224
|
case 'decline':
|
|
104
225
|
return handleDeclineEvent(args);
|
|
105
226
|
case 'cancel':
|
|
@@ -111,7 +232,7 @@ const calendarTools = [
|
|
|
111
232
|
content: [
|
|
112
233
|
{
|
|
113
234
|
type: 'text',
|
|
114
|
-
text: "Invalid action. Use 'decline', 'cancel', or 'delete'.",
|
|
235
|
+
text: "Invalid action. Use 'update', 'decline', 'cancel', or 'delete'.",
|
|
115
236
|
},
|
|
116
237
|
],
|
|
117
238
|
};
|
|
@@ -127,4 +248,5 @@ module.exports = {
|
|
|
127
248
|
handleCreateEvent,
|
|
128
249
|
handleCancelEvent,
|
|
129
250
|
handleDeleteEvent,
|
|
251
|
+
handleUpdateEvent,
|
|
130
252
|
};
|