@littlebearapps/outlook-assistant 3.12.1 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.env.example +27 -3
  2. package/README.md +108 -33
  3. package/advanced/index.js +44 -174
  4. package/auth/auth-errors.js +23 -1
  5. package/auth/client-config.js +142 -0
  6. package/auth/index.js +4 -2
  7. package/auth/oauth-server.js +12 -2
  8. package/auth/token-manager.js +7 -3
  9. package/auth/token-storage.js +46 -33
  10. package/auth/tools.js +223 -93
  11. package/calendar/attendees.js +36 -0
  12. package/calendar/cancel.js +9 -25
  13. package/calendar/create.js +42 -48
  14. package/calendar/decline.js +10 -25
  15. package/calendar/delete.js +10 -25
  16. package/calendar/index.js +20 -37
  17. package/calendar/list.js +4 -16
  18. package/calendar/preview.js +335 -0
  19. package/calendar/update.js +42 -86
  20. package/categories/index.js +59 -264
  21. package/config.js +36 -2
  22. package/contacts/index.js +72 -128
  23. package/email/attachments.js +42 -124
  24. package/email/conversations.js +44 -78
  25. package/email/delta.js +10 -34
  26. package/email/draft.js +140 -96
  27. package/email/export.js +141 -110
  28. package/email/folder-utils.js +3 -2
  29. package/email/headers.js +11 -49
  30. package/email/index.js +85 -109
  31. package/email/list.js +4 -17
  32. package/email/mail-tips.js +86 -57
  33. package/email/mark-as-read.js +13 -49
  34. package/email/mime.js +14 -49
  35. package/email/read.js +16 -50
  36. package/email/search.js +46 -86
  37. package/email/send.js +82 -48
  38. package/folder/create.js +6 -25
  39. package/folder/delete.js +117 -38
  40. package/folder/index.js +17 -16
  41. package/folder/list.js +5 -17
  42. package/folder/move.js +13 -42
  43. package/folder/resolve.js +11 -6
  44. package/folder/stats.js +6 -20
  45. package/index.js +23 -45
  46. package/llms-install.md +31 -7
  47. package/llms.txt +19 -10
  48. package/outlook-auth-server.js +10 -3
  49. package/package.json +6 -2
  50. package/request-handler.js +217 -116
  51. package/rules/create.js +27 -70
  52. package/rules/index.js +30 -92
  53. package/rules/list.js +5 -17
  54. package/rules/rule-builder.js +57 -20
  55. package/rules/update.js +26 -60
  56. package/server.js +37 -0
  57. package/settings/index.js +142 -143
  58. package/tools.js +30 -0
  59. package/utils/field-presets.js +4 -2
  60. package/utils/graph-api.js +65 -22
  61. package/utils/logger.js +251 -0
  62. package/utils/mock-data.js +91 -2
  63. package/utils/read-only.js +59 -0
  64. package/utils/response-formatter.js +54 -15
  65. package/utils/risk-classes.js +324 -0
  66. package/utils/safe-write.js +372 -6
  67. package/utils/safety.js +109 -25
  68. package/utils/server-instructions.js +62 -0
  69. package/utils/tool-error.js +33 -0
package/.env.example CHANGED
@@ -14,13 +14,24 @@ OUTLOOK_CLIENT_SECRET=your-client-secret-here
14
14
  # Optional: Enable test mode with mock data (true/false)
15
15
  USE_TEST_MODE=false
16
16
 
17
- # Optional: Safety controls for send-email
18
- # Maximum emails that can be sent per server session (0 = unlimited)
17
+ # Optional: Safety controls for sending
18
+ # Default per-session cap, counted separately per tool, for send-email
19
+ # (including draft send), draft create/update/reply/reply-all/forward,
20
+ # manage-rules and create-event (0 = unlimited). Override one tool with
21
+ # OUTLOOK_MAX_<TOOL>_PER_SESSION, e.g. OUTLOOK_MAX_SEND_EMAIL_PER_SESSION.
19
22
  # OUTLOOK_MAX_EMAILS_PER_SESSION=10
20
23
 
21
- # Restrict sending to specific domains/addresses (comma-separated)
24
+ # Restrict recipients of sends, drafts, rule forwards and event attendees to
25
+ # these domains/addresses (comma-separated)
22
26
  # OUTLOOK_ALLOWED_RECIPIENTS=mycompany.com,partner@example.com
23
27
 
28
+ # Optional: Read-only mode. Every tool call that would change something (send,
29
+ # draft, move, flag, delete, rules, settings, local file writes) is refused
30
+ # before it runs, including dryRun previews; reads and sign-in still work.
31
+ # Accepts true/1/yes/on (any case). A value that isn't recognised also turns it
32
+ # on, with a warning. Restart the server after changing it.
33
+ # OUTLOOK_READ_ONLY=true
34
+
24
35
  # Optional: Enable immutable IDs (IDs persist through folder moves)
25
36
  # OUTLOOK_IMMUTABLE_IDS=true
26
37
 
@@ -39,6 +50,19 @@ USE_TEST_MODE=false
39
50
  # retried on a 429 asking for a short wait (10 s or less, 20 s in total).
40
51
  # OUTLOOK_REQUEST_TIMEOUT_MS=60000
41
52
 
53
+ # Optional: Debug logging to stderr (true/1/yes/on). Off by default, when each
54
+ # tool call logs one line (tool, action, outcome, duration) and never its
55
+ # arguments. On, stderr also shows search terms, subjects, folder names and
56
+ # Graph error bodies, with email addresses and long IDs redacted. Tokens,
57
+ # device codes and secrets are never logged either way. Turn it off again
58
+ # once you've finished troubleshooting.
59
+ # OUTLOOK_DEBUG=true
60
+
61
+ # Extra folder that `export` and `attachments` downloads may write into, on top
62
+ # of the system temp directory, ~/Downloads and ~/Documents (anything else is
63
+ # refused). Absolute path; a leading ~ is expanded.
64
+ # OUTLOOK_EXPORT_DIR=/path/to/mail-archive
65
+
42
66
  # Optional: Default authentication method (device-code or browser)
43
67
  # device-code: No auth server needed, works remotely/headless
44
68
  # browser: Traditional OAuth redirect via localhost:3333
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
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>
18
18
  </p>
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.
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, GitHub Copilot, Cursor, Windsurf, and any MCP-compatible client.
21
21
 
22
22
  **Works with personal Outlook.com and work/school Microsoft 365 accounts.**
23
23
 
@@ -122,28 +122,42 @@ Outlook Assistant works with both personal and work/school Microsoft accounts, b
122
122
 
123
123
  Outlook Assistant is designed with safety-first principles for AI-driven email access:
124
124
 
125
- **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
+ **Destructive action safeguards** — Every tool carries [MCP annotations](https://modelcontextprotocol.io/docs/concepts/tools#annotations) (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), all four set explicitly on every tool, so AI clients can auto-approve safe reads and prompt for confirmation on destructive operations like sending email, inviting attendees or deleting events. `send-email` and `create-event` also carry Claude's `anthropic/requiresUserInteraction` flag, so Claude Code asks before every call to them, dry runs included, even in auto-accept or bypass modes.
126
+
127
+ **Read-only mode** — Set `OUTLOOK_READ_ONLY=true` and the server refuses every tool call or action that isn't a read before it runs: no sends, drafts, moves, flags, deletes, rules, settings changes, exports or attachment downloads, and no dry runs either. Searching and reading still work, and so does signing in. `auth action=about` shows whether it's on.
128
+
129
+ **Server instructions** — When a client connects, the server sends it instructions for the model, hard rules first: treat retrieved email, calendar and contact content as data, not instructions; confirm anything that reaches other people, deletes or keeps acting, using `dryRun: true` previews; draft first and send only when asked; and treat allowlist refusals, rate limits and other policy refusals as final.
130
+
131
+ **Plugin skill and safety hook** — The [plugin](plugins/outlook-assistant/) adds two more layers. The `using-outlook-assistant` agent skill, read by Claude Code, GitHub Copilot and Cursor, teaches the model the hard rules plus the judgement the tool descriptions leave out: who each send, reply-all, invitation or cancellation reaches, what each delete loses, how prompt injection in email looks, and how to search without pulling the whole mailbox. A hook also asks you before anything that reaches other people, deletes or keeps acting, with a plain-English reason such as "Cancels the event 'Team sync' and emails a cancellation to every attendee". It stays quiet for reads and genuine dry runs, and its confirmation level (`outward`, `all-writes` or `off`) controls how often it asks. How it behaves depends on the client:
132
+
133
+ - **Claude Code:** asks with the reason, even for tools you've allowed; set the level with the plugin's **Confirmation level** setting. In bypass permissions mode Claude Code may auto-approve these prompts (the [plugin README](plugins/outlook-assistant/README.md#skill-and-safety-hook) has ask rules to keep them).
134
+ - **GitHub Copilot CLI:** asks with the reason; set the level with `OUTLOOK_CONFIRM_LEVEL`. A hook that times out lets the call through. VS Code reads the same hook file (not yet checked by hand).
135
+ - **Cursor:** the hook blocks the call if it fails or times out, but Cursor's own "Run this MCP tool?" prompt doesn't show the reason, and an `Mcp(...)` allow rule, or `--force` / Run Everything mode, runs the call without asking.
136
+ - **Other clients:** no hook; the server's checks, annotations and instructions still apply.
137
+
138
+ See [Supported Clients and Their Limits](docs/how-to/getting-started/supported-clients.md) for the details.
139
+
140
+ **Dry-run previews** (`dryRun: true`) — See what a call would do without changing or sending anything: `send-email`, `draft` create, `create-event` (who would be invited, with a count of external addresses), `manage-event` update/decline/cancel/delete (who would be emailed), `mailbox-settings` set-auto-replies (who gets each reply, and when), `manage-rules` create/update, and `folders` delete and `manage-contact` delete (what would be lost). Any other call with `dryRun: true` is refused before it runs, so a preview can never send, delete or change anything for real.
126
141
 
127
142
  **Send-email protections** — The `send-email` tool includes:
128
- - **Pre-send mail tips** (`checkRecipients: true`) — check recipients for out-of-office, mailbox full, delivery restrictions before sending
143
+ - **Pre-send mail tips** (`checkRecipients: true`) — check recipients for out-of-office, mailbox full and delivery restrictions. If the tips show any of those, an external recipient or a group with external members, the send is refused with the warnings listed; repeat it with `acknowledgeWarnings: true` once you've seen them. A failed check also stops the send. Mail tips are Microsoft 365 only: personal accounts return none
129
144
  - **Dry-run mode** (`dryRun: true`) — preview composed emails without sending
130
145
  - **Session rate limiting** — configurable via `OUTLOOK_MAX_EMAILS_PER_SESSION` (default: unlimited)
131
- - **Recipient allowlist** — restrict sending to approved addresses/domains via `OUTLOOK_ALLOWED_RECIPIENTS`
146
+ - **Recipient allowlist** — restrict recipients to approved addresses/domains via `OUTLOOK_ALLOWED_RECIPIENTS`. It covers `send-email`, `draft` (create, update, forward, reply, reply-all and send), rule forward/redirect (a rule that would forward or redirect to a blocked address is refused whole), `create-event` attendees and `manage-event` update attendees; it doesn't cover `manage-event` cancel/decline messages, the cancellation an organiser's delete sends, or `mailbox-settings` automatic replies. Anything that isn't a single plain email address is refused while it's set
132
147
 
133
148
  > **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
149
  >
135
150
  > ```json
136
151
  > "env": {
137
152
  > "OUTLOOK_CLIENT_ID": "…",
138
- > "OUTLOOK_CLIENT_SECRET": "…",
139
153
  > "OUTLOOK_MAX_EMAILS_PER_SESSION": "10",
140
154
  > "OUTLOOK_ALLOWED_RECIPIENTS": "your-domain.com,trusted@example.com"
141
155
  > }
142
156
  > ```
143
157
 
144
- **Input and file hardening** — IDs containing `.` or `..` path segments are refused before any request is made, continuation links (`deltaToken`) must point at `graph.microsoft.com`, and attachment downloads and exports write sanitised filenames inside the output directory without overwriting existing files or following symlinks.
158
+ **Input and file hardening** — IDs containing `.` or `..` path segments are refused before any request is made, continuation links (`deltaToken`) must point at `graph.microsoft.com`, and attachment downloads and exports write only inside the system temp directory, `~/Downloads`, `~/Documents` or `OUTLOOK_EXPORT_DIR` (never to dot-prefixed names), using sanitised filenames without overwriting existing files or following symlinks. Paths must be absolute (or start with `~/`). An explicit `export` file path is replaced only when you pass `overwrite: true`, and never if it's a symlink. Files are created readable only by you (`0600`; new folders `0700`).
145
159
 
146
- **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. `update`, `send` and `delete` refuse any ID that is not an unsent draft, so a received or sent message is never edited, deleted or re-sent.
160
+ **Draft protections** — The `draft` tool shares `send-email` safety controls: dry-run preview (`create`), mail-tips validation, rate limiting and the recipient allowlist. The allowlist is checked on create, update and forward; a reply or reply-all draft whose recipients it doesn't allow is deleted again; and `send` re-checks the draft's current to/cc/bcc, so a draft edited in Outlook can't slip past it. The `send` action shares the `send-email` rate limit counter, preventing circumvention via the draft-then-send pathway. `update`, `send` and `delete` refuse any ID that is not an unsent draft, so a received or sent message is never edited, deleted or re-sent.
147
161
 
148
162
  **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.
149
163
 
@@ -166,7 +180,7 @@ npx @littlebearapps/outlook-assistant
166
180
  To check which version you have, or to see the available options:
167
181
 
168
182
  ```bash
169
- outlook-assistant --version # prints e.g. 3.12.1
183
+ outlook-assistant --version # prints e.g. 3.14.0
170
184
  outlook-assistant --help # usage, options and key environment variables
171
185
  ```
172
186
 
@@ -180,14 +194,36 @@ You need a Microsoft Azure app registration to authenticate. See the **[Azure Se
180
194
 
181
195
  1. Create a new app registration at [portal.azure.com](https://portal.azure.com/)
182
196
  2. Add Microsoft Graph delegated permissions (Mail, Calendar, Contacts)
183
- 3. Create a client secret and copy the **Value** (not the Secret ID)
197
+ 3. _(Browser flow only)_ Create a client secret and copy the **Value** (not the Secret ID). The default device-code sign-in doesn't need one
184
198
  4. Under Authentication > **Add a platform** > **Mobile and desktop applications** — check `nativeclient` URI
185
199
  5. Enable **"Allow public client flows"** in Authentication > Advanced settings
186
200
  6. _(Optional)_ Set redirect URI to `http://localhost:3333/auth/callback` — only needed for browser auth flow
187
201
 
188
202
  ### 3. Configure Your MCP Client
189
203
 
190
- Add to your MCP client config:
204
+ **Client support.** Every MCP client gets the server's own checks. The plugin adds the `using-outlook-assistant` skill and a safety hook in Claude Code, GitHub Copilot and Cursor, with different limits in each. See [Supported Clients and Their Limits](docs/how-to/getting-started/supported-clients.md).
205
+
206
+ **Plugin install.** The plugin ([`plugins/outlook-assistant`](plugins/outlook-assistant/)) bundles the server pinned to an exact version, the skill and the safety hook. It follows both the Claude Code plugin format and the [Agent Plugins](https://agent-plugins.org/) format used by GitHub Copilot, plus a Cursor manifest (`.cursor-plugin/`).
207
+
208
+ - **Claude Code.** The plugin asks for your settings when you enable it (client ID, sign-in audience, send limit per session, allowed recipients, read-only mode and confirmation level):
209
+
210
+ ```bash
211
+ claude plugin marketplace add littlebearapps/outlook-assistant
212
+ claude plugin install outlook-assistant@littlebearapps
213
+ ```
214
+
215
+ - **GitHub Copilot CLI.** Copilot has no plugin settings, so give your client ID when you first sign in, and set the hook's confirmation level with the `OUTLOOK_CONFIRM_LEVEL` environment variable:
216
+
217
+ ```bash
218
+ copilot plugin marketplace add littlebearapps/outlook-assistant
219
+ copilot plugin install outlook-assistant@littlebearapps
220
+ ```
221
+
222
+ VS Code's Copilot agent reads the same plugin and hook file; that hasn't been checked by hand yet.
223
+
224
+ - **Cursor** (v3.14.0 or later). Cursor loads the folder as a Cursor plugin (`.cursor-plugin/plugin.json`). In Cursor CLI, load it from a clone of this repository with `cursor-agent --plugin-dir outlook-assistant/plugins/outlook-assistant`. Give your client ID when you first sign in. The v3.13.0 plugin can't sign in from Cursor (`AADSTS900023`); use the manual config below instead.
225
+
226
+ **Manual config.** Use this for Claude Desktop, Codex CLI, Gemini CLI, Windsurf and other MCP clients, or in place of a plugin (you then get no hook). Add to your MCP client config. Only `OUTLOOK_CLIENT_ID` is needed for the default device-code sign-in; add `OUTLOOK_CLIENT_SECRET` only if you use the [browser flow](#browser-redirect-flow-alternative). You can also leave the client ID out and give it to your assistant when you first connect (`auth action=authenticate clientId=…`), which saves it to `~/.outlook-assistant-config.json`. An `OUTLOOK_CLIENT_ID` in the environment always takes precedence.
191
227
 
192
228
  <details>
193
229
  <summary><strong>Claude Desktop</strong> (<code>claude_desktop_config.json</code>)</summary>
@@ -199,8 +235,7 @@ Add to your MCP client config:
199
235
  "command": "npx",
200
236
  "args": ["@littlebearapps/outlook-assistant"],
201
237
  "env": {
202
- "OUTLOOK_CLIENT_ID": "your-application-client-id",
203
- "OUTLOOK_CLIENT_SECRET": "your-client-secret-VALUE"
238
+ "OUTLOOK_CLIENT_ID": "your-application-client-id"
204
239
  }
205
240
  }
206
241
  }
@@ -214,17 +249,46 @@ Add to your MCP client config:
214
249
  ```bash
215
250
  claude mcp add outlook \
216
251
  -e OUTLOOK_CLIENT_ID=your-application-client-id \
217
- -e OUTLOOK_CLIENT_SECRET=your-client-secret-VALUE \
218
252
  -- npx -y @littlebearapps/outlook-assistant
219
253
  ```
220
254
 
221
255
  The MCP server reads its settings from the environment your client passes it; it doesn't load a `.env` file.
222
256
  </details>
223
257
 
258
+ <details>
259
+ <summary><strong>VS Code / GitHub Copilot</strong> (<code>.vscode/mcp.json</code>)</summary>
260
+
261
+ VS Code prompts for the client ID the first time the server starts and stores it securely:
262
+
263
+ ```json
264
+ {
265
+ "inputs": [
266
+ {
267
+ "type": "promptString",
268
+ "id": "outlook-client-id",
269
+ "description": "Azure application (client) ID"
270
+ }
271
+ ],
272
+ "servers": {
273
+ "outlook": {
274
+ "type": "stdio",
275
+ "command": "npx",
276
+ "args": ["-y", "@littlebearapps/outlook-assistant"],
277
+ "env": {
278
+ "OUTLOOK_CLIENT_ID": "${input:outlook-client-id}"
279
+ }
280
+ }
281
+ }
282
+ }
283
+ ```
284
+
285
+ Use it from Copilot Chat in **Agent** mode. To use it in every workspace, add the same entry to your user `mcp.json` (Command Palette → **MCP: Open User Configuration**).
286
+ </details>
287
+
224
288
  <details>
225
289
  <summary><strong>Cursor</strong> (<code>.cursor/mcp.json</code>)</summary>
226
290
 
227
- [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=Outlook%20Assistant&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBsaXR0bGViZWFyYXBwcy9vdXRsb29rLWFzc2lzdGFudCJdLCJlbnYiOnsiT1VUTE9PS19DTElFTlRfSUQiOiIiLCJPVVRMT09LX0NMSUVOVF9TRUNSRVQiOiIifX0=)
291
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=Outlook%20Assistant&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBsaXR0bGViZWFyYXBwcy9vdXRsb29rLWFzc2lzdGFudCJdLCJlbnYiOnsiT1VUTE9PS19DTElFTlRfSUQiOiIifX0=)
228
292
 
229
293
  Or add manually to `.cursor/mcp.json`:
230
294
 
@@ -235,8 +299,7 @@ Or add manually to `.cursor/mcp.json`:
235
299
  "command": "npx",
236
300
  "args": ["@littlebearapps/outlook-assistant"],
237
301
  "env": {
238
- "OUTLOOK_CLIENT_ID": "your-application-client-id",
239
- "OUTLOOK_CLIENT_SECRET": "your-client-secret-VALUE"
302
+ "OUTLOOK_CLIENT_ID": "your-application-client-id"
240
303
  }
241
304
  }
242
305
  }
@@ -254,8 +317,7 @@ Or add manually to `.cursor/mcp.json`:
254
317
  "command": "npx",
255
318
  "args": ["@littlebearapps/outlook-assistant"],
256
319
  "env": {
257
- "OUTLOOK_CLIENT_ID": "your-application-client-id",
258
- "OUTLOOK_CLIENT_SECRET": "your-client-secret-VALUE"
320
+ "OUTLOOK_CLIENT_ID": "your-application-client-id"
259
321
  }
260
322
  }
261
323
  }
@@ -339,6 +401,8 @@ a server that would ignore it.
339
401
 
340
402
  ### Create a Client Secret
341
403
 
404
+ Only needed for the [browser redirect flow](#browser-redirect-flow-alternative). Skip this if you sign in with the default device code.
405
+
342
406
  1. Go to **Certificates & secrets** > **New client secret**
343
407
  2. Enter a description and select expiration
344
408
  3. Click **Add**
@@ -370,15 +434,20 @@ USE_TEST_MODE=false
370
434
  |----------|---------|---------|
371
435
  | `OUTLOOK_AUTH_AUDIENCE` | OAuth audience: `common`, `consumers` (personal-only Azure apps), `organizations`, or single-tenant GUID. Fixes `AADSTS9002331` for personal-only app registrations. | `common` |
372
436
  | `OUTLOOK_DEFAULT_TIMEZONE` | IANA timezone applied to calendar events when callers don't pass one (e.g. `Europe/London`, `America/New_York`). | `Australia/Melbourne` |
373
- | `OUTLOOK_MAX_EMAILS_PER_SESSION` | Cap on `send-email` + `draft send` per MCP server lifetime. | unlimited |
374
- | `OUTLOOK_ALLOWED_RECIPIENTS` | Comma-separated allowlist of domains/addresses for sends, drafts, and rule forwards. | unrestricted |
437
+ | `OUTLOOK_MAX_EMAILS_PER_SESSION` | Default per-session cap for each rate-limited tool, counted separately until the server restarts: `send-email` (including `draft action=send`), `draft` create/update/reply/reply-all/forward, `manage-rules` and `create-event`. Override one tool with `OUTLOOK_MAX_<TOOL>_PER_SESSION`, e.g. `OUTLOOK_MAX_SEND_EMAIL_PER_SESSION`. | unlimited |
438
+ | `OUTLOOK_ALLOWED_RECIPIENTS` | Comma-separated allowlist of domains/addresses for sends, drafts, rule forwards and calendar invitations (`create-event` and `manage-event` update attendees). Not applied to cancellation/decline messages or automatic replies. | unrestricted |
375
439
  | `OUTLOOK_SHARED_MAILBOX` | Opt-in shared-mailbox support (work/school only). `read` requests `Mail.Read.Shared`; `true` (or `readwrite`/`1`) also requests `Mail.ReadWrite.Shared`. Unset leaves sign-in unchanged. After enabling, restart and run `auth action=authenticate force=true`. | unset (off) |
376
440
  | `OUTLOOK_SEARCH_SCAN_LIMIT` | How many recent messages the client-side search fallback scans. Personal accounts match `to` locally within this window, so the default caps how far back a `to` search reaches. Max 5000. | `500` |
377
441
  | `OUTLOOK_REQUEST_TIMEOUT_MS` | Inactivity timeout for each Graph request attempt, in milliseconds: an attempt that receives no data for this long is abandoned with a timeout error. It isn't an overall deadline, so a slow response that keeps arriving isn't cut off. Throttled (`429`) and busy (`503`/`504`) responses are retried automatically, honouring `Retry-After`. | `60000` |
442
+ | `OUTLOOK_READ_ONLY` | Read-only mode: `true` (or `1`/`yes`/`on`) refuses every tool call or action that isn't a read, including dry runs, exports and attachment downloads, before it runs. Signing in still works. An unrecognised value also turns it on, with a warning. Restart the server after changing it. | off |
443
+ | `OUTLOOK_DEBUG` | Detailed stderr logs: `true` (or `1`/`yes`/`on`) adds search strategies, subjects, folder names and Graph error bodies, with email addresses and long IDs redacted. Off, each tool call logs one line (tool, action, outcome, duration) and never its arguments. Tokens, device codes and secrets are never logged. See [Server Logs and Debug Logging](docs/troubleshooting.md#server-logs-and-debug-logging). | off |
444
+ | `OUTLOOK_EXPORT_DIR` | Extra folder that `export` and `attachments` downloads may write into. Without it, files can only go to the system temp directory, `~/Downloads` or `~/Documents`; other paths are refused. Absolute path (a leading `~` is expanded). | unset |
445
+
446
+ `OUTLOOK_CONFIRM_LEVEL` (`outward`, `all-writes` or `off`; default `outward`) isn't a server setting: the plugin's safety hook reads it, in clients with no plugin settings (GitHub Copilot, VS Code, Cursor). Set it in the environment the client starts from, not in the server's `env` block. In Claude Code, use the plugin's **Confirmation level** setting instead. See [Supported Clients and Their Limits](docs/how-to/getting-started/supported-clients.md).
378
447
 
379
448
  ### MCP Client Configuration
380
449
 
381
- See [Quick Start — Configure Your MCP Client](#3-configure-your-mcp-client) above for Claude Desktop, Claude Code, Cursor, and Windsurf configs.
450
+ See [Quick Start — Configure Your MCP Client](#3-configure-your-mcp-client) above for the plugin installs and the Claude Desktop, Claude Code, VS Code / GitHub Copilot, Cursor, and Windsurf configs.
382
451
 
383
452
  If installed from source, use `node` instead of `npx`:
384
453
 
@@ -389,8 +458,7 @@ If installed from source, use `node` instead of `npx`:
389
458
  "command": "node",
390
459
  "args": ["/path/to/outlook-assistant/index.js"],
391
460
  "env": {
392
- "OUTLOOK_CLIENT_ID": "your-application-client-id",
393
- "OUTLOOK_CLIENT_SECRET": "your-client-secret-VALUE"
461
+ "OUTLOOK_CLIENT_ID": "your-application-client-id"
394
462
  }
395
463
  }
396
464
  }
@@ -441,7 +509,10 @@ This starts a local server on port 3333 to handle the OAuth callback. (The `outl
441
509
 
442
510
  ```
443
511
  outlook-assistant/
444
- ├── index.js # Main entry point (22 tools)
512
+ ├── index.js # Entry point: CLI flags, stdio transport
513
+ ├── server.js # MCP server factory (capabilities, request handler)
514
+ ├── tools.js # Tool registry (22 tools)
515
+ ├── request-handler.js # Routes MCP requests; JSON-RPC errors for unknown methods/tools
445
516
  ├── config.js # Configuration settings
446
517
  ├── outlook-auth-server.js # OAuth server (port 3333)
447
518
  ├── auth/ # Authentication module (1 tool)
@@ -464,8 +535,10 @@ outlook-assistant/
464
535
  └── utils/
465
536
  ├── graph-api.js # Microsoft Graph API client (includes $batch, path guards)
466
537
  ├── mailbox.js # me vs users/{sharedMailbox} prefix, shared-mailbox opt-in
538
+ ├── risk-classes.js # Risk class per tool/action; derives annotations and titles
539
+ ├── tool-error.js # isError tool results with a next step
467
540
  ├── safety.js # Rate limiting, recipient allowlist, dry-run
468
- ├── safe-write.js # Exclusive, outputDir-confined file writes
541
+ ├── safe-write.js # Exclusive, folder-confined file writes
469
542
  ├── datetime.js # ISO 8601 parsing and timezone conversion
470
543
  ├── odata-helpers.js # OData query building
471
544
  ├── field-presets.js # Token-efficient field selections
@@ -508,9 +581,9 @@ Enable "Allow public client flows" in Azure Portal > App registrations > Authent
508
581
 
509
582
  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.
510
583
 
511
- ### Empty API responses
584
+ ### "Authentication required."
512
585
 
513
- Check authentication status with the `auth` tool (action=status). Tokens may have expired — re-authenticate if needed.
586
+ You're signed out, or the saved token expired and couldn't be refreshed. The error says what to do next: sign in with the `auth` tool with `action=authenticate` (add `force=true` to replace an existing session), then retry the call. `auth action=status` shows the current state.
514
587
 
515
588
  ## Development
516
589
 
@@ -534,18 +607,20 @@ USE_TEST_MODE=true npm start
534
607
  1. Create a new module directory (e.g. `tasks/`)
535
608
  2. Implement tool handlers in separate files
536
609
  3. Export tool definitions from the module's `index.js`
537
- 4. Import and add tools to the `TOOLS` array in main `index.js`
538
- 5. Add tests in `test/`
539
- 6. Update `docs/quickrefs/tools-reference.md`
610
+ 4. Add the module's tools to the `TOOLS` array in `tools.js`
611
+ 5. Classify every tool and action in `utils/risk-classes.js` (a test fails on anything unclassified); the annotations and title come from there
612
+ 6. Add tests in `test/`
613
+ 7. Update `docs/quickrefs/tools-reference.md`
540
614
 
541
615
  ## Documentation
542
616
 
543
617
  | Guide | Description |
544
618
  |-------|-------------|
545
619
  | [Getting Started](docs/how-to/getting-started/connect-outlook-to-claude.md) | Install, configure, and authenticate — start here |
620
+ | [Supported Clients](docs/how-to/getting-started/supported-clients.md) | Install per client, what the skill and safety hook do in each, and known limits |
546
621
  | [Azure Setup Guide](docs/guides/azure-setup.md) | Azure account creation, app registration, permissions, and secrets |
547
- | [How-To Guides](docs/how-to/index.md) | 29 practical guides for email, calendar, contacts, and settings |
548
- | [Roadmap](ROADMAP.md) | Active milestones (v3.12.x, v3.8.x, v3.13.0+) and recent releases |
622
+ | [How-To Guides](docs/how-to/index.md) | 30 practical guides for email, calendar, contacts, and settings |
623
+ | [Roadmap](ROADMAP.md) | Active milestones (v3.14.0, v3.15.0, v4.0.0, v3.8.x, v3.16.0+) and recent releases |
549
624
  | [Troubleshooting](docs/troubleshooting.md) | Known errors and fixes, including auth, search, export and shared mailboxes |
550
625
  | [FAQ](docs/faq/faq.md) | Install, accounts, permissions, tokens, updates, uninstall |
551
626
  | [Tools Reference](docs/quickrefs/tools-reference.md) | All 22 tools with parameters |
@@ -560,7 +635,7 @@ Full documentation: [docs/](docs/README.md)
560
635
  - **Focused Inbox**: Only available on work/school Microsoft 365 accounts.
561
636
  - **Shared mailboxes**: Require a work/school account and are **opt-in**: set `OUTLOOK_SHARED_MAILBOX=read` (read) or `=true` (read and organise), restart the server, then re-authenticate with `auth action=authenticate force=true`. Until then, `sharedMailbox` calls are refused with setup guidance (`access-shared-mailbox` keeps its previous well-known-folder behaviour). `auth action=about` shows whether the shared scopes were actually granted. Support covers reading and organising only. Reading needs `Mail.Read.Shared`; organising (move/categorise/flag/mark-read/create folders via `sharedMailbox`) needs `Mail.ReadWrite.Shared` — add it in Azure and re-authenticate (until then, shared-scoped writes fail with 403; they never fall back to your own mailbox). Custom subfolders are supported — pass `folder` as a display name or nested path (e.g. `Inbox/Vendors/Acme`), a raw `folderId`, or use `listFolders: true` (or `folders action=list, sharedMailbox: …`) to discover them. **Sending, drafts, replies, and forwards from a shared mailbox are not supported** — `send-email` and `draft` (including reply/reply-all/forward) always act on the signed-in user's own mailbox, and `Mail.Send.Shared` is not requested.
562
637
  - **Meeting room search**: Requires `Place.Read.All` permission with admin consent (work/school accounts only).
563
- - **Export default path**: Exports and attachment downloads save to the system temp directory by default. Use `outputDir` (or `savePath`) to choose a different location.
638
+ - **Export default path**: Exports and attachment downloads save to the system temp directory by default (a batch `export` with `target=messages` needs an `outputDir`). Use `outputDir` (or `savePath`) with an absolute path (or one starting with `~/`) inside the system temp directory, `~/Downloads`, `~/Documents` or `OUTLOOK_EXPORT_DIR`; relative paths and other folders are refused. An existing `savePath` file is replaced only with `overwrite: true`.
564
639
  - **`list-events` date filters**: `startAfter`/`startBefore` must include `Z` or a ±hh:mm offset; zone-less and date-only values are rejected rather than guessed.
565
640
 
566
641
  ## Contributing