@chrischall/office-outlook-mcp 0.5.0 → 0.5.1
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 +7 -3
- package/dist/bundle.js +27 -9
- package/dist/tools/calendar.js +2 -2
- package/dist/tools/mail.js +1 -1
- package/dist/tools/writes.js +30 -4
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/server.json +30 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Outlook / Microsoft 365 — mail, calendar, contacts and tasks via natural language",
|
|
10
|
-
"version": "0.5.
|
|
10
|
+
"version": "0.5.1"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Outlook (Microsoft 365)",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "MCP server for Outlook / Microsoft 365 — triage unread mail, reply, answer meeting invites and schedule meetings, plus folders, calendar, contacts and tasks, with confirmation before anything is sent",
|
|
18
|
-
"version": "0.5.
|
|
18
|
+
"version": "0.5.1",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "office-outlook-mcp",
|
|
3
3
|
"displayName": "Outlook (Microsoft 365)",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.1",
|
|
5
5
|
"description": "Outlook / Microsoft 365 for Claude — triage unread mail, reply, answer meeting invites and schedule meetings, plus folders, calendar, contacts and tasks, with confirmation before anything is sent, using your signed-in browser session",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
package/README.md
CHANGED
|
@@ -180,7 +180,10 @@ An agent can work through new mail end to end without opening Outlook:
|
|
|
180
180
|
there are more.
|
|
181
181
|
2. **Act on each item** —
|
|
182
182
|
- an invite: `outlook_respond_to_invite` with the message id and
|
|
183
|
-
`accept`, `tentative` or `decline` (and an optional comment)
|
|
183
|
+
`accept`, `tentative` or `decline` (and an optional comment). Once you
|
|
184
|
+
respond, Outlook moves the invite message to Deleted Items under a new
|
|
185
|
+
id, so an answered invite is already filed — skip steps 3 and 4 for it
|
|
186
|
+
(its old id would 404);
|
|
184
187
|
- a message that needs an answer: `outlook_reply` (`reply`, `replyAll` or
|
|
185
188
|
`forward`), or `draftOnly: true` to leave it in Drafts for you to review;
|
|
186
189
|
- a request for a meeting: `outlook_find_meeting_times` for the attendees,
|
|
@@ -190,8 +193,9 @@ An agent can work through new mail end to end without opening Outlook:
|
|
|
190
193
|
`outlook_list_categories`) and marks read up to 50 messages in one call;
|
|
191
194
|
`outlook_move_message` moves one to another folder.
|
|
192
195
|
4. **Mark it handled** — `outlook_mark_read` (or `isRead: true` on
|
|
193
|
-
`outlook_update_message`), so the next
|
|
194
|
-
what is still new.
|
|
196
|
+
`outlook_update_message`) on the plain mail you handled, so the next
|
|
197
|
+
`outlook_get_unread` returns only what is still new. Answered invites are
|
|
198
|
+
already out of the inbox (see step 2).
|
|
195
199
|
|
|
196
200
|
Replies, forwards, invite responses and new meetings reach other people, so
|
|
197
201
|
each asks you to confirm first — except a reply saved with `draftOnly: true`
|
package/dist/bundle.js
CHANGED
|
@@ -37588,7 +37588,7 @@ var init_version = __esm({
|
|
|
37588
37588
|
"src/version.ts"() {
|
|
37589
37589
|
"use strict";
|
|
37590
37590
|
PACKAGE_NAME = "office-outlook-mcp";
|
|
37591
|
-
VERSION = "0.5.
|
|
37591
|
+
VERSION = "0.5.1";
|
|
37592
37592
|
}
|
|
37593
37593
|
});
|
|
37594
37594
|
|
|
@@ -49246,7 +49246,7 @@ function registerMailTools(server, client2) {
|
|
|
49246
49246
|
server.registerTool(
|
|
49247
49247
|
"outlook_get_unread",
|
|
49248
49248
|
{
|
|
49249
|
-
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 \u2014 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 \u2014 already declined, cancelled or deleted \u2014 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 \u2192 act on each item (reply, respond to the invite, file, flag \u2014 or leave it) \u2192 call outlook_mark_read on the
|
|
49249
|
+
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 \u2014 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 \u2014 already declined, cancelled or deleted \u2014 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 \u2192 act on each item (reply, respond to the invite, file, flag \u2014 or leave it) \u2192 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. ' + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49250
49250
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
49251
49251
|
inputSchema: external_exports.object({
|
|
49252
49252
|
folder: external_exports.string().min(1).optional().describe('Folder id or well-known name (default "inbox")'),
|
|
@@ -49465,7 +49465,7 @@ function registerCalendarTools(server, client2) {
|
|
|
49465
49465
|
server.registerTool(
|
|
49466
49466
|
"outlook_list_events",
|
|
49467
49467
|
{
|
|
49468
|
-
description: 'List calendar events in a date window. This uses the calendar VIEW, which expands recurring series into their individual occurrences \u2014 the right tool for "what is on my schedule". Times are returned in `timeZone` when given. ' + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49468
|
+
description: 'List calendar events in a date window. This uses the calendar VIEW, which expands recurring series into their individual occurrences \u2014 the right tool for "what is on my schedule". Times are returned in `timeZone` when given. view:"raw" returns Outlook\'s records as-is and uncapped; the list carries no Attendees in any view \u2014 outlook_get_event returns them. ' + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49469
49469
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
49470
49470
|
inputSchema: external_exports.object({
|
|
49471
49471
|
view: viewParam(VIEWS2),
|
|
@@ -49505,7 +49505,7 @@ function registerCalendarTools(server, client2) {
|
|
|
49505
49505
|
server.registerTool(
|
|
49506
49506
|
"outlook_get_event",
|
|
49507
49507
|
{
|
|
49508
|
-
description:
|
|
49508
|
+
description: `Get one calendar event in full, including attendees and their responses. Attendees appear in view:"full", every one of them \u2014 there is no 20-address cap as on a message's To/Cc; view:"raw" returns Outlook's untouched record, uncapped, Attendees included. ` + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49509
49509
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
49510
49510
|
inputSchema: external_exports.object({
|
|
49511
49511
|
view: viewParam(VIEWS2),
|
|
@@ -49851,6 +49851,7 @@ async function writeOrUnknown(run, checkWhere) {
|
|
|
49851
49851
|
}
|
|
49852
49852
|
var INVITE_EVENT_SELECT = "Id,Subject,IsOrganizer,IsCancelled,Start,End,Organizer,ResponseStatus";
|
|
49853
49853
|
var DELETE_EVENT_SELECT = "Id,Subject,IsOrganizer,IsCancelled,ResponseStatus,Type,SeriesMasterId,Start,End,Organizer,Attendees";
|
|
49854
|
+
var INVITE_FILED_NOTE = "Outlook moves the invite message out of your inbox (to Deleted Items, under a new id) once you respond, so it is already filed: do not call outlook_mark_read or outlook_move_message on its old id.";
|
|
49854
49855
|
var INVITE_RESPONSES = {
|
|
49855
49856
|
accept: { verb: "accept", status: "Accepted" },
|
|
49856
49857
|
tentative: { verb: "tentativelyaccept", status: "TentativelyAccepted" },
|
|
@@ -50327,7 +50328,7 @@ function registerWriteTools(server, client2) {
|
|
|
50327
50328
|
server.registerTool(
|
|
50328
50329
|
"outlook_respond_to_invite",
|
|
50329
50330
|
{
|
|
50330
|
-
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` \u2014 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. " + CONFIRM_FLOW_SENTENCE + " " + TIMEOUT_SENTENCE + " " + CONFIRM_INJECTION_RULE,
|
|
50331
|
+
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` \u2014 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. " + CONFIRM_FLOW_SENTENCE + " " + TIMEOUT_SENTENCE + " " + CONFIRM_INJECTION_RULE,
|
|
50331
50332
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
50332
50333
|
inputSchema: external_exports.object({
|
|
50333
50334
|
messageId: external_exports.string().min(1).optional().describe("Id of the invite message (resolved to its event). Give this or eventId."),
|
|
@@ -50420,14 +50421,16 @@ function registerWriteTools(server, client2) {
|
|
|
50420
50421
|
eventId: id,
|
|
50421
50422
|
response: status,
|
|
50422
50423
|
sentResponse: notify,
|
|
50423
|
-
note: "The declined meeting is no longer on your calendar."
|
|
50424
|
+
note: "The declined meeting is no longer on your calendar.",
|
|
50425
|
+
inviteMessage: INVITE_FILED_NOTE
|
|
50424
50426
|
});
|
|
50425
50427
|
}
|
|
50426
50428
|
return minifiedResult({
|
|
50427
50429
|
responded: null,
|
|
50428
50430
|
eventId: id,
|
|
50429
50431
|
sentResponse: notify,
|
|
50430
|
-
warning: `Outlook accepted the response but re-reading the event failed (${e instanceof Error ? e.message : String(e)}), so it is unverified
|
|
50432
|
+
warning: `Outlook accepted the response but re-reading the event failed (${e instanceof Error ? e.message : String(e)}), so it is unverified.`,
|
|
50433
|
+
inviteMessage: INVITE_FILED_NOTE
|
|
50431
50434
|
});
|
|
50432
50435
|
}
|
|
50433
50436
|
const got = after?.ResponseStatus?.Response;
|
|
@@ -50436,7 +50439,8 @@ function registerWriteTools(server, client2) {
|
|
|
50436
50439
|
eventId: id,
|
|
50437
50440
|
response: got,
|
|
50438
50441
|
sentResponse: notify,
|
|
50439
|
-
...got === status ? {} : { warning: `Outlook accepted the response but the status did not change (still ${got ?? "unknown"}).` }
|
|
50442
|
+
...got === status ? {} : { warning: `Outlook accepted the response but the status did not change (still ${got ?? "unknown"}).` },
|
|
50443
|
+
inviteMessage: INVITE_FILED_NOTE
|
|
50440
50444
|
});
|
|
50441
50445
|
}
|
|
50442
50446
|
);
|
|
@@ -50557,12 +50561,26 @@ function registerWriteTools(server, client2) {
|
|
|
50557
50561
|
...forwardTo
|
|
50558
50562
|
});
|
|
50559
50563
|
} catch (e) {
|
|
50564
|
+
const what = `your comment${mode === "forward" ? " and recipients" : ""}`;
|
|
50565
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
50566
|
+
if (isCredentialFailure(e)) {
|
|
50567
|
+
throw new McpToolError(
|
|
50568
|
+
`The reply draft was created (draftId ${created.Id}) but adding ${what} failed: ${reason}`,
|
|
50569
|
+
{
|
|
50570
|
+
hint: `${e instanceof McpToolError && e.hint ? `${e.hint} ` : ""}Then edit draft ${created.Id} in Drafts, or delete it before trying again.`,
|
|
50571
|
+
cause: e,
|
|
50572
|
+
kind: errorKindOf(e),
|
|
50573
|
+
status: errorStatusOf(e)
|
|
50574
|
+
}
|
|
50575
|
+
);
|
|
50576
|
+
}
|
|
50577
|
+
const warning = e instanceof WriteOutcomeUnknownError ? `The draft was created, and ${what} may have been applied: Outlook did not confirm the edit (${e.timedOut ? "it did not answer in time" : "the connection dropped"}). Check the draft in Drafts before editing it again or trying again.` : `The draft was created but adding ${what} failed (${reason}). Edit it in Drafts, or delete it before trying again.`;
|
|
50560
50578
|
return minifiedResult({
|
|
50561
50579
|
drafted: true,
|
|
50562
50580
|
mode,
|
|
50563
50581
|
draftId: created.Id,
|
|
50564
50582
|
WebLink: created.WebLink,
|
|
50565
|
-
warning
|
|
50583
|
+
warning
|
|
50566
50584
|
});
|
|
50567
50585
|
}
|
|
50568
50586
|
return minifiedResult({ drafted: true, mode, draftId: created.Id, WebLink: created.WebLink });
|
package/dist/tools/calendar.js
CHANGED
|
@@ -40,7 +40,7 @@ function trimTime(dt) {
|
|
|
40
40
|
const attendeeList = (what) => z.array(z.string().min(3)).describe(`Email addresses of ${what}`);
|
|
41
41
|
export function registerCalendarTools(server, client) {
|
|
42
42
|
server.registerTool('outlook_list_events', {
|
|
43
|
-
description: 'List calendar events in a date window. This uses the calendar VIEW, which expands recurring series into their individual occurrences — the right tool for "what is on my schedule". Times are returned in `timeZone` when given.' +
|
|
43
|
+
description: 'List calendar events in a date window. This uses the calendar VIEW, which expands recurring series into their individual occurrences — the right tool for "what is on my schedule". Times are returned in `timeZone` when given. view:"raw" returns Outlook\'s records as-is and uncapped; the list carries no Attendees in any view — outlook_get_event returns them.' +
|
|
44
44
|
' ' + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
45
45
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
46
46
|
inputSchema: z.object({
|
|
@@ -75,7 +75,7 @@ export function registerCalendarTools(server, client) {
|
|
|
75
75
|
return mailboxUntrusted(projectCollection(data, v === 'full' ? fullEvent : compactEvent, 'event'));
|
|
76
76
|
});
|
|
77
77
|
server.registerTool('outlook_get_event', {
|
|
78
|
-
description: 'Get one calendar event in full, including attendees and their responses.' +
|
|
78
|
+
description: 'Get one calendar event in full, including attendees and their responses. Attendees appear in view:"full", every one of them — there is no 20-address cap as on a message\'s To/Cc; view:"raw" returns Outlook\'s untouched record, uncapped, Attendees included.' +
|
|
79
79
|
' ' + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
80
80
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
81
81
|
inputSchema: z.object({
|
package/dist/tools/mail.js
CHANGED
|
@@ -240,7 +240,7 @@ export function registerMailTools(server, client) {
|
|
|
240
240
|
return mailboxUntrusted(v === 'full' ? fullMessage(data) : compactMessage(data));
|
|
241
241
|
});
|
|
242
242
|
server.registerTool('outlook_get_unread', {
|
|
243
|
-
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
|
|
243
|
+
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.' +
|
|
244
244
|
' ' + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
245
245
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
246
246
|
inputSchema: z.object({
|
package/dist/tools/writes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { CONFIRM_FLOW_SENTENCE, CONFIRM_INJECTION_RULE, confirmTokenParam, confirmWrite, errorStatusOf, mapWithConcurrency, McpToolError, minifiedResult, WriteOutcomeUnknownError, } from '@chrischall/mcp-utils';
|
|
2
|
+
import { CONFIRM_FLOW_SENTENCE, CONFIRM_INJECTION_RULE, confirmTokenParam, confirmWrite, errorKindOf, errorStatusOf, mapWithConcurrency, McpToolError, minifiedResult, WriteOutcomeUnknownError, } from '@chrischall/mcp-utils';
|
|
3
3
|
import { isCredentialFailure } from '../client.js';
|
|
4
4
|
import { mailboxTimeZone } from '../timezone.js';
|
|
5
5
|
import { cappedRecipients, MAX_RECIPIENTS } from '../view.js';
|
|
@@ -90,6 +90,12 @@ async function writeOrUnknown(run, checkWhere) {
|
|
|
90
90
|
}
|
|
91
91
|
const INVITE_EVENT_SELECT = 'Id,Subject,IsOrganizer,IsCancelled,Start,End,Organizer,ResponseStatus';
|
|
92
92
|
const DELETE_EVENT_SELECT = 'Id,Subject,IsOrganizer,IsCancelled,ResponseStatus,Type,SeriesMasterId,Start,End,Organizer,Attendees';
|
|
93
|
+
/**
|
|
94
|
+
* What happens to the invite MESSAGE once you respond. Live 2026-10-10:
|
|
95
|
+
* Outlook moves it to Deleted Items under a new id, so the triage loop's
|
|
96
|
+
* "respond, then outlook_mark_read the ids you handled" 404'd on every invite.
|
|
97
|
+
*/
|
|
98
|
+
const INVITE_FILED_NOTE = 'Outlook moves the invite message out of your inbox (to Deleted Items, under a new id) once you respond, so it is already filed: do not call outlook_mark_read or outlook_move_message on its old id.';
|
|
93
99
|
/** Tool input → the action segment Outlook takes and the ResponseStatus it should leave. */
|
|
94
100
|
const INVITE_RESPONSES = {
|
|
95
101
|
accept: { verb: 'accept', status: 'Accepted' },
|
|
@@ -655,7 +661,7 @@ export function registerWriteTools(server, client) {
|
|
|
655
661
|
});
|
|
656
662
|
});
|
|
657
663
|
server.registerTool('outlook_respond_to_invite', {
|
|
658
|
-
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.' +
|
|
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.' +
|
|
659
665
|
' ' +
|
|
660
666
|
CONFIRM_FLOW_SENTENCE +
|
|
661
667
|
' ' +
|
|
@@ -765,6 +771,7 @@ export function registerWriteTools(server, client) {
|
|
|
765
771
|
response: status,
|
|
766
772
|
sentResponse: notify,
|
|
767
773
|
note: 'The declined meeting is no longer on your calendar.',
|
|
774
|
+
inviteMessage: INVITE_FILED_NOTE,
|
|
768
775
|
});
|
|
769
776
|
}
|
|
770
777
|
return minifiedResult({
|
|
@@ -772,6 +779,7 @@ export function registerWriteTools(server, client) {
|
|
|
772
779
|
eventId: id,
|
|
773
780
|
sentResponse: notify,
|
|
774
781
|
warning: `Outlook accepted the response but re-reading the event failed (${e instanceof Error ? e.message : String(e)}), so it is unverified.`,
|
|
782
|
+
inviteMessage: INVITE_FILED_NOTE,
|
|
775
783
|
});
|
|
776
784
|
}
|
|
777
785
|
const got = after?.ResponseStatus?.Response;
|
|
@@ -783,6 +791,7 @@ export function registerWriteTools(server, client) {
|
|
|
783
791
|
...(got === status
|
|
784
792
|
? {}
|
|
785
793
|
: { warning: `Outlook accepted the response but the status did not change (still ${got ?? 'unknown'}).` }),
|
|
794
|
+
inviteMessage: INVITE_FILED_NOTE,
|
|
786
795
|
});
|
|
787
796
|
});
|
|
788
797
|
server.registerTool('outlook_delete_event', {
|
|
@@ -925,14 +934,31 @@ export function registerWriteTools(server, client) {
|
|
|
925
934
|
});
|
|
926
935
|
}
|
|
927
936
|
catch (e) {
|
|
937
|
+
const what = `your comment${mode === 'forward' ? ' and recipients' : ''}`;
|
|
938
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
939
|
+
// A rejected or missing credential is not about this draft: every
|
|
940
|
+
// next call fails the same way, so it stays the tool error — with
|
|
941
|
+
// the draft id in it, so a retry does not leave a second draft.
|
|
942
|
+
if (isCredentialFailure(e)) {
|
|
943
|
+
throw new McpToolError(`The reply draft was created (draftId ${created.Id}) but adding ${what} failed: ${reason}`, {
|
|
944
|
+
hint: `${e instanceof McpToolError && e.hint ? `${e.hint} ` : ''}Then edit draft ${created.Id} in Drafts, or delete it before trying again.`,
|
|
945
|
+
cause: e,
|
|
946
|
+
kind: errorKindOf(e),
|
|
947
|
+
status: errorStatusOf(e),
|
|
948
|
+
});
|
|
949
|
+
}
|
|
928
950
|
// The draft exists either way; an error here would hide its id and
|
|
929
|
-
// invite a retry that leaves a second one.
|
|
951
|
+
// invite a retry that leaves a second one. A PATCH that was sent
|
|
952
|
+
// but never answered may well have landed, so it is not "failed".
|
|
953
|
+
const warning = e instanceof WriteOutcomeUnknownError
|
|
954
|
+
? `The draft was created, and ${what} may have been applied: Outlook did not confirm the edit (${e.timedOut ? 'it did not answer in time' : 'the connection dropped'}). Check the draft in Drafts before editing it again or trying again.`
|
|
955
|
+
: `The draft was created but adding ${what} failed (${reason}). Edit it in Drafts, or delete it before trying again.`;
|
|
930
956
|
return minifiedResult({
|
|
931
957
|
drafted: true,
|
|
932
958
|
mode,
|
|
933
959
|
draftId: created.Id,
|
|
934
960
|
WebLink: created.WebLink,
|
|
935
|
-
warning
|
|
961
|
+
warning,
|
|
936
962
|
});
|
|
937
963
|
}
|
|
938
964
|
return minifiedResult({ drafted: true, mode, draftId: created.Id, WebLink: created.WebLink });
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/office-outlook-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
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>",
|
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.5.
|
|
9
|
+
"version": "0.5.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/office-outlook-mcp",
|
|
14
|
-
"version": "0.5.
|
|
14
|
+
"version": "0.5.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -70,6 +70,34 @@
|
|
|
70
70
|
"description": "Absolute path where the fetchproxy long-term identity keypair is stored (default ~/.fetchproxy/identity/).",
|
|
71
71
|
"isRequired": false,
|
|
72
72
|
"format": "string"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"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.",
|
|
77
|
+
"isRequired": false,
|
|
78
|
+
"format": "string",
|
|
79
|
+
"isSecret": false
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "MCP_CONFIRM_ELICITATION",
|
|
83
|
+
"description": "\"off\" never shows a confirmation prompt, so every client gets the MCP_CONFIRM_MODE flow. Set it for a client that says it can show prompts but never does. Defaults to on.",
|
|
84
|
+
"isRequired": false,
|
|
85
|
+
"format": "string",
|
|
86
|
+
"isSecret": false
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "MCP_CONFIRM_TTL_SECONDS",
|
|
90
|
+
"description": "How long a confirmToken stays valid, in seconds. Defaults to 600.",
|
|
91
|
+
"isRequired": false,
|
|
92
|
+
"format": "string",
|
|
93
|
+
"isSecret": false
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "MCP_CONFIRM_SECRET",
|
|
97
|
+
"description": "Signing key for confirmTokens. Random per process by default; set it only if tokens must survive a server restart.",
|
|
98
|
+
"isRequired": false,
|
|
99
|
+
"format": "string",
|
|
100
|
+
"isSecret": true
|
|
73
101
|
}
|
|
74
102
|
]
|
|
75
103
|
}
|