@chrischall/office-outlook-mcp 0.7.0 → 0.7.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.7.0"
10
+ "version": "0.7.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.7.0",
18
+ "version": "0.7.1",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "office-outlook-mcp",
3
3
  "displayName": "Outlook (Microsoft 365)",
4
- "version": "0.7.0",
4
+ "version": "0.7.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/dist/bundle.js CHANGED
@@ -35575,7 +35575,8 @@ var init_errors3 = __esm({
35575
35575
  HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
35576
35576
  AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
35577
35577
  JSON_SECRET_KEYS = [
35578
- "(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
35578
+ "(?:access|refresh|id|auth|session|bearer|csrf|xsrf|api)[_-]?token",
35579
+ // api: fleet-audit#1444 (Pipedrive/Todoist-style, as in QUERY_SECRET_RE)
35579
35580
  "client[_-]?secret",
35580
35581
  "api[_-]?secret",
35581
35582
  "(?:x[_-])?api[_-]?key",
@@ -37347,6 +37348,154 @@ var init_transport_failure = __esm({
37347
37348
  }
37348
37349
  });
37349
37350
 
37351
+ // node_modules/@chrischall/mcp-utils/dist/netguard/address.js
37352
+ function isPublicAddress(ip) {
37353
+ const v4 = parseIPv4(ip);
37354
+ if (v4)
37355
+ return isPublicIPv4(v4);
37356
+ const v6 = parseIPv6(ip);
37357
+ if (v6)
37358
+ return isPublicIPv6(v6);
37359
+ return false;
37360
+ }
37361
+ function parseIPv4(text) {
37362
+ if (!/^(?:0|[1-9]\d{0,2})(?:\.(?:0|[1-9]\d{0,2})){3}$/.test(text))
37363
+ return void 0;
37364
+ const octets = text.split(".").map(Number);
37365
+ return octets.every((o) => o <= 255) ? octets : void 0;
37366
+ }
37367
+ function parseIPv6(text) {
37368
+ if (!/^[0-9a-fA-F:.]+$/.test(text) || !text.includes(":"))
37369
+ return void 0;
37370
+ const halves = text.split("::");
37371
+ if (halves.length > 2)
37372
+ return void 0;
37373
+ const groups = (part) => {
37374
+ if (part === "")
37375
+ return [];
37376
+ const out = [];
37377
+ const pieces = part.split(":");
37378
+ for (const [i, piece] of pieces.entries()) {
37379
+ if (i === pieces.length - 1 && piece.includes(".")) {
37380
+ const v4 = parseIPv4(piece);
37381
+ if (!v4)
37382
+ return void 0;
37383
+ const [a = 0, b = 0, c = 0, d = 0] = v4;
37384
+ out.push(a << 8 | b, c << 8 | d);
37385
+ continue;
37386
+ }
37387
+ if (!/^[0-9a-fA-F]{1,4}$/.test(piece))
37388
+ return void 0;
37389
+ out.push(parseInt(piece, 16));
37390
+ }
37391
+ return out;
37392
+ };
37393
+ const head = groups(halves[0] ?? "");
37394
+ const tail = halves.length === 2 ? groups(halves[1] ?? "") : [];
37395
+ if (!head || !tail)
37396
+ return void 0;
37397
+ const fill = 8 - head.length - tail.length;
37398
+ if (halves.length === 2 ? fill < 1 : fill !== 0)
37399
+ return void 0;
37400
+ const words = [...head, ...new Array(fill).fill(0), ...tail];
37401
+ const bytes = [];
37402
+ for (const w of words)
37403
+ bytes.push(w >> 8, w & 255);
37404
+ return bytes;
37405
+ }
37406
+ function inRange(bytes, prefix, bits) {
37407
+ for (let i = 0; i < bits; i += 1) {
37408
+ const byte = i >> 3;
37409
+ const mask = 128 >> (i & 7);
37410
+ if (((bytes[byte] ?? 0) & mask) !== ((prefix[byte] ?? 0) & mask))
37411
+ return false;
37412
+ }
37413
+ return true;
37414
+ }
37415
+ function isPublicIPv4(octets) {
37416
+ return !BLOCKED_V4.some(([prefix, bits]) => inRange(octets, prefix, bits));
37417
+ }
37418
+ function isPublicIPv6(bytes) {
37419
+ if (inRange(bytes, MAPPED_V4, 96) || inRange(bytes, SIIT_V4, 96) || inRange(bytes, NAT64, 96)) {
37420
+ return isPublicIPv4(bytes.slice(12, 16));
37421
+ }
37422
+ if (inRange(bytes, SIX_TO_FOUR, 16))
37423
+ return isPublicIPv4(bytes.slice(2, 6));
37424
+ return !BLOCKED_V6.some(([prefix, bits]) => inRange(bytes, prefix, bits));
37425
+ }
37426
+ var BLOCKED_V4, V6, BLOCKED_V6, MAPPED_V4, SIIT_V4, NAT64, SIX_TO_FOUR;
37427
+ var init_address = __esm({
37428
+ "node_modules/@chrischall/mcp-utils/dist/netguard/address.js"() {
37429
+ BLOCKED_V4 = [
37430
+ [[0, 0, 0, 0], 8],
37431
+ // this network
37432
+ [[10, 0, 0, 0], 8],
37433
+ // RFC 1918
37434
+ [[100, 64, 0, 0], 10],
37435
+ // CGNAT
37436
+ [[127, 0, 0, 0], 8],
37437
+ // loopback
37438
+ [[169, 254, 0, 0], 16],
37439
+ // link-local, incl. cloud metadata
37440
+ [[172, 16, 0, 0], 12],
37441
+ // RFC 1918
37442
+ [[192, 0, 0, 0], 24],
37443
+ // IETF protocol assignments
37444
+ [[192, 0, 2, 0], 24],
37445
+ // TEST-NET-1
37446
+ [[192, 88, 99, 0], 24],
37447
+ // 6to4 relay anycast (deprecated)
37448
+ [[192, 168, 0, 0], 16],
37449
+ // RFC 1918
37450
+ [[198, 18, 0, 0], 15],
37451
+ // benchmarking
37452
+ [[198, 51, 100, 0], 24],
37453
+ // TEST-NET-2
37454
+ [[203, 0, 113, 0], 24],
37455
+ // TEST-NET-3
37456
+ [[224, 0, 0, 0], 4],
37457
+ // multicast
37458
+ [[240, 0, 0, 0], 4]
37459
+ // reserved + broadcast
37460
+ ];
37461
+ V6 = (hex3) => parseIPv6(hex3);
37462
+ BLOCKED_V6 = [
37463
+ [V6("::"), 96],
37464
+ // ::, ::1 and the deprecated IPv4-compatible form, refused whole
37465
+ [V6("64:ff9b:1::"), 48],
37466
+ // local-use NAT64 (RFC 8215)
37467
+ [V6("100::"), 64],
37468
+ // discard-only
37469
+ [V6("2001::"), 32],
37470
+ // Teredo
37471
+ [V6("2001:2::"), 48],
37472
+ // benchmarking
37473
+ [V6("2001:10::"), 28],
37474
+ // ORCHID (deprecated)
37475
+ [V6("2001:20::"), 28],
37476
+ // ORCHIDv2
37477
+ [V6("2001:db8::"), 32],
37478
+ // documentation
37479
+ [V6("3fff::"), 20],
37480
+ // documentation (RFC 9637)
37481
+ [V6("5f00::"), 16],
37482
+ // SRv6 SIDs
37483
+ [V6("fc00::"), 7],
37484
+ // unique local
37485
+ [V6("fe80::"), 10],
37486
+ // link-local
37487
+ [V6("fec0::"), 10],
37488
+ // site-local (deprecated)
37489
+ [V6("ff00::"), 8]
37490
+ // multicast
37491
+ ];
37492
+ MAPPED_V4 = V6("::ffff:0:0");
37493
+ SIIT_V4 = V6("::ffff:0:0:0");
37494
+ NAT64 = V6("64:ff9b::");
37495
+ SIX_TO_FOUR = V6("2002::");
37496
+ }
37497
+ });
37498
+
37350
37499
  // node_modules/@chrischall/mcp-utils/dist/internal/retry-after.js
37351
37500
  function retryAfterToMs(header, now = Date.now()) {
37352
37501
  if (header == null)
@@ -37366,6 +37515,44 @@ var init_retry_after = __esm({
37366
37515
  }
37367
37516
  });
37368
37517
 
37518
+ // node_modules/@chrischall/mcp-utils/dist/internal/sleep.js
37519
+ function abortReason(signal) {
37520
+ return signal.reason ?? new DOMException("This operation was aborted", "AbortError");
37521
+ }
37522
+ function cancellableSleep(ms, sleep3) {
37523
+ const signal = currentCallSignal();
37524
+ if (signal?.aborted)
37525
+ return Promise.reject(abortReason(signal));
37526
+ if (!signal)
37527
+ return sleep3 ? sleep3(ms) : new Promise((resolve5) => setTimeout(resolve5, ms));
37528
+ return new Promise((resolve5, reject) => {
37529
+ let timer;
37530
+ const onAbort = () => {
37531
+ if (timer !== void 0)
37532
+ clearTimeout(timer);
37533
+ reject(abortReason(signal));
37534
+ };
37535
+ const done = () => {
37536
+ signal.removeEventListener("abort", onAbort);
37537
+ resolve5();
37538
+ };
37539
+ signal.addEventListener("abort", onAbort, { once: true });
37540
+ if (sleep3) {
37541
+ sleep3(ms).then(done, (err) => {
37542
+ signal.removeEventListener("abort", onAbort);
37543
+ reject(err);
37544
+ });
37545
+ } else {
37546
+ timer = setTimeout(done, ms);
37547
+ }
37548
+ });
37549
+ }
37550
+ var init_sleep = __esm({
37551
+ "node_modules/@chrischall/mcp-utils/dist/internal/sleep.js"() {
37552
+ init_cancel();
37553
+ }
37554
+ });
37555
+
37369
37556
  // node_modules/@chrischall/mcp-utils/dist/http/index.js
37370
37557
  function withoutCredentials(headers, tokenHeader) {
37371
37558
  const drop = new Set(CROSS_ORIGIN_DROPPED_HEADERS);
@@ -37417,6 +37604,9 @@ async function writeOrUnknown(run, onUnknown) {
37417
37604
  return { ok: false, result: await onUnknown(err, unknownOutcomeReason(err)) };
37418
37605
  }
37419
37606
  }
37607
+ function isOutcomeUnknownStatus(status) {
37608
+ return status >= 500 && status <= 599 && status !== 501 && status !== 505;
37609
+ }
37420
37610
  function headerOf(headers, name) {
37421
37611
  if (headers === void 0)
37422
37612
  return void 0;
@@ -37532,7 +37722,7 @@ function createApiClient(opts) {
37532
37722
  const retry = opts.retry ?? DEFAULT_RETRY;
37533
37723
  const service = opts.serviceName ?? hostOf(opts.baseUrl);
37534
37724
  const doFetch = opts.fetchImpl ?? fetch;
37535
- const sleep3 = opts.sleep ?? defaultSleep;
37725
+ const sleep3 = (ms) => cancellableSleep(ms, opts.sleep);
37536
37726
  const unauthorized = () => opts.onUnauthorized ? opts.onUnauthorized() : new UnauthorizedError(service);
37537
37727
  const retryStatuses = retry.statuses ?? [429];
37538
37728
  const timeoutMs = opts.timeout === false ? 0 : opts.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
@@ -37547,6 +37737,7 @@ function createApiClient(opts) {
37547
37737
  }
37548
37738
  const authHeader = (token) => !token ? {} : opts.tokenHeader ? { [opts.tokenHeader]: token } : { Authorization: `Bearer ${token}` };
37549
37739
  const transportFailures = /* @__PURE__ */ new WeakSet();
37740
+ const answered5xx = /* @__PURE__ */ new WeakSet();
37550
37741
  const markTransportFailure = (err) => {
37551
37742
  if (typeof err === "object" && err !== null && err.name !== "AbortError") {
37552
37743
  transportFailures.add(err);
@@ -37626,7 +37817,7 @@ function createApiClient(opts) {
37626
37817
  const baseParts = (() => {
37627
37818
  try {
37628
37819
  const u = new URL(base);
37629
- return { origin: u.origin, username: u.username, password: u.password };
37820
+ return { origin: u.origin, hostname: u.hostname, username: u.username, password: u.password };
37630
37821
  } catch {
37631
37822
  return void 0;
37632
37823
  }
@@ -37666,6 +37857,15 @@ function createApiClient(opts) {
37666
37857
  const next = new URL(href);
37667
37858
  return baseOrigin !== void 0 && next.origin === baseOrigin && next.username === baseParts?.username && next.password === baseParts?.password;
37668
37859
  }
37860
+ function isNonPublicLiteralHost(hostname3) {
37861
+ const host = hostname3.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
37862
+ if (host === "localhost" || host.endsWith(".localhost"))
37863
+ return true;
37864
+ if (/^[\d.]+$/.test(host) || host.includes(":"))
37865
+ return !isPublicAddress(host);
37866
+ return false;
37867
+ }
37868
+ const baseIsPublic = baseParts !== void 0 && !isNonPublicLiteralHost(baseParts.hostname);
37669
37869
  function sameOriginHop(location, from) {
37670
37870
  const href = resolveLocation(location, from);
37671
37871
  const next = new URL(href);
@@ -37718,7 +37918,11 @@ function createApiClient(opts) {
37718
37918
  }
37719
37919
  if (stripCrossOrigin) {
37720
37920
  hopUrl = resolveLocation(location, hopUrl);
37721
- if (credentialed && !onBaseOrigin(hopUrl))
37921
+ const offOrigin = !onBaseOrigin(hopUrl);
37922
+ if (offOrigin && baseIsPublic && isNonPublicLiteralHost(new URL(hopUrl).hostname)) {
37923
+ throw new RedirectRefusedError(`Refusing redirect from ${service} to ${new URL(hopUrl).origin}: it is not a public address (loopback, link-local or a private network), so nothing is sent there.`);
37924
+ }
37925
+ if (credentialed && offOrigin)
37722
37926
  credentialed = false;
37723
37927
  } else {
37724
37928
  hopUrl = sameOriginHop(location, hopUrl);
@@ -37793,19 +37997,22 @@ function createApiClient(opts) {
37793
37997
  hint: opts.writeOutcomeHint
37794
37998
  });
37795
37999
  }
37796
- if (typeof err === "object" && err !== null && transportFailures.has(err)) {
38000
+ if (typeof err === "object" && err !== null && (transportFailures.has(err) || answered5xx.has(err))) {
37797
38001
  throw new WriteOutcomeUnknownError(service, method, { cause: err, hint: opts.writeOutcomeHint });
37798
38002
  }
37799
38003
  throw err;
37800
38004
  }
37801
38005
  }
37802
- function withIdempotency(opt) {
38006
+ function withIdempotency(method, opt) {
37803
38007
  const name = opts.idempotencyHeader;
37804
38008
  if (name === void 0 || name === "")
37805
38009
  return opt;
37806
38010
  const lower2 = name.toLowerCase();
37807
38011
  if (opt.headers && Object.keys(opt.headers).some((k) => k.toLowerCase() === lower2))
37808
38012
  return opt;
38013
+ const repeatable = opt.idempotent === true || SAFE_METHODS.has(method.toUpperCase());
38014
+ if (opt.idempotencyKey === void 0 && repeatable)
38015
+ return opt;
37809
38016
  const key = opt.idempotencyKey ?? globalThis.crypto.randomUUID();
37810
38017
  return { ...opt, headers: { ...opt.headers, [name]: key } };
37811
38018
  }
@@ -37820,18 +38027,21 @@ function createApiClient(opts) {
37820
38027
  if (decision === "retry" && !retried)
37821
38028
  return "retry";
37822
38029
  }
37823
- return httpError(res, text, method, path, service);
38030
+ const err = httpError(res, text, method, path, service);
38031
+ if (isOutcomeUnknownStatus(res.status) && !(err instanceof EdgeBlockedError))
38032
+ answered5xx.add(err);
38033
+ return err;
37824
38034
  }
37825
38035
  const fetchJson = (method, path, opt = {}) => {
37826
- const o = withIdempotency(opt);
38036
+ const o = withIdempotency(method, opt);
37827
38037
  return guardWrite(method, o, (dispatch) => fetchJsonOnce(method, path, o, dispatch));
37828
38038
  };
37829
38039
  const fetchHtml = (method, path, opt = {}) => {
37830
- const o = withIdempotency(opt);
38040
+ const o = withIdempotency(method, opt);
37831
38041
  return guardWrite(method, o, (dispatch) => fetchHtmlOnce(method, path, o, dispatch));
37832
38042
  };
37833
38043
  const fetchRaw = (method, path, opt = {}) => {
37834
- const o = withIdempotency(opt);
38044
+ const o = withIdempotency(method, opt);
37835
38045
  return guardWrite(method, o, (dispatch) => fetchRawOnce(method, path, o, dispatch));
37836
38046
  };
37837
38047
  async function fetchJsonOnce(method, path, opt, dispatch) {
@@ -37973,7 +38183,7 @@ function decodeJwtExp(token) {
37973
38183
  }
37974
38184
  return exp;
37975
38185
  }
37976
- 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;
38186
+ var DEFAULT_RETRY, 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;
37977
38187
  var init_http = __esm({
37978
38188
  "node_modules/@chrischall/mcp-utils/dist/http/index.js"() {
37979
38189
  init_headers();
@@ -37989,9 +38199,10 @@ var init_http = __esm({
37989
38199
  init_body_limit();
37990
38200
  init_net_atoms();
37991
38201
  init_url_safety();
38202
+ init_address();
37992
38203
  init_retry_after();
38204
+ init_sleep();
37993
38205
  DEFAULT_RETRY = { count: 1, delayMs: 2e3 };
37994
- defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
37995
38206
  DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
37996
38207
  DEFAULT_MAX_REDIRECTS = 5;
37997
38208
  FETCH_MAX_REDIRECTS = 20;
@@ -38233,7 +38444,7 @@ var init_version = __esm({
38233
38444
  "src/version.ts"() {
38234
38445
  "use strict";
38235
38446
  PACKAGE_NAME = "office-outlook-mcp";
38236
- VERSION = "0.7.0";
38447
+ VERSION = "0.7.1";
38237
38448
  }
38238
38449
  });
38239
38450
 
@@ -50614,7 +50825,7 @@ function dateTimeTimeZone2(value, zone) {
50614
50825
  }
50615
50826
  return { DateTime: value, TimeZone: zone };
50616
50827
  }
50617
- var TIMEOUT_SENTENCE = 'If Outlook does not answer in time (or the connection drops) the result has status "unknown": the write may well have gone through, so check before retrying \u2014 never resend blindly.';
50828
+ var TIMEOUT_SENTENCE = 'If Outlook does not answer in time, the connection drops, or a server error (5xx) comes back after the request was sent, the result has status "unknown": the write may well have gone through, so check before retrying \u2014 never resend blindly.';
50618
50829
  function writeOrUnknown2(run, checkWhere) {
50619
50830
  return writeOrUnknown(
50620
50831
  run,
@@ -51491,6 +51702,9 @@ function expiry(opts) {
51491
51702
  return Math.floor((opts?.now ?? Date.now()) + ttl);
51492
51703
  }
51493
51704
  function toArrayBuffer(view) {
51705
+ const buf = view.buffer;
51706
+ if (buf instanceof ArrayBuffer && view.byteOffset === 0 && view.byteLength === buf.byteLength)
51707
+ return buf;
51494
51708
  return view.slice().buffer;
51495
51709
  }
51496
51710
  function createBlobStore(opts) {
@@ -51704,6 +51918,7 @@ function buildZip(entries, modifiedAt = new Date(Date.UTC(1980, 0, 1))) {
51704
51918
  // node_modules/@chrischall/mcp-utils/dist/export/sink.js
51705
51919
  var EXPORT_MARKER = ".mcp-export";
51706
51920
  var EXPORT_DIR_NAME = /-\d{8}-\d{6}(?:-\d+)?$/;
51921
+ var EXPORT_BLOB_MAX_BYTES = 256 * 1024 * 1024;
51707
51922
  var EXPORT_ID = /^[A-Za-z0-9_-]{8,64}$/;
51708
51923
  var MAX_PART = 9999;
51709
51924
  var MAX_REL_PATH = 1024;
@@ -51781,14 +51996,17 @@ async function openExportSink(opts) {
51781
51996
  const part = checkPart(opts.part ?? 1);
51782
51997
  const exportId = checkExportId(opts.exportId);
51783
51998
  const now = opts.now;
51999
+ if (opts.maxBytes !== void 0 && !(Number.isSafeInteger(opts.maxBytes) && opts.maxBytes > 0)) {
52000
+ throw new TypeError(`openExportSink: maxBytes must be a positive whole number, got ${opts.maxBytes}.`);
52001
+ }
51784
52002
  const store = blobStoreFromEnv(env);
51785
52003
  if (store)
51786
52004
  return openBlobSink(store, exportId, part, opts, now);
51787
52005
  if (opts.existingDir !== void 0) {
51788
- return openDiskSink(resumeDir(opts.existingDir, opts.envVar, env, exportId), exportId, part);
52006
+ return openDiskSink(resumeDir(opts.existingDir, opts.envVar, env, exportId, opts.name), exportId, part);
51789
52007
  }
51790
52008
  const dir = freshDir(opts, env, now);
51791
- await writeFileSafe(join10(dir, EXPORT_MARKER), new TextEncoder().encode(JSON.stringify({ exportId })), {
52009
+ await writeFileSafe(join10(dir, EXPORT_MARKER), new TextEncoder().encode(JSON.stringify({ exportId, name: opts.name })), {
51792
52010
  allowedRoots: [dir],
51793
52011
  mode: 384
51794
52012
  });
@@ -51831,6 +52049,7 @@ function freshDir(opts, env, now) {
51831
52049
  function notThisExport() {
51832
52050
  return badInput2("The export directory to resume was not created by this export.", "Pass the cursor exactly as returned, or start a new export without one.");
51833
52051
  }
52052
+ var MAX_MARKER = 4096;
51834
52053
  function readMarker(dir) {
51835
52054
  let fd;
51836
52055
  try {
@@ -51839,18 +52058,21 @@ function readMarker(dir) {
51839
52058
  return void 0;
51840
52059
  }
51841
52060
  try {
51842
- const buf = Buffer.alloc(256);
52061
+ const buf = Buffer.alloc(MAX_MARKER);
51843
52062
  const n = readSync(fd, buf, 0, buf.length, 0);
51844
52063
  const parsed = JSON.parse(buf.subarray(0, n).toString("utf8"));
51845
52064
  const id = parsed?.exportId;
51846
- return typeof id === "string" ? id : void 0;
52065
+ const name = parsed?.name;
52066
+ if (typeof id !== "string")
52067
+ return void 0;
52068
+ return { exportId: id, name: typeof name === "string" ? name : void 0 };
51847
52069
  } catch {
51848
52070
  return void 0;
51849
52071
  } finally {
51850
52072
  closeSync4(fd);
51851
52073
  }
51852
52074
  }
51853
- function resumeDir(existingDir, envVar, env, exportId) {
52075
+ function resumeDir(existingDir, envVar, env, exportId, name) {
51854
52076
  if (typeof existingDir !== "string" || existingDir.length === 0)
51855
52077
  throw outsideRoots("export directory", envVar);
51856
52078
  const roots = exportAllowedRoots(envVar, env);
@@ -51876,7 +52098,10 @@ function resumeDir(existingDir, envVar, env, exportId) {
51876
52098
  } catch {
51877
52099
  markerIsFile = false;
51878
52100
  }
51879
- if (!markerIsFile || readMarker(real) !== exportId)
52101
+ if (!markerIsFile)
52102
+ throw notThisExport();
52103
+ const marker = readMarker(real);
52104
+ if (!marker || marker.exportId !== exportId || marker.name !== name)
51880
52105
  throw notThisExport();
51881
52106
  return real;
51882
52107
  }
@@ -51930,6 +52155,8 @@ function openBlobSink(store, exportId, part, opts, now) {
51930
52155
  const signOpts = { ...opts.ttlMs !== void 0 ? { ttlMs: opts.ttlMs } : {} };
51931
52156
  store.signGetUrl(key, { ...signOpts, now: now ?? Date.now() });
51932
52157
  const entries = /* @__PURE__ */ new Map();
52158
+ const maxBytes = opts.maxBytes ?? EXPORT_BLOB_MAX_BYTES;
52159
+ let held = 0;
51933
52160
  let finished = false;
51934
52161
  return {
51935
52162
  mode: "blob",
@@ -51942,6 +52169,20 @@ function openBlobSink(store, exportId, part, opts, now) {
51942
52169
  if (entries.has(relPath) && !o.overwrite)
51943
52170
  throw badInput2(`Export file ${relPath} was already written.`);
51944
52171
  const bytes = toBytes(data);
52172
+ const replaced = entries.get(relPath)?.bytes.byteLength ?? 0;
52173
+ if (bytes.byteLength > maxBytes) {
52174
+ throw new McpToolError(`Export file ${relPath} alone is ${bytes.byteLength} bytes, over the ${maxBytes}-byte part limit.`, {
52175
+ kind: "too_large",
52176
+ hint: "No export part can hold this file: skip it, or raise the part limit (maxBytes)."
52177
+ });
52178
+ }
52179
+ if (held - replaced + bytes.byteLength > maxBytes) {
52180
+ throw new McpToolError(`This export part would exceed its ${maxBytes}-byte limit with ${relPath}.`, {
52181
+ kind: "too_large",
52182
+ hint: "Finish this part and continue the export in the next one (return the resume cursor)."
52183
+ });
52184
+ }
52185
+ held += bytes.byteLength - replaced;
51945
52186
  entries.delete(relPath);
51946
52187
  entries.set(relPath, { bytes, compress: shouldCompress(relPath, contentType) });
51947
52188
  return { name: relPath, bytes: bytes.byteLength };
@@ -52138,7 +52379,7 @@ function isoBound(name, raw) {
52138
52379
  }
52139
52380
  return decodeFrom(raw).t;
52140
52381
  }
52141
- var defaultSleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
52382
+ var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
52142
52383
  async function with429Retry(fn, sleep3) {
52143
52384
  for (let attempt = 0; ; attempt++) {
52144
52385
  try {
@@ -52278,7 +52519,7 @@ function extendSpan(totals, t) {
52278
52519
  if (!totals.oldest || ms < timeOf(totals.oldest)) totals.oldest = t;
52279
52520
  if (!totals.newest || ms > timeOf(totals.newest)) totals.newest = t;
52280
52521
  }
52281
- function inRange(received, since, until) {
52522
+ function inRange2(received, since, until) {
52282
52523
  if (!since && !until) return true;
52283
52524
  const t = timeOf(received);
52284
52525
  if (Number.isNaN(t)) return false;
@@ -52658,7 +52899,7 @@ function registerExportTools(server, client2, opts = {}) {
52658
52899
  const run = {
52659
52900
  ...opts,
52660
52901
  env: opts.env ?? process.env,
52661
- sleep: opts.sleep ?? defaultSleep2,
52902
+ sleep: opts.sleep ?? defaultSleep,
52662
52903
  clock: opts.clock ?? Date.now
52663
52904
  };
52664
52905
  const pageSize = Math.min(Math.max(1, opts.pageSize ?? MAX_TOP2), MAX_TOP2);
@@ -52745,7 +52986,7 @@ function registerExportTools(server, client2, opts = {}) {
52745
52986
  sleep3
52746
52987
  ),
52747
52988
  (link2) => with429Retry(() => client2.getAbsolute(link2, { text: true }), sleep3),
52748
- (m) => inRange(m.ReceivedDateTime, since, until)
52989
+ (m) => inRange2(m.ReceivedDateTime, since, until)
52749
52990
  );
52750
52991
  };
52751
52992
  return runExport(client2, run, input2, {
@@ -68,7 +68,7 @@ function dateTimeTimeZone(value, zone) {
68
68
  * Tool-description sentence for a write whose timeout leaves the outcome
69
69
  * unknown. Kept next to {@link unknownOutcome} so the two cannot drift.
70
70
  */
71
- const TIMEOUT_SENTENCE = 'If Outlook does not answer in time (or the connection drops) the result has status "unknown": the write may well have gone through, so check before retrying — never resend blindly.';
71
+ const TIMEOUT_SENTENCE = 'If Outlook does not answer in time, the connection drops, or a server error (5xx) comes back after the request was sent, the result has status "unknown": the write may well have gone through, so check before retrying — never resend blindly.';
72
72
  /**
73
73
  * Run a non-idempotent write; a timeout or dropped connection after the
74
74
  * request was sent (mcp-utils' WriteOutcomeUnknownError) becomes a non-error
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.7.0'; // x-release-please-version
13
+ export const VERSION = '0.7.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.7.0",
3
+ "version": "0.7.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,7 +45,7 @@
45
45
  "typecheck": "tsc -p tsconfig.check.json"
46
46
  },
47
47
  "dependencies": {
48
- "@chrischall/mcp-utils": "^3.3.0",
48
+ "@chrischall/mcp-utils": "^3.3.1",
49
49
  "@fetchproxy/server": "^3.6.2",
50
50
  "@modelcontextprotocol/server": "^2.3.0",
51
51
  "dotenv": "^18.0.1",
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.7.0",
9
+ "version": "0.7.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/office-outlook-mcp",
14
- "version": "0.7.0",
14
+ "version": "0.7.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },