@chrischall/office-outlook-mcp 0.5.2 → 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/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.5.2";
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
 
@@ -49028,6 +49174,7 @@ function isMeetingMessage(m) {
49028
49174
 
49029
49175
  // src/tools/_paging.ts
49030
49176
  init_zod();
49177
+ init_dist2();
49031
49178
  var nextLinkParam = external_exports.string().min(1).optional().describe(
49032
49179
  "Continue a previous listing: pass its `nextLink` value unchanged to fetch the next page. When set, the paging/filter arguments are taken from the link and ignored here."
49033
49180
  );
@@ -49043,6 +49190,71 @@ function plainCollection(data) {
49043
49190
  if (next) out.nextLink = next;
49044
49191
  return out;
49045
49192
  }
49193
+ var fromParam = external_exports.string().min(1).optional().describe(
49194
+ 'Page oldest first from this point: an ISO 8601 UTC time (e.g. "2026-10-01T00:00:00Z"; one with no zone is read as UTC), or a previous result\'s `next_from` passed unchanged. The result always carries `next_from` and `has_more`.'
49195
+ );
49196
+ var MAX_TOP = 1e3;
49197
+ var FROM_HINT = "Pass a previous next_from unchanged, or an ISO 8601 time such as 2026-10-01T00:00:00Z (one with no zone is read as UTC).";
49198
+ var ISO_DATE_TIME = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/i;
49199
+ function utcLiteral(ms) {
49200
+ return new Date(ms).toISOString().replace(/\.000Z$/, "Z");
49201
+ }
49202
+ function parseTime(raw) {
49203
+ if (!ISO_DATE_TIME.test(raw)) return void 0;
49204
+ const t = /T[\d:.]+$/.test(raw) ? `${raw}Z` : raw;
49205
+ const ms = Date.parse(t);
49206
+ if (Number.isNaN(ms)) return void 0;
49207
+ if (new Date(ms + offsetMs(t)).toISOString().slice(0, 10) !== t.slice(0, 10)) return void 0;
49208
+ return ms;
49209
+ }
49210
+ function offsetMs(t) {
49211
+ const m = /([+-])(\d{2}):?(\d{2})$/.exec(t);
49212
+ if (!m || /Z$/i.test(t)) return 0;
49213
+ const mins = Number(m[2]) * 60 + Number(m[3]);
49214
+ return (m[1] === "-" ? -1 : 1) * mins * 6e4;
49215
+ }
49216
+ function encodeFrom(pos) {
49217
+ return Buffer.from(JSON.stringify({ t: pos.t, ids: pos.ids }), "utf8").toString("base64url");
49218
+ }
49219
+ function decodeFrom(from) {
49220
+ if (/^\d{4}-/.test(from)) {
49221
+ const ms2 = parseTime(from);
49222
+ if (ms2 === void 0) {
49223
+ throw new McpToolError(`\`from\` is not a valid ISO 8601 UTC date-time: ${from}`, { hint: FROM_HINT });
49224
+ }
49225
+ return { t: utcLiteral(ms2), ids: [] };
49226
+ }
49227
+ let parsed;
49228
+ try {
49229
+ parsed = /^[A-Za-z0-9_-]+$/.test(from) ? JSON.parse(Buffer.from(from, "base64url").toString("utf8")) : void 0;
49230
+ } catch {
49231
+ parsed = void 0;
49232
+ }
49233
+ const p = parsed;
49234
+ const ms = typeof p?.t === "string" ? parseTime(p.t) : void 0;
49235
+ if (ms === void 0 || !Array.isArray(p?.ids) || !p.ids.every((id) => typeof id === "string")) {
49236
+ throw new McpToolError("`from` is neither an ISO 8601 time nor a next_from token.", { hint: FROM_HINT });
49237
+ }
49238
+ return { t: utcLiteral(ms), ids: p.ids };
49239
+ }
49240
+ function fromQuery(pos, limit) {
49241
+ return { bound: `ReceivedDateTime ge ${pos.t}`, top: Math.min(limit + pos.ids.length, MAX_TOP) };
49242
+ }
49243
+ function fromPage(rows, pos, limit, apiHasMore) {
49244
+ const seen = new Set(pos.ids);
49245
+ const fresh = rows.filter((r) => r.Id === void 0 || !seen.has(r.Id));
49246
+ const kept = fresh.slice(0, limit);
49247
+ const next = nextPosition(kept, pos);
49248
+ return { rows: kept, next_from: encodeFrom(next), has_more: fresh.length > limit || apiHasMore };
49249
+ }
49250
+ function nextPosition(rows, pos) {
49251
+ const dated = rows.map((r) => ({ id: r.Id, ms: r.ReceivedDateTime ? Date.parse(r.ReceivedDateTime) : NaN })).filter((r) => r.id !== void 0 && !Number.isNaN(r.ms));
49252
+ if (dated.length === 0) return pos;
49253
+ const newest = Math.max(...dated.map((r) => r.ms));
49254
+ const tied = dated.filter((r) => r.ms === newest).map((r) => r.id);
49255
+ const carried = newest === Date.parse(pos.t) ? pos.ids.filter((id) => !tied.includes(id)) : [];
49256
+ return { t: utcLiteral(newest), ids: [...carried, ...tied] };
49257
+ }
49046
49258
 
49047
49259
  // src/tools/_untrusted.ts
49048
49260
  init_dist2();
@@ -49152,6 +49364,37 @@ function utf8Boundary(buf, max) {
49152
49364
  function sinceLiteral(hours) {
49153
49365
  return new Date(Date.now() - hours * 36e5).toISOString().replace(/\.\d{3}Z$/, "Z");
49154
49366
  }
49367
+ function fromPosition(from, conflicts) {
49368
+ if (from === void 0) return void 0;
49369
+ for (const [name, value] of Object.entries(conflicts)) {
49370
+ if (value === void 0) continue;
49371
+ throw new McpToolError(`\`from\` and \`${name}\` cannot be combined.`, {
49372
+ hint: name === "nextLink" ? "To continue a `from` listing, pass its `next_from` as `from`; `nextLink` belongs to the newest-first listing." : `\`from\` pages oldest first by time; drop \`${name}\`, or omit \`from\` for the newest-first listing.`
49373
+ });
49374
+ }
49375
+ return decodeFrom(from);
49376
+ }
49377
+ var FROM_DESCRIPTION = "Pass `from` (an ISO 8601 UTC time, or a previous `next_from`) to page OLDEST first from that time instead: the result carries `next_from` (pass it back unchanged as `from` for the next page; on an empty page it is the same position, so it can be polled for new arrivals) and `has_more` (more already waiting), and no nextLink. Backfill: pass an old `from` and keep passing `next_from` until `has_more` is false; then keep polling with the last `next_from` for new mail.";
49378
+ async function listFrom(client2, pos, opts) {
49379
+ const { bound, top } = fromQuery(pos, opts.limit);
49380
+ const data = await client2.get(
49381
+ `/me/mailfolders/${folderSegment(opts.folder ?? "inbox")}/messages`,
49382
+ {
49383
+ query: {
49384
+ $top: top,
49385
+ $select: LIST_SELECT,
49386
+ // The date bound leads: Outlook rejects a $filter that does not start with the $orderby property.
49387
+ $filter: opts.unreadOnly === true ? `${bound} and IsRead eq false` : bound,
49388
+ $orderby: "ReceivedDateTime asc"
49389
+ }
49390
+ }
49391
+ );
49392
+ const listed = Array.isArray(data?.value) ? data.value : [];
49393
+ const page = fromPage(listed, pos, opts.limit, Boolean(data?.["@odata.nextLink"]));
49394
+ const { "@odata.nextLink": _superseded, ...rest } = data ?? {};
49395
+ const body = opts.view === "raw" ? { ...rest, value: page.rows } : projectCollection({ value: page.rows }, opts.view === "full" ? fullMessage : compactMessage, "message");
49396
+ return mailboxUntrusted({ ...body, next_from: page.next_from, has_more: page.has_more });
49397
+ }
49155
49398
  function registerMailTools(server, client2) {
49156
49399
  server.registerTool(
49157
49400
  "outlook_list_folders",
@@ -49180,7 +49423,7 @@ function registerMailTools(server, client2) {
49180
49423
  server.registerTool(
49181
49424
  "outlook_list_messages",
49182
49425
  {
49183
- description: 'List messages in a folder, newest first. Defaults to the inbox. Use `unreadOnly` for a triage view. Bodies are NOT included \u2014 call outlook_get_message for one. Note `search` and `unreadOnly` cannot be combined on a first-page request (the API rejects $search with $filter); pass `search` alone to search the whole mailbox. When following a `nextLink`, both are ignored, so echoing them back is harmless. Each message\'s To list (and Cc, in the full view) holds at most 20 addresses; a longer list adds ToCount/ToTruncated (CcCount/CcTruncated) with the full count \u2014 pass view:"raw" for every address. ' + UNTRUSTED_DESCRIPTION_SUFFIX,
49426
+ description: 'List messages in a folder, newest first. Defaults to the inbox. Use `unreadOnly` for a triage view. Bodies are NOT included \u2014 call outlook_get_message for one. Note `search` and `unreadOnly` cannot be combined on a first-page request (the API rejects $search with $filter); pass `search` alone to search the whole mailbox. When following a `nextLink`, both are ignored, so echoing them back is harmless. Each message\'s To list (and Cc, in the full view) holds at most 20 addresses; a longer list adds ToCount/ToTruncated (CcCount/CcTruncated) with the full count \u2014 pass view:"raw" for every address. ' + FROM_DESCRIPTION + " Cannot combine with `search`, `skip` or `nextLink`. " + UNTRUSTED_DESCRIPTION_SUFFIX,
49184
49427
  annotations: { readOnlyHint: true, openWorldHint: true },
49185
49428
  inputSchema: external_exports.object({
49186
49429
  view: viewParam(VIEWS2),
@@ -49189,10 +49432,15 @@ function registerMailTools(server, client2) {
49189
49432
  skip: external_exports.number().int().min(0).optional().describe("Offset for paging"),
49190
49433
  unreadOnly: external_exports.boolean().optional().describe("Only unread messages"),
49191
49434
  search: external_exports.string().optional().describe("Full-text search across the whole mailbox; cannot combine with unreadOnly on a first page (ignored with nextLink)"),
49435
+ from: fromParam,
49192
49436
  nextLink: nextLinkParam
49193
49437
  })
49194
49438
  },
49195
- async ({ view, folder, limit, skip, unreadOnly, search, nextLink }) => {
49439
+ async ({ view, folder, limit, skip, unreadOnly, search, from, nextLink }) => {
49440
+ const pos = fromPosition(from, { nextLink, search, skip });
49441
+ if (pos !== void 0) {
49442
+ return listFrom(client2, pos, { view: resolveView(view, VIEWS2), folder, limit: limit ?? 25, unreadOnly });
49443
+ }
49196
49444
  if (nextLink === void 0 && search !== void 0 && unreadOnly === true) {
49197
49445
  throw new McpToolError("`search` and `unreadOnly` cannot be combined.", {
49198
49446
  hint: "Outlook rejects $search together with $filter. Search first, then filter the results."
@@ -49246,19 +49494,24 @@ function registerMailTools(server, client2) {
49246
49494
  server.registerTool(
49247
49495
  "outlook_get_unread",
49248
49496
  {
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,
49497
+ 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. ' + FROM_DESCRIPTION + " Cannot combine with `sinceHours` or `nextLink`. " + UNTRUSTED_DESCRIPTION_SUFFIX,
49250
49498
  annotations: { readOnlyHint: true, openWorldHint: true },
49251
49499
  inputSchema: external_exports.object({
49252
49500
  folder: external_exports.string().min(1).optional().describe('Folder id or well-known name (default "inbox")'),
49253
49501
  limit: external_exports.number().int().min(1).max(50).optional().describe("Max messages (default 25, max 50)"),
49254
- sinceHours: external_exports.number().positive().max(24 * 365).optional().describe("Only mail received in the last N hours"),
49502
+ sinceHours: external_exports.number().positive().max(24 * 365).optional().describe("Only mail received in the last N hours (not with `from`)"),
49255
49503
  includeBody: external_exports.boolean().optional().describe("Fetch each plain-text body (default true). Invite events are resolved either way."),
49256
49504
  maxBodyChars: external_exports.number().int().min(100).max(1e5).optional().describe("Truncate each body to this many characters (default 4000); `bodyTruncated` marks a cut"),
49505
+ from: fromParam,
49257
49506
  nextLink: nextLinkParam
49258
49507
  })
49259
49508
  },
49260
- async ({ folder, limit, sinceHours, includeBody, maxBodyChars, nextLink }) => {
49509
+ async ({ folder, limit, sinceHours, includeBody, maxBodyChars, from, nextLink }) => {
49510
+ const pos = fromPosition(from, { nextLink, sinceHours });
49511
+ const max = limit ?? 25;
49512
+ const keyset = pos !== void 0 ? fromQuery(pos, max) : void 0;
49261
49513
  const filter = [
49514
+ keyset?.bound,
49262
49515
  sinceHours !== void 0 ? `ReceivedDateTime ge ${sinceLiteral(sinceHours)}` : void 0,
49263
49516
  "IsRead eq false"
49264
49517
  ].filter((c) => c !== void 0).join(" and ");
@@ -49268,14 +49521,16 @@ function registerMailTools(server, client2) {
49268
49521
  `/me/mailfolders/${folderSegment(folder ?? "inbox")}/messages`,
49269
49522
  {
49270
49523
  query: {
49271
- $top: limit ?? 25,
49524
+ $top: keyset?.top ?? max,
49272
49525
  $select: UNREAD_SELECT,
49273
49526
  $filter: filter,
49274
- $orderby: "ReceivedDateTime desc"
49527
+ $orderby: keyset ? "ReceivedDateTime asc" : "ReceivedDateTime desc"
49275
49528
  }
49276
49529
  }
49277
49530
  );
49278
- const rows = Array.isArray(data?.value) ? data.value : [];
49531
+ const listed = Array.isArray(data?.value) ? data.value : [];
49532
+ const page = pos !== void 0 ? fromPage(listed, pos, max, Boolean(data?.["@odata.nextLink"])) : void 0;
49533
+ const rows = page?.rows ?? listed;
49279
49534
  const wantBody = includeBody !== false;
49280
49535
  const cap = maxBodyChars ?? 4e3;
49281
49536
  const items = await mapWithConcurrency(rows, BODY_CONCURRENCY, async (m) => {
@@ -49323,8 +49578,13 @@ function registerMailTools(server, client2) {
49323
49578
  return item;
49324
49579
  });
49325
49580
  const out = { count: items.length, items };
49326
- const next = data?.["@odata.nextLink"];
49327
- if (next) out.nextLink = next;
49581
+ if (page !== void 0) {
49582
+ out.next_from = page.next_from;
49583
+ out.has_more = page.has_more;
49584
+ } else {
49585
+ const next = data?.["@odata.nextLink"];
49586
+ if (next) out.nextLink = next;
49587
+ }
49328
49588
  return mailboxUntrusted(out);
49329
49589
  }
49330
49590
  );
@@ -49814,6 +50074,9 @@ function messageBody(subject, body, html) {
49814
50074
  }
49815
50075
  var TEAMS = { IsOnlineMeeting: true, OnlineMeetingProvider: "TeamsForBusiness" };
49816
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
+ );
49817
50080
  var EVENT_STATE_SELECT = "Id,Subject,IsOrganizer,Start,End,Location,Attendees,IsOnlineMeeting,OnlineMeetingProvider,OnlineMeeting";
49818
50081
  function attendee(Address, Type) {
49819
50082
  return { Type, EmailAddress: { Address } };
@@ -49963,39 +50226,30 @@ function registerWriteTools(server, client2) {
49963
50226
  server.registerTool(
49964
50227
  "outlook_create_draft",
49965
50228
  {
49966
- 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,
49967
- 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 },
49968
50233
  inputSchema: external_exports.object({
49969
50234
  to: recipientList,
49970
50235
  cc: recipientList,
49971
50236
  subject: external_exports.string().describe("Subject line"),
49972
50237
  body: external_exports.string().describe("Message body"),
49973
- html: external_exports.boolean().optional().describe("Compose the body as HTML (default: plain text)"),
49974
- confirmToken: confirmTokenParam
50238
+ html: external_exports.boolean().optional().describe("Compose the body as HTML (default: plain text)")
49975
50239
  })
49976
50240
  },
49977
- async ({ to, cc, subject, body, html, confirmToken }, ctx) => {
50241
+ async ({ to, cc, subject, body, html }) => {
49978
50242
  const payload = {
49979
50243
  ...messageBody(subject, body, html),
49980
50244
  ToRecipients: toRecipients(to),
49981
50245
  ...cc?.length ? { CcRecipients: toRecipients(cc) } : {}
49982
50246
  };
49983
- const gate = await confirmWrite(ctx, {
49984
- tool: "outlook_create_draft",
49985
- action: "mail.create_draft",
49986
- message: "Review and confirm this draft:",
49987
- summary: `Create draft "${subject}"`,
49988
- // One signed-in mailbox per server process.
49989
- account: void 0,
49990
- request: { method: "POST", path: "/me/messages", body: payload },
49991
- confirmToken
49992
- });
49993
- if (gate) return gate;
49994
- const created = await client2.write(
49995
- "POST",
49996
- "/me/messages",
49997
- payload
50247
+ const create = await writeOrUnknown(
50248
+ () => client2.write("POST", "/me/messages", payload),
50249
+ "Drafts"
49998
50250
  );
50251
+ if (!create.ok) return create.result;
50252
+ const created = create.value;
49999
50253
  return minifiedResult({ created: true, Id: created?.Id, WebLink: created?.WebLink });
50000
50254
  }
50001
50255
  );
@@ -50212,7 +50466,7 @@ function registerWriteTools(server, client2) {
50212
50466
  server.registerTool(
50213
50467
  "outlook_update_event",
50214
50468
  {
50215
- 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,
50216
50470
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
50217
50471
  inputSchema: external_exports.object({
50218
50472
  id: external_exports.string().min(1).describe("Event Id from outlook_list_events or outlook_get_event"),
@@ -50225,7 +50479,7 @@ function registerWriteTools(server, client2) {
50225
50479
  addAttendees: recipientList.describe("Required attendees to add"),
50226
50480
  addOptionalAttendees: recipientList.describe("Optional attendees to add"),
50227
50481
  removeAttendees: recipientList.describe("Attendees to remove"),
50228
- teamsMeeting: teamsMeetingParam,
50482
+ teamsMeeting: addTeamsMeetingParam,
50229
50483
  confirmToken: confirmTokenParam
50230
50484
  })
50231
50485
  },
@@ -50278,18 +50532,21 @@ function registerWriteTools(server, client2) {
50278
50532
  }
50279
50533
  payload.Attendees = next;
50280
50534
  }
50281
- const addTeams = args.teamsMeeting !== false && !(current && isTeams(current));
50535
+ const addTeams = args.teamsMeeting === true && !(current && isTeams(current));
50282
50536
  if (addTeams) Object.assign(payload, TEAMS);
50283
50537
  if (Object.keys(payload).length === 0) {
50284
50538
  throw new McpToolError("Nothing to update.", {
50285
- 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."
50286
50540
  });
50287
50541
  }
50288
50542
  const gate = await confirmWrite(ctx, {
50289
50543
  tool: "outlook_update_event",
50290
50544
  action: "calendar.update_event",
50291
- message: "Review and confirm this change (attendees are sent an updated invitation):",
50292
- 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("; ")}`,
50293
50550
  // One signed-in mailbox per server process.
50294
50551
  account: void 0,
50295
50552
  target: args.id,