@chrischall/office-outlook-mcp 0.6.0 → 0.6.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/mcp.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +11 -6
- package/dist/bundle.js +196 -53
- package/dist/tools/writes.js +35 -25
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/server.json +2 -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.6.
|
|
10
|
+
"version": "0.6.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.6.
|
|
18
|
+
"version": "0.6.1",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/.claude-plugin/mcp.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "office-outlook-mcp",
|
|
3
3
|
"displayName": "Outlook (Microsoft 365)",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.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
|
@@ -119,8 +119,9 @@ Everything is optional — with nothing set, the server captures from the browse
|
|
|
119
119
|
`outlook_update_message`, `outlook_move_message`, `outlook_create_event`,
|
|
120
120
|
`outlook_update_event`, `outlook_respond_to_invite`, `outlook_delete_event`,
|
|
121
121
|
`outlook_reply`.
|
|
122
|
-
|
|
123
|
-
`
|
|
122
|
+
Some skip the confirmation because nothing reaches anyone else:
|
|
123
|
+
`outlook_create_draft` (the draft stays in Drafts until you send it from
|
|
124
|
+
Outlook), `outlook_reply` with `draftOnly: true` (the reply stays in Drafts),
|
|
124
125
|
`outlook_respond_to_invite` with `sendResponse: false`, and
|
|
125
126
|
`outlook_delete_event` on your own appointment with no attendees or on a
|
|
126
127
|
meeting the organizer already cancelled (in each, only your calendar
|
|
@@ -129,9 +130,12 @@ changes).
|
|
|
129
130
|
**Meetings** — `outlook_find_meeting_times` asks Outlook's Scheduling
|
|
130
131
|
Assistant for slots when everyone is free, and `outlook_get_schedule` shows
|
|
131
132
|
each person's busy blocks. Each suggested slot carries `createEventArgs`,
|
|
132
|
-
which `outlook_create_event` accepts unchanged. `outlook_create_event`
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
which `outlook_create_event` accepts unchanged. `outlook_create_event`
|
|
134
|
+
attaches a Microsoft Teams meeting by default and returns its join link; pass
|
|
135
|
+
`teamsMeeting: false` to leave it off. `outlook_update_event` never changes how
|
|
136
|
+
people join a meeting unless asked: pass `teamsMeeting: true` to add a Teams
|
|
137
|
+
meeting to an event that lacks one, and the confirmation says so in plain
|
|
138
|
+
words. Only the organizer
|
|
135
139
|
can update a meeting. `outlook_respond_to_invite` accepts, tentatively accepts
|
|
136
140
|
or declines an invite, by its message id or its event id.
|
|
137
141
|
`outlook_delete_event` deletes an event you organize — one occurrence by the
|
|
@@ -217,7 +221,8 @@ or repeated across pages, and marking mail read between pages loses nothing.
|
|
|
217
221
|
## Confirmations
|
|
218
222
|
|
|
219
223
|
Every write asks you to confirm before anything is sent or changed (except a
|
|
220
|
-
draft-only reply and a silent invite response — see
|
|
224
|
+
new draft, a draft-only reply and a silent invite response — see
|
|
225
|
+
[Tools](#tools)). A client
|
|
221
226
|
that can show a confirmation prompt (Claude Code) shows one. On a client that
|
|
222
227
|
cannot, the first call does nothing and returns a `confirmation-required`
|
|
223
228
|
preview plus a `confirmToken`; only a repeat call with that token performs the
|
package/dist/bundle.js
CHANGED
|
@@ -248,6 +248,7 @@ __export(util_exports, {
|
|
|
248
248
|
required: () => required,
|
|
249
249
|
safeExtend: () => safeExtend,
|
|
250
250
|
shallowClone: () => shallowClone,
|
|
251
|
+
sizeCaps: () => sizeCaps,
|
|
251
252
|
slugify: () => slugify,
|
|
252
253
|
stringifyPrimitive: () => stringifyPrimitive,
|
|
253
254
|
toZod: () => toZod,
|
|
@@ -671,6 +672,18 @@ function prefixIssues(path, issues) {
|
|
|
671
672
|
function unwrapMessage(message) {
|
|
672
673
|
return typeof message === "string" ? message : message?.message;
|
|
673
674
|
}
|
|
675
|
+
function sizeCaps(defChecks) {
|
|
676
|
+
const caps = [];
|
|
677
|
+
for (const ch of defChecks ?? []) {
|
|
678
|
+
const d = ch._zod.def;
|
|
679
|
+
if (!/_(length|size)$|_equals$/.test(d.check))
|
|
680
|
+
break;
|
|
681
|
+
const max = d.maximum ?? d.length ?? d.size;
|
|
682
|
+
if (max >= 0)
|
|
683
|
+
caps.push([max, ch]);
|
|
684
|
+
}
|
|
685
|
+
return caps;
|
|
686
|
+
}
|
|
674
687
|
function attachSchema(issues, start, inst) {
|
|
675
688
|
var _a3;
|
|
676
689
|
for (let i = start; i < issues.length; i++) {
|
|
@@ -2234,7 +2247,7 @@ var init_versions = __esm({
|
|
|
2234
2247
|
version = {
|
|
2235
2248
|
major: 4,
|
|
2236
2249
|
minor: 6,
|
|
2237
|
-
patch:
|
|
2250
|
+
patch: 7
|
|
2238
2251
|
};
|
|
2239
2252
|
}
|
|
2240
2253
|
});
|
|
@@ -2396,6 +2409,28 @@ function isValidJWT(token, algorithm = null) {
|
|
|
2396
2409
|
return false;
|
|
2397
2410
|
}
|
|
2398
2411
|
}
|
|
2412
|
+
function capSize(inst, size) {
|
|
2413
|
+
const caps = sizeCaps(inst._zod.def.checks);
|
|
2414
|
+
if (!caps.length)
|
|
2415
|
+
return;
|
|
2416
|
+
const cap = Math.min(...caps.map((c) => c[0]));
|
|
2417
|
+
const parse3 = inst._zod.parse;
|
|
2418
|
+
inst._zod.parse = (payload, ctx) => {
|
|
2419
|
+
const n = size(payload.value);
|
|
2420
|
+
if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
|
|
2421
|
+
for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
|
|
2422
|
+
if (n > max && ch._zod.def.when(payload)) {
|
|
2423
|
+
const start = payload.issues.length;
|
|
2424
|
+
ch._zod.check(payload);
|
|
2425
|
+
payload.issues[start].continue = false;
|
|
2426
|
+
attachSchema(payload.issues, start, inst);
|
|
2427
|
+
return payload;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
return parse3(payload, ctx);
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2399
2434
|
function handleArrayResult(result, final, index) {
|
|
2400
2435
|
if (result.issues.length) {
|
|
2401
2436
|
final.issues.push(...prefixIssues(index, result.issues));
|
|
@@ -2896,7 +2931,7 @@ function handleRefineResult(result, payload, input2, inst) {
|
|
|
2896
2931
|
payload.issues.push(issue(_iss));
|
|
2897
2932
|
}
|
|
2898
2933
|
}
|
|
2899
|
-
var $ZodType, toStandardResult, $ZodString, $ZodStringFormat, $ZodGUID, $ZodUUID, $ZodEmail, URL_BAD_FORMAT, URL_UNPARSEABLE, asciiTabOrNewline, $ZodURL, $ZodEmoji, $ZodNanoID, $ZodCUID, $ZodCUID2, $ZodULID, $ZodXID, $ZodKSUID, $ZodISODateTime, $ZodISODate, $ZodISOTime, $ZodISODuration, $ZodIPv4, ipv6Alphabet, $ZodIPv6, $ZodMAC, $ZodCIDRv4, $ZodCIDRv6, base64Charset, $ZodBase64, base64urlCharset, $ZodBase64URL, $ZodE164, CC_SANITIZE, $ZodCreditCard, $ZodIBAN, $ZodJWT, $ZodCustomStringFormat, $ZodNumber, $ZodNumberFormat, $ZodBoolean, $ZodBigInt, $ZodBigIntFormat, $ZodSymbol, $ZodUndefined, $ZodNull, $ZodAny, $ZodUnknown, $ZodNever, $ZodVoid, $ZodDate, $ZodArray, NO_SYMBOL_KEYS, $ZodObject, $ZodObjectJIT, $ZodUnion, $ZodXor, $ZodDiscriminatedUnion, $ZodIntersection, $ZodTuple, $ZodRecord, $ZodMap, $ZodSet, $ZodEnum, $ZodLiteral, $ZodFile, $ZodTransform, $ZodOptional, $ZodExactOptional, $ZodNullable, $ZodDefault, $ZodPrefault, $ZodNonOptional, $ZodSuccess, $ZodCatch, $ZodNaN, $ZodPipe, $ZodCodec, $ZodPreprocess, $ZodReadonly, $ZodTemplateLiteral, $ZodFunction, $ZodPromise, $ZodLazy, $ZodCustom;
|
|
2934
|
+
var $ZodType, toStandardResult, $ZodString, $ZodStringFormat, $ZodGUID, $ZodUUID, $ZodEmail, URL_BAD_FORMAT, URL_UNPARSEABLE, asciiTabOrNewline, $ZodURL, $ZodEmoji, $ZodNanoID, $ZodCUID, $ZodCUID2, $ZodULID, $ZodXID, $ZodKSUID, $ZodISODateTime, $ZodISODate, $ZodISOTime, $ZodISODuration, $ZodIPv4, ipv6Alphabet, $ZodIPv6, $ZodMAC, $ZodCIDRv4, $ZodCIDRv6, base64Charset, $ZodBase64, base64urlCharset, $ZodBase64URL, $ZodE164, CC_SANITIZE, $ZodCreditCard, $ZodIBAN, $ZodJWT, $ZodCustomStringFormat, $ZodNumber, $ZodNumberFormat, $ZodBoolean, $ZodBigInt, $ZodBigIntFormat, $ZodSymbol, $ZodUndefined, $ZodNull, $ZodAny, $ZodUnknown, $ZodNever, $ZodVoid, $ZodDate, arraySize, mapSize, setSize, $ZodArray, NO_SYMBOL_KEYS, $ZodObject, $ZodObjectJIT, $ZodUnion, $ZodXor, $ZodDiscriminatedUnion, $ZodIntersection, $ZodTuple, $ZodRecord, $ZodMap, $ZodSet, $ZodEnum, $ZodLiteral, $ZodFile, $ZodTransform, $ZodOptional, $ZodExactOptional, $ZodNullable, $ZodDefault, $ZodPrefault, $ZodNonOptional, $ZodSuccess, $ZodCatch, $ZodNaN, $ZodPipe, $ZodCodec, $ZodPreprocess, $ZodReadonly, $ZodTemplateLiteral, $ZodFunction, $ZodPromise, $ZodLazy, $ZodCustom;
|
|
2900
2935
|
var init_schemas = __esm({
|
|
2901
2936
|
"node_modules/zod/v4/core/schemas.js"() {
|
|
2902
2937
|
init_checks();
|
|
@@ -3503,6 +3538,9 @@ var init_schemas = __esm({
|
|
|
3503
3538
|
return payload;
|
|
3504
3539
|
};
|
|
3505
3540
|
});
|
|
3541
|
+
arraySize = (input2) => Array.isArray(input2) ? input2.length : -1;
|
|
3542
|
+
mapSize = (input2) => input2 instanceof Map ? input2.size : -1;
|
|
3543
|
+
setSize = (input2) => input2 instanceof Set ? input2.size : -1;
|
|
3506
3544
|
$ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
|
|
3507
3545
|
$ZodType.init(inst, def);
|
|
3508
3546
|
const memo3 = globalConfig.memoizer;
|
|
@@ -3540,6 +3578,7 @@ var init_schemas = __esm({
|
|
|
3540
3578
|
}
|
|
3541
3579
|
return payload;
|
|
3542
3580
|
};
|
|
3581
|
+
capSize(inst, arraySize);
|
|
3543
3582
|
});
|
|
3544
3583
|
NO_SYMBOL_KEYS = [];
|
|
3545
3584
|
$ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
@@ -4178,6 +4217,7 @@ var init_schemas = __esm({
|
|
|
4178
4217
|
return Promise.all(proms).then(() => payload);
|
|
4179
4218
|
return payload;
|
|
4180
4219
|
};
|
|
4220
|
+
capSize(inst, mapSize);
|
|
4181
4221
|
});
|
|
4182
4222
|
$ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
|
|
4183
4223
|
$ZodType.init(inst, def);
|
|
@@ -4214,6 +4254,7 @@ var init_schemas = __esm({
|
|
|
4214
4254
|
return Promise.all(proms).then(() => payload);
|
|
4215
4255
|
return payload;
|
|
4216
4256
|
};
|
|
4257
|
+
capSize(inst, setSize);
|
|
4217
4258
|
});
|
|
4218
4259
|
$ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
4219
4260
|
$ZodType.init(inst, def);
|
|
@@ -14003,9 +14044,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
14003
14044
|
doc.write(`}`);
|
|
14004
14045
|
return outputVar;
|
|
14005
14046
|
}
|
|
14047
|
+
function generateSizeGate(doc, schema, size) {
|
|
14048
|
+
for (const [max, check2] of sizeCaps(schema._zod.def.checks)) {
|
|
14049
|
+
doc.write(`if (${size} > ${numericOperand(max, check2._zod.def.check)}) return INVALID;`);
|
|
14050
|
+
}
|
|
14051
|
+
}
|
|
14006
14052
|
function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
14007
14053
|
const def = schema._zod.def;
|
|
14008
14054
|
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
14055
|
+
generateSizeGate(doc, schema, `${accessor}.length`);
|
|
14009
14056
|
const outputVar = buildsValue ? newVar(ctx) : null;
|
|
14010
14057
|
const iVar = newVar(ctx);
|
|
14011
14058
|
const elemVar = newVar(ctx);
|
|
@@ -14405,6 +14452,7 @@ function literalPropertyKey(ctx, key) {
|
|
|
14405
14452
|
function generateMapCheck(doc, ctx, schema, accessor) {
|
|
14406
14453
|
const def = schema._zod.def;
|
|
14407
14454
|
doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
|
|
14455
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
14408
14456
|
const outputVar = newVar(ctx);
|
|
14409
14457
|
const kVar = newVar(ctx);
|
|
14410
14458
|
const valVar = newVar(ctx);
|
|
@@ -14421,6 +14469,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
|
|
|
14421
14469
|
function generateSetCheck(doc, ctx, schema, accessor) {
|
|
14422
14470
|
const def = schema._zod.def;
|
|
14423
14471
|
doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
|
|
14472
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
14424
14473
|
const outputVar = newVar(ctx);
|
|
14425
14474
|
const valVar = newVar(ctx);
|
|
14426
14475
|
doc.write(`const ${outputVar} = new Set();`);
|
|
@@ -35482,7 +35531,7 @@ var init_errors3 = __esm({
|
|
|
35482
35531
|
// webhook signing secret (Stripe-style)
|
|
35483
35532
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
35484
35533
|
GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
|
|
35485
|
-
QUERY_SECRET_RE = new RegExp(`([
|
|
35534
|
+
QUERY_SECRET_RE = new RegExp(`([?&#;](?:(?:access|refresh|id|auth|session|csrf|xsrf|api)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|secret|token|auth|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
35486
35535
|
OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
35487
35536
|
FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
35488
35537
|
HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
@@ -35493,6 +35542,8 @@ var init_errors3 = __esm({
|
|
|
35493
35542
|
"api[_-]?secret",
|
|
35494
35543
|
"(?:x[_-])?api[_-]?key",
|
|
35495
35544
|
"private[_-]?key",
|
|
35545
|
+
"aws[_-]?secret[_-]?access[_-]?key",
|
|
35546
|
+
// fleet-audit#1297
|
|
35496
35547
|
"code[_-]?verifier",
|
|
35497
35548
|
// PKCE; a bare `verifier` key is too generic for JSON
|
|
35498
35549
|
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
@@ -36651,6 +36702,9 @@ var init_headers = __esm({
|
|
|
36651
36702
|
});
|
|
36652
36703
|
|
|
36653
36704
|
// node_modules/@chrischall/mcp-utils/dist/internal/ascii.js
|
|
36705
|
+
function asciiLower(s) {
|
|
36706
|
+
return s.replace(/[A-Z]+/g, (m) => m.toLowerCase());
|
|
36707
|
+
}
|
|
36654
36708
|
var init_ascii = __esm({
|
|
36655
36709
|
"node_modules/@chrischall/mcp-utils/dist/internal/ascii.js"() {
|
|
36656
36710
|
}
|
|
@@ -36662,16 +36716,42 @@ var init_code_point = __esm({
|
|
|
36662
36716
|
}
|
|
36663
36717
|
});
|
|
36664
36718
|
|
|
36719
|
+
// node_modules/@chrischall/mcp-utils/dist/internal/tags.js
|
|
36720
|
+
var init_tags = __esm({
|
|
36721
|
+
"node_modules/@chrischall/mcp-utils/dist/internal/tags.js"() {
|
|
36722
|
+
}
|
|
36723
|
+
});
|
|
36724
|
+
|
|
36665
36725
|
// node_modules/@chrischall/mcp-utils/dist/scrape/index.js
|
|
36666
36726
|
function isCloudflareChallenge(html) {
|
|
36667
|
-
return
|
|
36727
|
+
return html.includes("_cf_chl_opt") || hasJustAMomentTitle(html);
|
|
36728
|
+
}
|
|
36729
|
+
function hasJustAMomentTitle(html) {
|
|
36730
|
+
const lower2 = asciiLower(html);
|
|
36731
|
+
let i = 0;
|
|
36732
|
+
for (; ; ) {
|
|
36733
|
+
const open2 = lower2.indexOf("<title", i);
|
|
36734
|
+
if (open2 < 0)
|
|
36735
|
+
return false;
|
|
36736
|
+
const gt = lower2.indexOf(">", open2 + 6);
|
|
36737
|
+
if (gt < 0)
|
|
36738
|
+
return false;
|
|
36739
|
+
let j = gt + 1;
|
|
36740
|
+
while (j < lower2.length && /\s/.test(lower2[j]))
|
|
36741
|
+
j++;
|
|
36742
|
+
if (lower2.startsWith(JUST_A_MOMENT, j))
|
|
36743
|
+
return true;
|
|
36744
|
+
i = gt + 1;
|
|
36745
|
+
}
|
|
36668
36746
|
}
|
|
36669
|
-
var NEXT_DATA_MAX_CHARS;
|
|
36747
|
+
var NEXT_DATA_MAX_CHARS, JUST_A_MOMENT;
|
|
36670
36748
|
var init_scrape = __esm({
|
|
36671
36749
|
"node_modules/@chrischall/mcp-utils/dist/scrape/index.js"() {
|
|
36672
36750
|
init_ascii();
|
|
36673
36751
|
init_code_point();
|
|
36752
|
+
init_tags();
|
|
36674
36753
|
NEXT_DATA_MAX_CHARS = 16 * 1024 * 1024;
|
|
36754
|
+
JUST_A_MOMENT = "just a moment";
|
|
36675
36755
|
}
|
|
36676
36756
|
});
|
|
36677
36757
|
|
|
@@ -36707,7 +36787,7 @@ var init_net_atoms = __esm({
|
|
|
36707
36787
|
// node_modules/@chrischall/mcp-utils/dist/http/body-limit.js
|
|
36708
36788
|
async function readBytesCapped(res, maxBytes, service, bounded) {
|
|
36709
36789
|
if (maxBytes !== void 0) {
|
|
36710
|
-
const declared = Number(res
|
|
36790
|
+
const declared = Number(responseHeader(res, "content-length") ?? NaN);
|
|
36711
36791
|
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
36712
36792
|
cancelStream(res.body);
|
|
36713
36793
|
throw new ResponseTooLargeError(service, maxBytes);
|
|
@@ -36752,6 +36832,7 @@ var ResponseTooLargeError;
|
|
|
36752
36832
|
var init_body_limit = __esm({
|
|
36753
36833
|
"node_modules/@chrischall/mcp-utils/dist/http/body-limit.js"() {
|
|
36754
36834
|
init_errors3();
|
|
36835
|
+
init_headers();
|
|
36755
36836
|
ResponseTooLargeError = class extends McpToolError {
|
|
36756
36837
|
maxBytes;
|
|
36757
36838
|
constructor(service, maxBytes) {
|
|
@@ -36831,6 +36912,27 @@ var init_retry_after = __esm({
|
|
|
36831
36912
|
});
|
|
36832
36913
|
|
|
36833
36914
|
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
36915
|
+
function withoutCredentials(headers, tokenHeader) {
|
|
36916
|
+
const drop = new Set(CROSS_ORIGIN_DROPPED_HEADERS);
|
|
36917
|
+
if (tokenHeader !== void 0)
|
|
36918
|
+
drop.add(tokenHeader.toLowerCase());
|
|
36919
|
+
const out = {};
|
|
36920
|
+
for (const [k, v] of Object.entries(headers))
|
|
36921
|
+
if (!drop.has(k.toLowerCase()))
|
|
36922
|
+
out[k] = v;
|
|
36923
|
+
return out;
|
|
36924
|
+
}
|
|
36925
|
+
function joinQuery(path, query) {
|
|
36926
|
+
if (!query)
|
|
36927
|
+
return path;
|
|
36928
|
+
const hashAt = path.indexOf("#");
|
|
36929
|
+
const head = hashAt < 0 ? path : path.slice(0, hashAt);
|
|
36930
|
+
const fragment = hashAt < 0 ? "" : path.slice(hashAt);
|
|
36931
|
+
if (!head.includes("?"))
|
|
36932
|
+
return `${head}${query}${fragment}`;
|
|
36933
|
+
const sep = head.endsWith("?") || head.endsWith("&") ? "" : "&";
|
|
36934
|
+
return `${head}${sep}${query.slice(1)}${fragment}`;
|
|
36935
|
+
}
|
|
36834
36936
|
function cancelBody(res) {
|
|
36835
36937
|
try {
|
|
36836
36938
|
const body = res.body;
|
|
@@ -36959,7 +37061,9 @@ function createApiClient(opts) {
|
|
|
36959
37061
|
const retryStatuses = retry.statuses ?? [429];
|
|
36960
37062
|
const timeoutMs = opts.timeout === false ? 0 : opts.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
36961
37063
|
const redirectMode = opts.redirect;
|
|
36962
|
-
const
|
|
37064
|
+
const stripCrossOrigin = redirectMode === void 0 && opts.tokenHeader !== void 0;
|
|
37065
|
+
const manualRedirects = redirectMode === "same-origin" || stripCrossOrigin;
|
|
37066
|
+
const maxRedirects = opts.maxRedirects ?? (stripCrossOrigin ? FETCH_MAX_REDIRECTS : DEFAULT_MAX_REDIRECTS);
|
|
36963
37067
|
const reportWriteOutcome = opts.writeOutcomeUnknown !== false;
|
|
36964
37068
|
const defaultMaxBytes = checkMaxBytes("maxResponseBytes", opts.maxResponseBytes);
|
|
36965
37069
|
if (!Number.isSafeInteger(maxRedirects) || maxRedirects < 0) {
|
|
@@ -37061,7 +37165,7 @@ function createApiClient(opts) {
|
|
|
37061
37165
|
`Refusing request to ${service}: path ${JSON.stringify(path.split(/[?#]/, 1)[0])} contains a ${hazard}, which URL normalisation would rewrite. Build paths with apiPath\`...\` so each value is one encoded segment.`
|
|
37062
37166
|
);
|
|
37063
37167
|
}
|
|
37064
|
-
const url2 = `${base}${path
|
|
37168
|
+
const url2 = `${base}${joinQuery(path, query)}`;
|
|
37065
37169
|
if (baseOrigin === void 0)
|
|
37066
37170
|
return url2;
|
|
37067
37171
|
let parsed;
|
|
@@ -37075,14 +37179,21 @@ function createApiClient(opts) {
|
|
|
37075
37179
|
}
|
|
37076
37180
|
return url2;
|
|
37077
37181
|
}
|
|
37078
|
-
function
|
|
37079
|
-
let next;
|
|
37182
|
+
function resolveLocation(location, from) {
|
|
37080
37183
|
try {
|
|
37081
|
-
|
|
37184
|
+
return new URL(location, from).href;
|
|
37082
37185
|
} catch {
|
|
37083
37186
|
throw new RedirectRefusedError(`Refusing redirect from ${service}: the Location header is not a valid URL.`);
|
|
37084
37187
|
}
|
|
37085
|
-
|
|
37188
|
+
}
|
|
37189
|
+
function onBaseOrigin(href) {
|
|
37190
|
+
const next = new URL(href);
|
|
37191
|
+
return baseOrigin !== void 0 && next.origin === baseOrigin && next.username === baseParts?.username && next.password === baseParts?.password;
|
|
37192
|
+
}
|
|
37193
|
+
function sameOriginHop(location, from) {
|
|
37194
|
+
const href = resolveLocation(location, from);
|
|
37195
|
+
const next = new URL(href);
|
|
37196
|
+
if (!onBaseOrigin(href)) {
|
|
37086
37197
|
throw new RedirectRefusedError(`Refusing redirect from ${service} to ${next.origin}: it leaves the base origin ${baseOrigin ?? "(unparseable)"}, and following it would re-send the credential there.`);
|
|
37087
37198
|
}
|
|
37088
37199
|
return next.href;
|
|
@@ -37092,20 +37203,21 @@ function createApiClient(opts) {
|
|
|
37092
37203
|
const query = opt.query ? buildQueryString(opt.query) : "";
|
|
37093
37204
|
const url2 = resolveUrl(path, query);
|
|
37094
37205
|
const bodyInit = reqBody !== void 0 ? { body: reqBody } : {};
|
|
37095
|
-
const fetchHop = (hopUrl, hopMethod, withBody, token, signal) => {
|
|
37206
|
+
const fetchHop = (hopUrl, hopMethod, withBody, token, signal, credentialed = true) => {
|
|
37096
37207
|
if (!signal?.aborted)
|
|
37097
37208
|
dispatch.sent = true;
|
|
37209
|
+
const headers = {
|
|
37210
|
+
Accept: "application/json",
|
|
37211
|
+
...withBody && contentType !== void 0 ? { "Content-Type": contentType } : {},
|
|
37212
|
+
...opts.baseHeaders,
|
|
37213
|
+
...authHeader(token || void 0),
|
|
37214
|
+
...opt.headers
|
|
37215
|
+
};
|
|
37098
37216
|
return doFetch(hopUrl, {
|
|
37099
37217
|
method: hopMethod,
|
|
37100
|
-
headers:
|
|
37101
|
-
Accept: "application/json",
|
|
37102
|
-
...withBody && contentType !== void 0 ? { "Content-Type": contentType } : {},
|
|
37103
|
-
...opts.baseHeaders,
|
|
37104
|
-
...authHeader(token || void 0),
|
|
37105
|
-
...opt.headers
|
|
37106
|
-
},
|
|
37218
|
+
headers: credentialed ? headers : withoutCredentials(headers, opts.tokenHeader),
|
|
37107
37219
|
...signal ? { signal } : {},
|
|
37108
|
-
...
|
|
37220
|
+
...manualRedirects ? { redirect: "manual" } : redirectMode !== void 0 ? { redirect: redirectMode } : {},
|
|
37109
37221
|
...withBody ? bodyInit : {}
|
|
37110
37222
|
}).catch((err) => {
|
|
37111
37223
|
markTransportFailure(err);
|
|
@@ -37113,13 +37225,14 @@ function createApiClient(opts) {
|
|
|
37113
37225
|
});
|
|
37114
37226
|
};
|
|
37115
37227
|
const fetchWith = async (token, signal) => {
|
|
37116
|
-
if (
|
|
37228
|
+
if (!manualRedirects)
|
|
37117
37229
|
return fetchHop(url2, method, true, token, signal);
|
|
37118
37230
|
let hopUrl = url2;
|
|
37119
37231
|
let hopMethod = method;
|
|
37120
37232
|
let withBody = true;
|
|
37233
|
+
let credentialed = true;
|
|
37121
37234
|
for (let hops = 0; ; hops += 1) {
|
|
37122
|
-
const res = await fetchHop(hopUrl, hopMethod, withBody, token, signal);
|
|
37235
|
+
const res = await fetchHop(hopUrl, hopMethod, withBody, token, signal, credentialed);
|
|
37123
37236
|
const location = REDIRECT_STATUSES.has(res.status) ? responseHeader(res, "location") : void 0;
|
|
37124
37237
|
if (location === void 0 || location === null)
|
|
37125
37238
|
return res;
|
|
@@ -37127,7 +37240,13 @@ function createApiClient(opts) {
|
|
|
37127
37240
|
if (hops >= maxRedirects) {
|
|
37128
37241
|
throw new RedirectRefusedError(`Refusing request to ${service}: too many redirects (more than ${maxRedirects}).`);
|
|
37129
37242
|
}
|
|
37130
|
-
|
|
37243
|
+
if (stripCrossOrigin) {
|
|
37244
|
+
hopUrl = resolveLocation(location, hopUrl);
|
|
37245
|
+
if (credentialed && !onBaseOrigin(hopUrl))
|
|
37246
|
+
credentialed = false;
|
|
37247
|
+
} else {
|
|
37248
|
+
hopUrl = sameOriginHop(location, hopUrl);
|
|
37249
|
+
}
|
|
37131
37250
|
if (res.status === 303 || (res.status === 301 || res.status === 302) && hopMethod === "POST") {
|
|
37132
37251
|
if (hopMethod !== "HEAD")
|
|
37133
37252
|
hopMethod = "GET";
|
|
@@ -37331,7 +37450,7 @@ function decodeJwtExp(token) {
|
|
|
37331
37450
|
}
|
|
37332
37451
|
return exp;
|
|
37333
37452
|
}
|
|
37334
|
-
var DEFAULT_RETRY, defaultSleep, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_MAX_REDIRECTS, REDIRECT_STATUSES, UnauthorizedError, RateLimitedError, RequestTimeoutError, DEFAULT_WRITE_OUTCOME_HINT, WriteOutcomeUnknownError, SAFE_METHODS, ApiError, EdgeBlockedError, EDGE_BLOCK_SCAN_MAX, EDGE_BLOCK_MARKERS;
|
|
37453
|
+
var DEFAULT_RETRY, defaultSleep, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_MAX_REDIRECTS, FETCH_MAX_REDIRECTS, CROSS_ORIGIN_DROPPED_HEADERS, REDIRECT_STATUSES, UnauthorizedError, RateLimitedError, RequestTimeoutError, DEFAULT_WRITE_OUTCOME_HINT, WriteOutcomeUnknownError, SAFE_METHODS, ApiError, EdgeBlockedError, EDGE_BLOCK_SCAN_MAX, EDGE_BLOCK_MARKERS;
|
|
37335
37454
|
var init_http = __esm({
|
|
37336
37455
|
"node_modules/@chrischall/mcp-utils/dist/http/index.js"() {
|
|
37337
37456
|
init_headers();
|
|
@@ -37351,6 +37470,8 @@ var init_http = __esm({
|
|
|
37351
37470
|
defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
37352
37471
|
DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
37353
37472
|
DEFAULT_MAX_REDIRECTS = 5;
|
|
37473
|
+
FETCH_MAX_REDIRECTS = 20;
|
|
37474
|
+
CROSS_ORIGIN_DROPPED_HEADERS = ["authorization", "cookie", "proxy-authorization"];
|
|
37354
37475
|
REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
37355
37476
|
UnauthorizedError = class extends Error {
|
|
37356
37477
|
status = 401;
|
|
@@ -37588,7 +37709,7 @@ var init_version = __esm({
|
|
|
37588
37709
|
"src/version.ts"() {
|
|
37589
37710
|
"use strict";
|
|
37590
37711
|
PACKAGE_NAME = "office-outlook-mcp";
|
|
37591
|
-
VERSION = "0.6.
|
|
37712
|
+
VERSION = "0.6.1";
|
|
37592
37713
|
}
|
|
37593
37714
|
});
|
|
37594
37715
|
|
|
@@ -38143,6 +38264,21 @@ var init_public_suffix = __esm({
|
|
|
38143
38264
|
}
|
|
38144
38265
|
});
|
|
38145
38266
|
|
|
38267
|
+
// node_modules/@fetchproxy/protocol/dist/ip-literal.js
|
|
38268
|
+
function isIpLiteralHost(host) {
|
|
38269
|
+
if (host.includes(":") || host.startsWith("["))
|
|
38270
|
+
return true;
|
|
38271
|
+
const labels = host.split(".");
|
|
38272
|
+
if (labels.length > 1 && labels[labels.length - 1] === "")
|
|
38273
|
+
labels.pop();
|
|
38274
|
+
const last = labels[labels.length - 1] ?? "";
|
|
38275
|
+
return /^[0-9]+$/.test(last) || /^0x[0-9a-f]*$/i.test(last);
|
|
38276
|
+
}
|
|
38277
|
+
var init_ip_literal = __esm({
|
|
38278
|
+
"node_modules/@fetchproxy/protocol/dist/ip-literal.js"() {
|
|
38279
|
+
}
|
|
38280
|
+
});
|
|
38281
|
+
|
|
38146
38282
|
// node_modules/@fetchproxy/protocol/dist/validate.js
|
|
38147
38283
|
function assertObject(x, label) {
|
|
38148
38284
|
if (typeof x !== "object" || x === null || Array.isArray(x)) {
|
|
@@ -38592,6 +38728,9 @@ function validateHello(raw) {
|
|
|
38592
38728
|
if (!HOSTNAME_RE.test(d)) {
|
|
38593
38729
|
throw new ProtocolError3(`hello.domains: invalid hostname ${JSON.stringify(d)}`);
|
|
38594
38730
|
}
|
|
38731
|
+
if (isIpLiteralHost(d)) {
|
|
38732
|
+
throw new ProtocolError3(`hello.domains: ${JSON.stringify(d)} is an IP address \u2014 declare the site's DNS hostname; IP-literal origins (LAN, loopback, link-local) are not reachable through the bridge`);
|
|
38733
|
+
}
|
|
38595
38734
|
if (isPublicSuffix(d)) {
|
|
38596
38735
|
throw new ProtocolError3(`hello.domains: ${JSON.stringify(d)} is a public suffix \u2014 declare a registrable domain (e.g. "example.${d}"), not the suffix every site under it shares`);
|
|
38597
38736
|
}
|
|
@@ -39365,6 +39504,7 @@ var init_validate = __esm({
|
|
|
39365
39504
|
init_account_attest();
|
|
39366
39505
|
init_json_pointer();
|
|
39367
39506
|
init_public_suffix();
|
|
39507
|
+
init_ip_literal();
|
|
39368
39508
|
ProtocolError3 = class extends Error {
|
|
39369
39509
|
constructor(message) {
|
|
39370
39510
|
super(message);
|
|
@@ -39544,6 +39684,7 @@ var init_dist3 = __esm({
|
|
|
39544
39684
|
init_seal();
|
|
39545
39685
|
init_encoding();
|
|
39546
39686
|
init_public_suffix();
|
|
39687
|
+
init_ip_literal();
|
|
39547
39688
|
init_json_pointer();
|
|
39548
39689
|
}
|
|
39549
39690
|
});
|
|
@@ -44514,7 +44655,7 @@ async function startHost(opts) {
|
|
|
44514
44655
|
if (ownRetryableRefusal)
|
|
44515
44656
|
resetSessionPromise();
|
|
44516
44657
|
resolveOwnSession(ownSession);
|
|
44517
|
-
} else {
|
|
44658
|
+
} else if (identified === "extension" && extensionWs === ws) {
|
|
44518
44659
|
const slot = peers.get(frame.mcpId);
|
|
44519
44660
|
if (slot)
|
|
44520
44661
|
slot.ws.send(JSON.stringify(frame));
|
|
@@ -45602,6 +45743,9 @@ var init_ws_server = __esm({
|
|
|
45602
45743
|
throw new Error("FetchproxyServer: opts.domains must be a non-empty array of hostnames");
|
|
45603
45744
|
}
|
|
45604
45745
|
for (const d of opts.domains) {
|
|
45746
|
+
if (typeof d === "string" && isIpLiteralHost(d)) {
|
|
45747
|
+
throw new Error(`FetchproxyServer: opts.domains entry ${JSON.stringify(d)} is an IP address \u2014 declare the site's DNS hostname; IP-literal origins (LAN, loopback, link-local) are not reachable through the bridge`);
|
|
45748
|
+
}
|
|
45605
45749
|
if (typeof d === "string" && isPublicSuffix(d)) {
|
|
45606
45750
|
throw new Error(`FetchproxyServer: opts.domains entry ${JSON.stringify(d)} is a public suffix \u2014 declare a registrable domain (e.g. "example.${d}"), not the suffix every site under it shares`);
|
|
45607
45751
|
}
|
|
@@ -47547,8 +47691,10 @@ var init_bulk = __esm({
|
|
|
47547
47691
|
});
|
|
47548
47692
|
|
|
47549
47693
|
// node_modules/@fetchproxy/server/dist/bot-wall.js
|
|
47694
|
+
var CLOUDFLARE_TITLE_SCAN_MAX;
|
|
47550
47695
|
var init_bot_wall = __esm({
|
|
47551
47696
|
"node_modules/@fetchproxy/server/dist/bot-wall.js"() {
|
|
47697
|
+
CLOUDFLARE_TITLE_SCAN_MAX = 64 * 1024;
|
|
47552
47698
|
}
|
|
47553
47699
|
});
|
|
47554
47700
|
|
|
@@ -49928,6 +50074,9 @@ function messageBody(subject, body, html) {
|
|
|
49928
50074
|
}
|
|
49929
50075
|
var TEAMS = { IsOnlineMeeting: true, OnlineMeetingProvider: "TeamsForBusiness" };
|
|
49930
50076
|
var teamsMeetingParam = external_exports.boolean().optional().describe("Attach a Microsoft Teams meeting (default true). false leaves it off.");
|
|
50077
|
+
var addTeamsMeetingParam = external_exports.boolean().optional().describe(
|
|
50078
|
+
"true adds a Microsoft Teams meeting to an event that lacks one (default false: the way people join is left as it is)."
|
|
50079
|
+
);
|
|
49931
50080
|
var EVENT_STATE_SELECT = "Id,Subject,IsOrganizer,Start,End,Location,Attendees,IsOnlineMeeting,OnlineMeetingProvider,OnlineMeeting";
|
|
49932
50081
|
function attendee(Address, Type) {
|
|
49933
50082
|
return { Type, EmailAddress: { Address } };
|
|
@@ -50077,39 +50226,30 @@ function registerWriteTools(server, client2) {
|
|
|
50077
50226
|
server.registerTool(
|
|
50078
50227
|
"outlook_create_draft",
|
|
50079
50228
|
{
|
|
50080
|
-
description: "Create a draft message in the Drafts folder without sending it. Returns the created draft, which can be reviewed and sent from Outlook. " +
|
|
50081
|
-
|
|
50229
|
+
description: "Create a draft message in the Drafts folder without sending it. Returns the created draft, which can be reviewed and sent from Outlook. It does not ask to confirm: a draft reaches nobody until it is sent from Outlook. Calling it twice leaves two drafts. " + TIMEOUT_SENTENCE,
|
|
50230
|
+
// Self-only and additive, like outlook_reply with draftOnly: nothing is
|
|
50231
|
+
// overwritten or sent. Not idempotent: a repeat creates a second draft.
|
|
50232
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
50082
50233
|
inputSchema: external_exports.object({
|
|
50083
50234
|
to: recipientList,
|
|
50084
50235
|
cc: recipientList,
|
|
50085
50236
|
subject: external_exports.string().describe("Subject line"),
|
|
50086
50237
|
body: external_exports.string().describe("Message body"),
|
|
50087
|
-
html: external_exports.boolean().optional().describe("Compose the body as HTML (default: plain text)")
|
|
50088
|
-
confirmToken: confirmTokenParam
|
|
50238
|
+
html: external_exports.boolean().optional().describe("Compose the body as HTML (default: plain text)")
|
|
50089
50239
|
})
|
|
50090
50240
|
},
|
|
50091
|
-
async ({ to, cc, subject, body, html
|
|
50241
|
+
async ({ to, cc, subject, body, html }) => {
|
|
50092
50242
|
const payload = {
|
|
50093
50243
|
...messageBody(subject, body, html),
|
|
50094
50244
|
ToRecipients: toRecipients(to),
|
|
50095
50245
|
...cc?.length ? { CcRecipients: toRecipients(cc) } : {}
|
|
50096
50246
|
};
|
|
50097
|
-
const
|
|
50098
|
-
|
|
50099
|
-
|
|
50100
|
-
message: "Review and confirm this draft:",
|
|
50101
|
-
summary: `Create draft "${subject}"`,
|
|
50102
|
-
// One signed-in mailbox per server process.
|
|
50103
|
-
account: void 0,
|
|
50104
|
-
request: { method: "POST", path: "/me/messages", body: payload },
|
|
50105
|
-
confirmToken
|
|
50106
|
-
});
|
|
50107
|
-
if (gate) return gate;
|
|
50108
|
-
const created = await client2.write(
|
|
50109
|
-
"POST",
|
|
50110
|
-
"/me/messages",
|
|
50111
|
-
payload
|
|
50247
|
+
const create = await writeOrUnknown(
|
|
50248
|
+
() => client2.write("POST", "/me/messages", payload),
|
|
50249
|
+
"Drafts"
|
|
50112
50250
|
);
|
|
50251
|
+
if (!create.ok) return create.result;
|
|
50252
|
+
const created = create.value;
|
|
50113
50253
|
return minifiedResult({ created: true, Id: created?.Id, WebLink: created?.WebLink });
|
|
50114
50254
|
}
|
|
50115
50255
|
);
|
|
@@ -50326,7 +50466,7 @@ function registerWriteTools(server, client2) {
|
|
|
50326
50466
|
server.registerTool(
|
|
50327
50467
|
"outlook_update_event",
|
|
50328
50468
|
{
|
|
50329
|
-
description: "Update a meeting you organize: retitle, move, relocate, edit the description, or add/remove attendees. Moving only `start` keeps the meeting's length.
|
|
50469
|
+
description: "Update a meeting you organize: retitle, move, relocate, edit the description, or add/remove attendees. Moving only `start` keeps the meeting's length. It does not add a Microsoft Teams meeting unless you pass `teamsMeeting: true` (on an event that lacks one). Attendees are sent an updated invitation. Only the organizer can update a meeting \u2014 an attendee's edit would change their own copy alone, so it is refused. The result is verified by re-reading the event. " + CONFIRM_FLOW_SENTENCE + " " + CONFIRM_INJECTION_RULE,
|
|
50330
50470
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
50331
50471
|
inputSchema: external_exports.object({
|
|
50332
50472
|
id: external_exports.string().min(1).describe("Event Id from outlook_list_events or outlook_get_event"),
|
|
@@ -50339,7 +50479,7 @@ function registerWriteTools(server, client2) {
|
|
|
50339
50479
|
addAttendees: recipientList.describe("Required attendees to add"),
|
|
50340
50480
|
addOptionalAttendees: recipientList.describe("Optional attendees to add"),
|
|
50341
50481
|
removeAttendees: recipientList.describe("Attendees to remove"),
|
|
50342
|
-
teamsMeeting:
|
|
50482
|
+
teamsMeeting: addTeamsMeetingParam,
|
|
50343
50483
|
confirmToken: confirmTokenParam
|
|
50344
50484
|
})
|
|
50345
50485
|
},
|
|
@@ -50392,18 +50532,21 @@ function registerWriteTools(server, client2) {
|
|
|
50392
50532
|
}
|
|
50393
50533
|
payload.Attendees = next;
|
|
50394
50534
|
}
|
|
50395
|
-
const addTeams = args.teamsMeeting
|
|
50535
|
+
const addTeams = args.teamsMeeting === true && !(current && isTeams(current));
|
|
50396
50536
|
if (addTeams) Object.assign(payload, TEAMS);
|
|
50397
50537
|
if (Object.keys(payload).length === 0) {
|
|
50398
50538
|
throw new McpToolError("Nothing to update.", {
|
|
50399
|
-
hint: "Pass at least one of subject, start, end, location, body, or
|
|
50539
|
+
hint: "Pass at least one of subject, start, end, location, body, attendee changes, or teamsMeeting: true."
|
|
50400
50540
|
});
|
|
50401
50541
|
}
|
|
50402
50542
|
const gate = await confirmWrite(ctx, {
|
|
50403
50543
|
tool: "outlook_update_event",
|
|
50404
50544
|
action: "calendar.update_event",
|
|
50405
|
-
message: "Review and confirm this change (attendees are sent an updated invitation):",
|
|
50406
|
-
summary: `Update "${current?.Subject ?? args.id}": ${
|
|
50545
|
+
message: addTeams ? "Review and confirm this change (attendees are sent an updated invitation). This will also make it a Microsoft Teams meeting: attendees get a Teams join link." : "Review and confirm this change (attendees are sent an updated invitation):",
|
|
50546
|
+
summary: `Update "${current?.Subject ?? args.id}": ${[
|
|
50547
|
+
Object.keys(payload).filter((k) => !(k in TEAMS)).join(", "),
|
|
50548
|
+
addTeams ? "this will also make it a Microsoft Teams meeting" : ""
|
|
50549
|
+
].filter((part) => part !== "").join("; ")}`,
|
|
50407
50550
|
// One signed-in mailbox per server process.
|
|
50408
50551
|
account: void 0,
|
|
50409
50552
|
target: args.id,
|
package/dist/tools/writes.js
CHANGED
|
@@ -31,6 +31,14 @@ const teamsMeetingParam = z
|
|
|
31
31
|
.boolean()
|
|
32
32
|
.optional()
|
|
33
33
|
.describe('Attach a Microsoft Teams meeting (default true). false leaves it off.');
|
|
34
|
+
/**
|
|
35
|
+
* On update the Teams meeting is opt-in: an edit to an in-person meeting (or one
|
|
36
|
+
* on another provider) must not change how people join it unless asked.
|
|
37
|
+
*/
|
|
38
|
+
const addTeamsMeetingParam = z
|
|
39
|
+
.boolean()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('true adds a Microsoft Teams meeting to an event that lacks one (default false: the way people join is left as it is).');
|
|
34
42
|
const EVENT_STATE_SELECT = 'Id,Subject,IsOrganizer,Start,End,Location,Attendees,IsOnlineMeeting,OnlineMeetingProvider,OnlineMeeting';
|
|
35
43
|
function attendee(Address, Type) {
|
|
36
44
|
return { Type, EmailAddress: { Address } };
|
|
@@ -242,37 +250,32 @@ export function registerWriteTools(server, client) {
|
|
|
242
250
|
return minifiedResult({ sent: true, subject, recipients: recipients || null });
|
|
243
251
|
});
|
|
244
252
|
server.registerTool('outlook_create_draft', {
|
|
245
|
-
description: 'Create a draft message in the Drafts folder without sending it. Returns the created draft, which can be reviewed and sent from Outlook.' +
|
|
246
|
-
' ' +
|
|
247
|
-
|
|
248
|
-
|
|
253
|
+
description: 'Create a draft message in the Drafts folder without sending it. Returns the created draft, which can be reviewed and sent from Outlook. ' +
|
|
254
|
+
'It does not ask to confirm: a draft reaches nobody until it is sent from Outlook. Calling it twice leaves two drafts. ' +
|
|
255
|
+
TIMEOUT_SENTENCE,
|
|
256
|
+
// Self-only and additive, like outlook_reply with draftOnly: nothing is
|
|
257
|
+
// overwritten or sent. Not idempotent: a repeat creates a second draft.
|
|
258
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
249
259
|
inputSchema: z.object({
|
|
250
260
|
to: recipientList,
|
|
251
261
|
cc: recipientList,
|
|
252
262
|
subject: z.string().describe('Subject line'),
|
|
253
263
|
body: z.string().describe('Message body'),
|
|
254
264
|
html: z.boolean().optional().describe('Compose the body as HTML (default: plain text)'),
|
|
255
|
-
confirmToken: confirmTokenParam,
|
|
256
265
|
}),
|
|
257
|
-
}, async ({ to, cc, subject, body, html
|
|
266
|
+
}, async ({ to, cc, subject, body, html }) => {
|
|
258
267
|
const payload = {
|
|
259
268
|
...messageBody(subject, body, html),
|
|
260
269
|
ToRecipients: toRecipients(to),
|
|
261
270
|
...(cc?.length ? { CcRecipients: toRecipients(cc) } : {}),
|
|
262
271
|
};
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
request: { method: 'POST', path: '/me/messages', body: payload },
|
|
271
|
-
confirmToken,
|
|
272
|
-
});
|
|
273
|
-
if (gate)
|
|
274
|
-
return gate;
|
|
275
|
-
const created = await client.write('POST', '/me/messages', payload);
|
|
272
|
+
// Nothing leaves the mailbox, so there is no one to protect with a
|
|
273
|
+
// confirmation (same rule as outlook_reply draftOnly). A lost answer
|
|
274
|
+
// says to look in Drafts before retrying, so a retry does not duplicate.
|
|
275
|
+
const create = await writeOrUnknown(() => client.write('POST', '/me/messages', payload), 'Drafts');
|
|
276
|
+
if (!create.ok)
|
|
277
|
+
return create.result;
|
|
278
|
+
const created = create.value;
|
|
276
279
|
return minifiedResult({ created: true, Id: created?.Id, WebLink: created?.WebLink });
|
|
277
280
|
});
|
|
278
281
|
server.registerTool('outlook_mark_read', {
|
|
@@ -507,7 +510,7 @@ export function registerWriteTools(server, client) {
|
|
|
507
510
|
});
|
|
508
511
|
});
|
|
509
512
|
server.registerTool('outlook_update_event', {
|
|
510
|
-
description: 'Update a meeting you organize: retitle, move, relocate, edit the description, or add/remove attendees. Moving only `start` keeps the meeting\'s length.
|
|
513
|
+
description: 'Update a meeting you organize: retitle, move, relocate, edit the description, or add/remove attendees. Moving only `start` keeps the meeting\'s length. It does not add a Microsoft Teams meeting unless you pass `teamsMeeting: true` (on an event that lacks one). Attendees are sent an updated invitation. Only the organizer can update a meeting — an attendee\'s edit would change their own copy alone, so it is refused. The result is verified by re-reading the event.' +
|
|
511
514
|
' ' +
|
|
512
515
|
CONFIRM_FLOW_SENTENCE +
|
|
513
516
|
' ' +
|
|
@@ -530,7 +533,7 @@ export function registerWriteTools(server, client) {
|
|
|
530
533
|
addAttendees: recipientList.describe('Required attendees to add'),
|
|
531
534
|
addOptionalAttendees: recipientList.describe('Optional attendees to add'),
|
|
532
535
|
removeAttendees: recipientList.describe('Attendees to remove'),
|
|
533
|
-
teamsMeeting:
|
|
536
|
+
teamsMeeting: addTeamsMeetingParam,
|
|
534
537
|
confirmToken: confirmTokenParam,
|
|
535
538
|
}),
|
|
536
539
|
}, async (args, ctx) => {
|
|
@@ -603,19 +606,26 @@ export function registerWriteTools(server, client) {
|
|
|
603
606
|
}
|
|
604
607
|
payload.Attendees = next;
|
|
605
608
|
}
|
|
606
|
-
const addTeams = args.teamsMeeting
|
|
609
|
+
const addTeams = args.teamsMeeting === true && !(current && isTeams(current));
|
|
607
610
|
if (addTeams)
|
|
608
611
|
Object.assign(payload, TEAMS);
|
|
609
612
|
if (Object.keys(payload).length === 0) {
|
|
610
613
|
throw new McpToolError('Nothing to update.', {
|
|
611
|
-
hint: 'Pass at least one of subject, start, end, location, body, or
|
|
614
|
+
hint: 'Pass at least one of subject, start, end, location, body, attendee changes, or teamsMeeting: true.',
|
|
612
615
|
});
|
|
613
616
|
}
|
|
614
617
|
const gate = await confirmWrite(ctx, {
|
|
615
618
|
tool: 'outlook_update_event',
|
|
616
619
|
action: 'calendar.update_event',
|
|
617
|
-
message:
|
|
618
|
-
|
|
620
|
+
message: addTeams
|
|
621
|
+
? 'Review and confirm this change (attendees are sent an updated invitation). This will also make it a Microsoft Teams meeting: attendees get a Teams join link.'
|
|
622
|
+
: 'Review and confirm this change (attendees are sent an updated invitation):',
|
|
623
|
+
summary: `Update "${current?.Subject ?? args.id}": ${[
|
|
624
|
+
Object.keys(payload).filter((k) => !(k in TEAMS)).join(', '),
|
|
625
|
+
addTeams ? 'this will also make it a Microsoft Teams meeting' : '',
|
|
626
|
+
]
|
|
627
|
+
.filter((part) => part !== '')
|
|
628
|
+
.join('; ')}`,
|
|
619
629
|
// One signed-in mailbox per server process.
|
|
620
630
|
account: undefined,
|
|
621
631
|
target: args.id,
|
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.6.
|
|
3
|
+
"version": "0.6.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>",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"typecheck": "tsc -p tsconfig.check.json"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@chrischall/mcp-utils": "^3.0.
|
|
49
|
-
"@fetchproxy/server": "^3.6.
|
|
48
|
+
"@chrischall/mcp-utils": "^3.0.3",
|
|
49
|
+
"@fetchproxy/server": "^3.6.2",
|
|
50
50
|
"@modelcontextprotocol/server": "^2.3.0",
|
|
51
51
|
"dotenv": "^18.0.1",
|
|
52
52
|
"zod": "^4.6.5"
|
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.
|
|
9
|
+
"version": "0.6.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/office-outlook-mcp",
|
|
14
|
-
"version": "0.6.
|
|
14
|
+
"version": "0.6.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|