apple-mail-mcp 2.20.22 → 2.20.23

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/build/cli.js CHANGED
@@ -12265,44 +12265,77 @@ function sensitiveRoots() {
12265
12265
  }
12266
12266
  });
12267
12267
  }
12268
+ function homeCanonical() {
12269
+ try {
12270
+ return canonicalize(homedir());
12271
+ } catch {
12272
+ return void 0;
12273
+ }
12274
+ }
12268
12275
  function isWithinRoot(candidate, root) {
12269
12276
  return candidate === root || candidate.startsWith(root + sep);
12270
12277
  }
12271
12278
  function hasHiddenPathSegment(candidate) {
12272
12279
  return candidate.split(sep).some((segment) => segment.startsWith(".") && segment.length > 1);
12273
12280
  }
12274
- function isProtectedPath(candidate) {
12281
+ function userLibraryTail(candidate) {
12282
+ const segments = candidate.split(sep).filter(Boolean);
12283
+ const lower = segments.map((segment) => segment.toLowerCase());
12284
+ const volumeTops = [
12285
+ 0,
12286
+ lower[0] === "volumes" ? 2 : -1,
12287
+ lower.slice(0, 3).join("/") === "system/volumes/data" ? 3 : -1
12288
+ ];
12289
+ for (const i of volumeTops) {
12290
+ if (i >= 0 && lower[i] === "users" && lower[i + 2] === "library") return lower.slice(i + 3);
12291
+ }
12292
+ const home = homeCanonical() ?? resolve3(homedir());
12293
+ if (home === sep || !isWithinRoot(candidate, home)) return void 0;
12294
+ const relative = candidate.slice(home.length).split(sep).filter(Boolean);
12295
+ if (relative[0]?.toLowerCase() !== "library") return void 0;
12296
+ return relative.slice(1).map((segment) => segment.toLowerCase());
12297
+ }
12298
+ function isLibraryDocument(tail) {
12299
+ return LIBRARY_DOCUMENT_FOLDERS.some((folder) => folder.every((name2, i) => tail[i] === name2));
12300
+ }
12301
+ function isProtectedPath(candidate, exemptRoots = []) {
12275
12302
  if (hasHiddenPathSegment(candidate)) return true;
12276
12303
  if (sensitiveRoots().some((root) => isWithinRoot(candidate, root))) return true;
12277
- let home;
12304
+ const tail = userLibraryTail(candidate);
12305
+ if (!tail || isLibraryDocument(tail)) return false;
12306
+ const home = homeCanonical();
12307
+ return !exemptRoots.some(
12308
+ (root) => isWithinRoot(candidate, root) && !(home && isWithinRoot(home, root))
12309
+ );
12310
+ }
12311
+ function canonicalTmpdir() {
12278
12312
  try {
12279
- home = canonicalize(homedir());
12313
+ return [canonicalize(tmpdir())];
12280
12314
  } catch {
12281
- home = resolve3(homedir());
12315
+ return [];
12282
12316
  }
12283
- if (!isWithinRoot(candidate, home)) return false;
12284
- const relative = candidate.slice(home.length).split(sep).filter(Boolean);
12285
- return relative.length >= 4 && relative[0].toLowerCase() === "library" && relative[1].toLowerCase() === "application support" && relative.at(-1)?.toLowerCase() === "config.json";
12286
12317
  }
12287
- function configuredRoots(env) {
12288
- const raw = env[ATTACHMENT_READ_ROOTS_ENV];
12289
- const extraRoots = raw === void 0 ? [] : raw.split(delimiter2).map((root) => root.trim()).filter(Boolean);
12290
- const requested = [...DEFAULT_ATTACHMENT_READ_ROOTS, ...extraRoots];
12291
- for (const root of requested) {
12292
- if (!isAbsolute(root)) {
12293
- throw new Error(`${ATTACHMENT_READ_ROOTS_ENV} entries must be absolute paths.`);
12294
- }
12295
- }
12318
+ function canonicalRoots(requested) {
12296
12319
  const resolved = [];
12297
12320
  for (const root of requested) {
12298
12321
  try {
12299
12322
  const canonical = canonicalize(resolve3(root));
12300
- if (!resolved.includes(canonical)) resolved.push(canonical);
12323
+ if (canonical !== sep && !resolved.includes(canonical)) resolved.push(canonical);
12301
12324
  } catch {
12302
12325
  }
12303
12326
  }
12304
12327
  return resolved;
12305
12328
  }
12329
+ function explicitRoots(env) {
12330
+ const raw = env[ATTACHMENT_READ_ROOTS_ENV];
12331
+ const extraRoots = raw === void 0 ? [] : raw.split(delimiter2).map((root) => root.trim()).filter(Boolean);
12332
+ for (const root of extraRoots) {
12333
+ if (!isAbsolute(root)) {
12334
+ throw new Error(`${ATTACHMENT_READ_ROOTS_ENV} entries must be absolute paths.`);
12335
+ }
12336
+ }
12337
+ return canonicalRoots(extraRoots);
12338
+ }
12306
12339
  function resolveAttachmentReadPath(filePath, env = process.env) {
12307
12340
  if (!isAbsolute(filePath)) {
12308
12341
  throw new Error(`Attachment path must be absolute: "${filePath}"`);
@@ -12321,19 +12354,22 @@ function resolveAttachmentReadPath(filePath, env = process.env) {
12321
12354
  if (error2 instanceof Error && error2.message.includes("not a regular file")) throw error2;
12322
12355
  throw new Error(`Attachment file not found: "${filePath}"`);
12323
12356
  }
12324
- if (isProtectedPath(canonical)) {
12357
+ const explicit = explicitRoots(env);
12358
+ const exempt = [...explicit, ...canonicalTmpdir()];
12359
+ if (isProtectedPath(canonical, exempt) || isProtectedPath(resolve3(filePath), exempt)) {
12325
12360
  throw new Error(
12326
- `Attachment path is in a protected location: "${filePath}". Hidden files and credential/configuration locations cannot be sent as attachments.`
12361
+ `Attachment path is in a protected location: "${filePath}". Hidden files, credential locations and user Library folders (except iCloud Drive and CloudStorage) cannot be sent as attachments unless ${ATTACHMENT_READ_ROOTS_ENV} names a root for them.`
12327
12362
  );
12328
12363
  }
12329
- if (!configuredRoots(env).some((root) => isWithinRoot(canonical, root))) {
12364
+ const roots = [...canonicalRoots(DEFAULT_ATTACHMENT_READ_ROOTS), ...explicit];
12365
+ if (!roots.some((root) => isWithinRoot(canonical, root))) {
12330
12366
  throw new Error(
12331
12367
  `Attachment path is outside the allowed read roots: "${filePath}". Use an ordinary home-directory, /Volumes, or temporary path, or configure ${ATTACHMENT_READ_ROOTS_ENV} for an additional explicit root.`
12332
12368
  );
12333
12369
  }
12334
12370
  return canonical;
12335
12371
  }
12336
- var ATTACHMENT_READ_ROOTS_ENV, DEFAULT_ATTACHMENT_READ_ROOTS, SENSITIVE_HOME_ROOTS;
12372
+ var ATTACHMENT_READ_ROOTS_ENV, DEFAULT_ATTACHMENT_READ_ROOTS, SENSITIVE_HOME_ROOTS, LIBRARY_DOCUMENT_FOLDERS;
12337
12373
  var init_attachmentReadPolicy = __esm({
12338
12374
  "src/utils/attachmentReadPolicy.ts"() {
12339
12375
  "use strict";
@@ -12345,6 +12381,7 @@ var init_attachmentReadPolicy = __esm({
12345
12381
  join(homedir(), ".config", "gh"),
12346
12382
  join(homedir(), "Library", "Keychains")
12347
12383
  ];
12384
+ LIBRARY_DOCUMENT_FOLDERS = [["mobile documents", "com~apple~clouddocs"], ["cloudstorage"]];
12348
12385
  }
12349
12386
  });
12350
12387
 
package/build/index.js CHANGED
@@ -64830,44 +64830,77 @@ function sensitiveRoots() {
64830
64830
  }
64831
64831
  });
64832
64832
  }
64833
+ function homeCanonical() {
64834
+ try {
64835
+ return canonicalize(homedir());
64836
+ } catch {
64837
+ return void 0;
64838
+ }
64839
+ }
64833
64840
  function isWithinRoot(candidate, root) {
64834
64841
  return candidate === root || candidate.startsWith(root + sep);
64835
64842
  }
64836
64843
  function hasHiddenPathSegment(candidate) {
64837
64844
  return candidate.split(sep).some((segment) => segment.startsWith(".") && segment.length > 1);
64838
64845
  }
64839
- function isProtectedPath(candidate) {
64846
+ function userLibraryTail(candidate) {
64847
+ const segments = candidate.split(sep).filter(Boolean);
64848
+ const lower = segments.map((segment) => segment.toLowerCase());
64849
+ const volumeTops = [
64850
+ 0,
64851
+ lower[0] === "volumes" ? 2 : -1,
64852
+ lower.slice(0, 3).join("/") === "system/volumes/data" ? 3 : -1
64853
+ ];
64854
+ for (const i of volumeTops) {
64855
+ if (i >= 0 && lower[i] === "users" && lower[i + 2] === "library") return lower.slice(i + 3);
64856
+ }
64857
+ const home = homeCanonical() ?? resolve3(homedir());
64858
+ if (home === sep || !isWithinRoot(candidate, home)) return void 0;
64859
+ const relative = candidate.slice(home.length).split(sep).filter(Boolean);
64860
+ if (relative[0]?.toLowerCase() !== "library") return void 0;
64861
+ return relative.slice(1).map((segment) => segment.toLowerCase());
64862
+ }
64863
+ function isLibraryDocument(tail) {
64864
+ return LIBRARY_DOCUMENT_FOLDERS.some((folder) => folder.every((name2, i) => tail[i] === name2));
64865
+ }
64866
+ function isProtectedPath(candidate, exemptRoots = []) {
64840
64867
  if (hasHiddenPathSegment(candidate)) return true;
64841
64868
  if (sensitiveRoots().some((root) => isWithinRoot(candidate, root))) return true;
64842
- let home;
64869
+ const tail = userLibraryTail(candidate);
64870
+ if (!tail || isLibraryDocument(tail)) return false;
64871
+ const home = homeCanonical();
64872
+ return !exemptRoots.some(
64873
+ (root) => isWithinRoot(candidate, root) && !(home && isWithinRoot(home, root))
64874
+ );
64875
+ }
64876
+ function canonicalTmpdir() {
64843
64877
  try {
64844
- home = canonicalize(homedir());
64878
+ return [canonicalize(tmpdir())];
64845
64879
  } catch {
64846
- home = resolve3(homedir());
64880
+ return [];
64847
64881
  }
64848
- if (!isWithinRoot(candidate, home)) return false;
64849
- const relative = candidate.slice(home.length).split(sep).filter(Boolean);
64850
- return relative.length >= 4 && relative[0].toLowerCase() === "library" && relative[1].toLowerCase() === "application support" && relative.at(-1)?.toLowerCase() === "config.json";
64851
64882
  }
64852
- function configuredRoots(env) {
64853
- const raw = env[ATTACHMENT_READ_ROOTS_ENV];
64854
- const extraRoots = raw === void 0 ? [] : raw.split(delimiter2).map((root) => root.trim()).filter(Boolean);
64855
- const requested = [...DEFAULT_ATTACHMENT_READ_ROOTS, ...extraRoots];
64856
- for (const root of requested) {
64857
- if (!isAbsolute(root)) {
64858
- throw new Error(`${ATTACHMENT_READ_ROOTS_ENV} entries must be absolute paths.`);
64859
- }
64860
- }
64883
+ function canonicalRoots(requested) {
64861
64884
  const resolved = [];
64862
64885
  for (const root of requested) {
64863
64886
  try {
64864
64887
  const canonical = canonicalize(resolve3(root));
64865
- if (!resolved.includes(canonical)) resolved.push(canonical);
64888
+ if (canonical !== sep && !resolved.includes(canonical)) resolved.push(canonical);
64866
64889
  } catch {
64867
64890
  }
64868
64891
  }
64869
64892
  return resolved;
64870
64893
  }
64894
+ function explicitRoots(env) {
64895
+ const raw = env[ATTACHMENT_READ_ROOTS_ENV];
64896
+ const extraRoots = raw === void 0 ? [] : raw.split(delimiter2).map((root) => root.trim()).filter(Boolean);
64897
+ for (const root of extraRoots) {
64898
+ if (!isAbsolute(root)) {
64899
+ throw new Error(`${ATTACHMENT_READ_ROOTS_ENV} entries must be absolute paths.`);
64900
+ }
64901
+ }
64902
+ return canonicalRoots(extraRoots);
64903
+ }
64871
64904
  function resolveAttachmentReadPath(filePath, env = process.env) {
64872
64905
  if (!isAbsolute(filePath)) {
64873
64906
  throw new Error(`Attachment path must be absolute: "${filePath}"`);
@@ -64886,19 +64919,22 @@ function resolveAttachmentReadPath(filePath, env = process.env) {
64886
64919
  if (error3 instanceof Error && error3.message.includes("not a regular file")) throw error3;
64887
64920
  throw new Error(`Attachment file not found: "${filePath}"`);
64888
64921
  }
64889
- if (isProtectedPath(canonical)) {
64922
+ const explicit = explicitRoots(env);
64923
+ const exempt = [...explicit, ...canonicalTmpdir()];
64924
+ if (isProtectedPath(canonical, exempt) || isProtectedPath(resolve3(filePath), exempt)) {
64890
64925
  throw new Error(
64891
- `Attachment path is in a protected location: "${filePath}". Hidden files and credential/configuration locations cannot be sent as attachments.`
64926
+ `Attachment path is in a protected location: "${filePath}". Hidden files, credential locations and user Library folders (except iCloud Drive and CloudStorage) cannot be sent as attachments unless ${ATTACHMENT_READ_ROOTS_ENV} names a root for them.`
64892
64927
  );
64893
64928
  }
64894
- if (!configuredRoots(env).some((root) => isWithinRoot(canonical, root))) {
64929
+ const roots = [...canonicalRoots(DEFAULT_ATTACHMENT_READ_ROOTS), ...explicit];
64930
+ if (!roots.some((root) => isWithinRoot(canonical, root))) {
64895
64931
  throw new Error(
64896
64932
  `Attachment path is outside the allowed read roots: "${filePath}". Use an ordinary home-directory, /Volumes, or temporary path, or configure ${ATTACHMENT_READ_ROOTS_ENV} for an additional explicit root.`
64897
64933
  );
64898
64934
  }
64899
64935
  return canonical;
64900
64936
  }
64901
- var ATTACHMENT_READ_ROOTS_ENV, DEFAULT_ATTACHMENT_READ_ROOTS, SENSITIVE_HOME_ROOTS;
64937
+ var ATTACHMENT_READ_ROOTS_ENV, DEFAULT_ATTACHMENT_READ_ROOTS, SENSITIVE_HOME_ROOTS, LIBRARY_DOCUMENT_FOLDERS;
64902
64938
  var init_attachmentReadPolicy = __esm({
64903
64939
  "src/utils/attachmentReadPolicy.ts"() {
64904
64940
  "use strict";
@@ -64910,6 +64946,7 @@ var init_attachmentReadPolicy = __esm({
64910
64946
  join(homedir(), ".config", "gh"),
64911
64947
  join(homedir(), "Library", "Keychains")
64912
64948
  ];
64949
+ LIBRARY_DOCUMENT_FOLDERS = [["mobile documents", "com~apple~clouddocs"], ["cloudstorage"]];
64913
64950
  }
64914
64951
  });
64915
64952
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.20.22",
3
+ "version": "2.20.23",
4
4
  "description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",