@chrischall/office-outlook-mcp 0.6.2 → 0.7.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.
@@ -99,7 +99,7 @@ function stripUndefined(o) {
99
99
  return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
100
100
  }
101
101
  /** Metadata probe for one attachment: never the bytes. */
102
- const ATTACHMENT_META_SELECT = 'Id,Name,ContentType,Size,IsInline';
102
+ export const ATTACHMENT_META_SELECT = 'Id,Name,ContentType,Size,IsInline';
103
103
  /** Above this an attachment is never inlined, whatever its type (base64 inflates it ~4/3 again). */
104
104
  const MAX_INLINE_BYTES = 5 * 1024 * 1024;
105
105
  /** Decoded text is cut here; a longer file is marked `textTruncated`. */
@@ -110,7 +110,7 @@ const MAX_TEXT_BYTES = 200 * 1024;
110
110
  */
111
111
  const TEXT_TYPE = /^text\/|^application\/(json|xml|csv|ics|calendar)\b|\+(json|xml)$/i;
112
112
  const TEXT_EXT = /\.(txt|csv|tsv|json|xml|ics|md|log|html?)$/i;
113
- function attachmentKind(a) {
113
+ export function attachmentKind(a) {
114
114
  const type = a['@odata.type'] ?? '';
115
115
  if (/ItemAttachment$/.test(type))
116
116
  return 'item';
@@ -126,7 +126,7 @@ function isText(contentType, name) {
126
126
  return TEXT_TYPE.test(contentType) || /svg/i.test(contentType) || TEXT_EXT.test(name);
127
127
  }
128
128
  /** Decoded size of a base64 string, without decoding it (an upper bound if it has stray characters). */
129
- function base64DecodedLength(b64) {
129
+ export function base64DecodedLength(b64) {
130
130
  const padding = b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0;
131
131
  return Math.floor((b64.length * 3) / 4) - padding;
132
132
  }
@@ -283,7 +283,7 @@ export function registerMailTools(server, client) {
283
283
  return mailboxUntrusted(data);
284
284
  return mailboxUntrusted(v === 'full' ? fullMessage(data) : compactMessage(data));
285
285
  });
286
- server.registerTool('outlook_get_unread', {
286
+ server.registerTool('outlook_list_unread_messages', {
287
287
  description: 'Triage batch: the unread messages in a folder (default inbox), newest first, each WITH its plain-text body (truncated to `maxBodyChars`) in one call — no follow-up outlook_get_message needed. Every item has a `kind`: "mail", "meetingRequest", "meetingCancelled" or "meetingResponse" (a reply to an invite you sent). Requests and cancellations also carry `event` (id, time, organizer, your current `responseStatus`) so the invite can be answered by its event id (when the meeting is no longer on your calendar — already declined, cancelled or deleted — the item has a `hint` instead and there is nothing to respond to). `to`/`cc` list at most 20 addresses; a longer list adds `toCount`/`ccCount` and `toTruncated`/`ccTruncated`. This does NOT mark anything read. Processing loop: read the batch → act on each item (reply, respond to the invite, file, flag — or leave it) → call outlook_mark_read on the plain mail you handled, so the next call returns only what is still new. An invite you answer with outlook_respond_to_invite needs no mark-read: Outlook moves it to Deleted Items under a new id, so its old id would 404. Follow `nextLink` for more. ' +
288
288
  FROM_DESCRIPTION +
289
289
  ' Cannot combine with `sinceHours` or `nextLink`.' +
@@ -190,7 +190,7 @@ export const createEventInput = z.object({
190
190
  confirmToken: confirmTokenParam,
191
191
  });
192
192
  export function registerWriteTools(server, client) {
193
- server.registerTool('outlook_send_mail', {
193
+ server.registerTool('outlook_send_message', {
194
194
  description: 'Send an email from the signed-in mailbox.' +
195
195
  ' ' +
196
196
  CONFIRM_FLOW_SENTENCE +
@@ -221,7 +221,7 @@ export function registerWriteTools(server, client) {
221
221
  };
222
222
  const recipients = [...(to ?? []), ...(cc ?? []), ...(bcc ?? [])].join(', ');
223
223
  const gate = await confirmWrite(ctx, {
224
- tool: 'outlook_send_mail',
224
+ tool: 'outlook_send_message',
225
225
  action: 'mail.send',
226
226
  message: 'Review and confirm this email before it is sent:',
227
227
  summary: `Send mail "${subject}" to ${recipients || '(no recipients)'}`,
@@ -305,7 +305,7 @@ export function registerWriteTools(server, client) {
305
305
  });
306
306
  });
307
307
  server.registerTool('outlook_update_message', {
308
- description: 'Flag, categorise and/or mark read up to 50 messages at once — the filing step of the triage loop (outlook_get_unread → act → update). `flag`: "flagged", "complete" or "none". `categories` replaces a message\'s categories; `addCategories`/`removeCategories` edit each message\'s current list instead (names from outlook_list_categories). Duplicate ids are collapsed, so each message is written once. Each id gets its own result `{ id, ok, error? }`, verified against what Outlook stored; one failure does not stop the rest, but a rejected or missing credential fails the whole call. Only your mailbox changes — nobody is notified.' +
308
+ description: 'Flag, categorise and/or mark read up to 50 messages at once — the filing step of the triage loop (outlook_list_unread_messages → act → update). `flag`: "flagged", "complete" or "none". `categories` replaces a message\'s categories; `addCategories`/`removeCategories` edit each message\'s current list instead (names from outlook_list_categories). Duplicate ids are collapsed, so each message is written once. Each id gets its own result `{ id, ok, error? }`, verified against what Outlook stored; one failure does not stop the rest, but a rejected or missing credential fails the whole call. Only your mailbox changes — nobody is notified.' +
309
309
  ' ' +
310
310
  CONFIRM_FLOW_SENTENCE,
311
311
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
@@ -661,7 +661,7 @@ export function registerWriteTools(server, client) {
661
661
  });
662
662
  });
663
663
  server.registerTool('outlook_respond_to_invite', {
664
- description: 'Accept, tentatively accept or decline a meeting invitation. Pass the invite\'s `messageId` (from outlook_get_unread, which marks it kind "meetingRequest") or the event\'s `eventId` — exactly one. The organizer is sent your response unless `sendResponse: false`, in which case only your calendar changes and no confirmation is asked. Cancelled meetings and meetings you organize are refused. The result is verified by re-reading the event\'s response status; a declined meeting may leave your calendar entirely. Outlook then moves the invite message itself to Deleted Items (under a new id), so an answered invite needs no outlook_mark_read.' +
664
+ description: 'Accept, tentatively accept or decline a meeting invitation. Pass the invite\'s `messageId` (from outlook_list_unread_messages, which marks it kind "meetingRequest") or the event\'s `eventId` — exactly one. The organizer is sent your response unless `sendResponse: false`, in which case only your calendar changes and no confirmation is asked. Cancelled meetings and meetings you organize are refused. The result is verified by re-reading the event\'s response status; a declined meeting may leave your calendar entirely. Outlook then moves the invite message itself to Deleted Items (under a new id), so an answered invite needs no outlook_mark_read.' +
665
665
  ' ' +
666
666
  CONFIRM_FLOW_SENTENCE +
667
667
  ' ' +
@@ -702,7 +702,7 @@ export function registerWriteTools(server, client) {
702
702
  throw new McpToolError("This invite's meeting is no longer on your calendar (already declined, cancelled or deleted) — there is nothing to respond to.", { hint: "Mark the invite read or file it; answering it again is not possible." });
703
703
  }
704
704
  throw new McpToolError('That message is not a meeting invite.', {
705
- hint: 'Only messages outlook_get_unread marks kind "meetingRequest" carry an event to answer.',
705
+ hint: 'Only messages outlook_list_unread_messages marks kind "meetingRequest" carry an event to answer.',
706
706
  });
707
707
  }
708
708
  }
@@ -907,7 +907,7 @@ export function registerWriteTools(server, client) {
907
907
  }
908
908
  if (mode !== 'forward' && to?.length) {
909
909
  throw new McpToolError('`to` applies only to a forward.', {
910
- hint: 'A reply goes where Outlook computes; use mode "forward" to send it somewhere else, or outlook_send_mail.',
910
+ hint: 'A reply goes where Outlook computes; use mode "forward" to send it somewhere else, or outlook_send_message.',
911
911
  });
912
912
  }
913
913
  const { send, draft, prefix } = REPLY_MODES[mode];
package/dist/version.js CHANGED
@@ -10,4 +10,4 @@
10
10
  * line below and nowhere else — not even in prose.
11
11
  */
12
12
  export const PACKAGE_NAME = 'office-outlook-mcp';
13
- export const VERSION = '0.6.2'; // x-release-please-version
13
+ export const VERSION = '0.7.0'; // x-release-please-version
package/mint.yaml CHANGED
@@ -5,6 +5,13 @@ summary: >-
5
5
  Outlook / Microsoft 365 for Claude — triage unread mail, reply, answer meeting
6
6
  invites and schedule meetings, plus folders, calendar, contacts and tasks,
7
7
  with confirmation before anything is sent
8
+ examples:
9
+ - prompt: Go through my unread Outlook inbox and summarise what needs a reply today.
10
+ note: Reads unread mail with bodies in one batch; nothing is marked read.
11
+ - prompt: What's on my work calendar for the rest of this week?
12
+ note: Recurring meetings are expanded into their individual occurrences.
13
+ - prompt: Which of my Outlook tasks are due this week?
14
+ note: Lists tasks with their status and due dates, read-only; Claude picks out the ones due this week.
8
15
  #
9
16
  # Hosting note (a comment, not user-facing summary text): this MCP mints its
10
17
  # credential through the fetchproxy BROWSER BRIDGE. It is not a full-bridge
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrischall/office-outlook-mcp",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "mcpName": "io.github.chrischall/office-outlook-mcp",
5
5
  "description": "Outlook / Microsoft 365 MCP server for Claude — developed and maintained by AI (Claude Code)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -45,7 +45,7 @@
45
45
  "typecheck": "tsc -p tsconfig.check.json"
46
46
  },
47
47
  "dependencies": {
48
- "@chrischall/mcp-utils": "^3.1.0",
48
+ "@chrischall/mcp-utils": "^3.3.0",
49
49
  "@fetchproxy/server": "^3.6.2",
50
50
  "@modelcontextprotocol/server": "^2.3.0",
51
51
  "dotenv": "^18.0.1",
@@ -58,5 +58,9 @@
58
58
  "esbuild": "^0.28.1",
59
59
  "typescript": "^7.0.2",
60
60
  "vitest": "^5.0.0"
61
+ },
62
+ "allowScripts": {
63
+ "esbuild": false,
64
+ "fsevents": false
61
65
  }
62
66
  }
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/office-outlook-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.6.2",
9
+ "version": "0.7.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/office-outlook-mcp",
14
- "version": "0.6.2",
14
+ "version": "0.7.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -59,6 +59,12 @@
59
59
  "isRequired": false,
60
60
  "format": "string"
61
61
  },
62
+ {
63
+ "name": "OUTLOOK_OUTPUT_DIR",
64
+ "description": "Absolute directory where outlook_export_messages and outlook_export_events write their files. Defaults to ~/Downloads/office-outlook-mcp. When set, a per-call dest must be inside it.",
65
+ "isRequired": false,
66
+ "format": "string"
67
+ },
62
68
  {
63
69
  "name": "FETCHPROXY_WS_HOST",
64
70
  "description": "Bind interface for the fetchproxy concentrator, a literal IP only (default 127.0.0.1). Keep on loopback; it exists for hosted deployments that run each MCP in its own network namespace.",
@@ -73,7 +79,7 @@
73
79
  },
74
80
  {
75
81
  "name": "MCP_CONFIRM_MODE",
76
- "description": "What outlook_send_mail, outlook_reply, outlook_respond_to_invite and the other write tools do on a client that cannot show a confirmation prompt (claude.ai, Claude Desktop): \"ask-user\" (default: the first call returns a preview and a confirmToken, and the model must get your approval in chat before using it), \"auto\" (the model may use the token after reviewing the preview itself) or \"refuse\". An unrecognised value is treated as refuse.",
82
+ "description": "What outlook_send_message, outlook_reply, outlook_respond_to_invite and the other write tools do on a client that cannot show a confirmation prompt (claude.ai, Claude Desktop): \"ask-user\" (default: the first call returns a preview and a confirmToken, and the model must get your approval in chat before using it), \"auto\" (the model may use the token after reviewing the preview itself) or \"refuse\". An unrecognised value is treated as refuse.",
77
83
  "isRequired": false,
78
84
  "format": "string",
79
85
  "isSecret": false