@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.
@@ -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.0"
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.0",
18
+ "version": "0.6.1",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "mcpServers": {
3
3
  "outlook": {
4
- "command": "node",
5
- "args": ["${CLAUDE_PLUGIN_ROOT}/dist/bundle.js"],
4
+ "command": "npx",
5
+ "args": ["-y", "@chrischall/office-outlook-mcp"],
6
6
  "env": {
7
7
  "OUTLOOK_ACCESS_TOKEN": "${OUTLOOK_ACCESS_TOKEN}"
8
8
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "office-outlook-mcp",
3
3
  "displayName": "Outlook (Microsoft 365)",
4
- "version": "0.6.0",
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
- Three skip the confirmation when nothing reaches anyone else:
123
- `outlook_reply` with `draftOnly: true` (the reply stays in Drafts),
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` and
133
- `outlook_update_event` attach a Microsoft Teams meeting by default and return
134
- its join link; pass `teamsMeeting: false` to leave it off. Only the organizer
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 [Tools](#tools)). A client
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: 5
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(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
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 /_cf_chl_opt/.test(html) || /<title[^>]*>\s*Just a moment/i.test(html);
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.headers.get("content-length") ?? NaN);
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 maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
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}${query}`;
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 sameOriginHop(location, from) {
37079
- let next;
37182
+ function resolveLocation(location, from) {
37080
37183
  try {
37081
- next = new URL(location, from);
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
- if (baseOrigin === void 0 || next.origin !== baseOrigin || next.username !== baseParts?.username || next.password !== baseParts?.password) {
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
- ...redirectMode !== void 0 ? { redirect: redirectMode === "same-origin" ? "manual" : redirectMode } : {},
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 (redirectMode !== "same-origin")
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
- hopUrl = sameOriginHop(location, hopUrl);
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.0";
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. " + CONFIRM_FLOW_SENTENCE,
50081
- annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
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, confirmToken }, ctx) => {
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 gate = await confirmWrite(ctx, {
50098
- tool: "outlook_create_draft",
50099
- action: "mail.create_draft",
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. A Microsoft Teams meeting is added if the event lacks one (`teamsMeeting: false` to skip). 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,
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: teamsMeetingParam,
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 !== false && !(current && isTeams(current));
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 attendee changes."
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}": ${Object.keys(payload).join(", ")}`,
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,
@@ -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
- CONFIRM_FLOW_SENTENCE,
248
- annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
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, confirmToken }, ctx) => {
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
- const gate = await confirmWrite(ctx, {
264
- tool: 'outlook_create_draft',
265
- action: 'mail.create_draft',
266
- message: 'Review and confirm this draft:',
267
- summary: `Create draft "${subject}"`,
268
- // One signed-in mailbox per server process.
269
- account: undefined,
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. A Microsoft Teams meeting is added if the event lacks one (`teamsMeeting: false` to skip). 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.' +
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: teamsMeetingParam,
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 !== false && !(current && isTeams(current));
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 attendee changes.',
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: 'Review and confirm this change (attendees are sent an updated invitation):',
618
- summary: `Update "${current?.Subject ?? args.id}": ${Object.keys(payload).join(', ')}`,
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
@@ -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.0'; // x-release-please-version
13
+ export const VERSION = '0.6.1'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrischall/office-outlook-mcp",
3
- "version": "0.6.0",
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.1",
49
- "@fetchproxy/server": "^3.6.0",
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.0",
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.0",
14
+ "version": "0.6.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },