@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +78 -8
- package/dist/bundle.js +1939 -190
- package/dist/index.js +2 -0
- package/dist/tools/_invite.js +1 -1
- package/dist/tools/export.js +1041 -0
- package/dist/tools/mail.js +4 -4
- package/dist/tools/writes.js +6 -6
- package/dist/version.js +1 -1
- package/mint.yaml +7 -0
- package/package.json +6 -2
- package/server.json +9 -3
package/dist/tools/mail.js
CHANGED
|
@@ -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('
|
|
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`.' +
|
package/dist/tools/writes.js
CHANGED
|
@@ -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('
|
|
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: '
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
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.
|
|
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.
|
|
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.
|
|
9
|
+
"version": "0.7.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/office-outlook-mcp",
|
|
14
|
-
"version": "0.
|
|
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
|
|
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
|