apple-mail-mcp 1.6.8 → 1.6.10

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/README.md CHANGED
@@ -800,6 +800,7 @@ The entrypoint is written as:
800
800
  | Attachments require absolute paths | File attachments must use full absolute paths (e.g., `/Users/me/file.pdf`) |
801
801
  | No smart mailboxes | Cannot access Smart Mailboxes via AppleScript |
802
802
  | Very large mailboxes not searchable | Apple Mail's AppleScript bridge times out on mailboxes with tens of thousands of messages, so unscoped `search-messages` skips mailboxes above `APPLE_MAIL_MAX_SEARCH_MAILBOX` (default 5000) and reports them as a partial result. Scope with `mailbox` + a date window to search inside one. ([#24](https://github.com/sweetrb/apple-mail-mcp/issues/24)) |
803
+ | Can't delete/rename server-side mailboxes or mutate drafts | Mail.app's AppleScript bridge can only `delete`/`rename` **local "On My Mac"** mailboxes and cannot delete/move drafts — it throws `AppleEvent handler failed` for IMAP/Gmail/Workspace/iCloud/Exchange mailboxes and drafts (the GUI can do it). `delete-mailbox`/`rename-mailbox`/`delete-message`/`move-message` now return a clear "do it in Mail.app directly" error in that case instead of a generic failure. ([#42](https://github.com/sweetrb/apple-mail-mcp/issues/42)) |
803
804
  | In-memory templates | Email templates are not persisted across server restarts |
804
805
  | Numeric-only message IDs | Message IDs must contain only digits (validated by schema) |
805
806
  | Batch size cap | Batch operations are limited to 100 messages per request |
package/build/index.js CHANGED
@@ -383,9 +383,9 @@ server.tool("unflag-message", {
383
383
  server.tool("delete-message", {
384
384
  id: MESSAGE_ID_SCHEMA,
385
385
  }, withErrorHandling(({ id }) => {
386
- const success = mailManager.deleteMessage(id);
386
+ const { success, error } = mailManager.deleteMessage(id);
387
387
  if (!success) {
388
- return errorResponse(`Failed to delete message "${id}"`);
388
+ return errorResponse(error || `Failed to delete message "${id}"`);
389
389
  }
390
390
  return successResponse("Message deleted");
391
391
  }, "Error deleting message"));
@@ -395,9 +395,9 @@ server.tool("move-message", {
395
395
  mailbox: z.string().min(1, "Destination mailbox is required"),
396
396
  account: z.string().optional().describe("Account containing the destination mailbox"),
397
397
  }, withErrorHandling(({ id, mailbox, account }) => {
398
- const success = mailManager.moveMessage(id, mailbox, account);
398
+ const { success, error } = mailManager.moveMessage(id, mailbox, account);
399
399
  if (!success) {
400
- return errorResponse(`Failed to move message to "${mailbox}"`);
400
+ return errorResponse(error || `Failed to move message to "${mailbox}"`);
401
401
  }
402
402
  return successResponse(`Message moved to "${mailbox}"`);
403
403
  }, "Error moving message"));
@@ -572,9 +572,9 @@ server.tool("delete-mailbox", {
572
572
  name: z.string().min(1, "Mailbox name is required"),
573
573
  account: z.string().optional().describe("Account containing the mailbox"),
574
574
  }, withErrorHandling(({ name, account }) => {
575
- const success = mailManager.deleteMailbox(name, account);
575
+ const { success, error } = mailManager.deleteMailbox(name, account);
576
576
  if (!success) {
577
- return errorResponse(`Failed to delete mailbox "${name}"`);
577
+ return errorResponse(error || `Failed to delete mailbox "${name}"`);
578
578
  }
579
579
  return successResponse(`Mailbox "${name}" deleted`);
580
580
  }, "Error deleting mailbox"));
@@ -584,9 +584,9 @@ server.tool("rename-mailbox", {
584
584
  newName: z.string().min(1, "New mailbox name is required"),
585
585
  account: z.string().optional().describe("Account containing the mailbox"),
586
586
  }, withErrorHandling(({ oldName, newName, account }) => {
587
- const success = mailManager.renameMailbox(oldName, newName, account);
587
+ const { success, error } = mailManager.renameMailbox(oldName, newName, account);
588
588
  if (!success) {
589
- return errorResponse(`Failed to rename mailbox "${oldName}" to "${newName}"`);
589
+ return errorResponse(error || `Failed to rename mailbox "${oldName}" to "${newName}"`);
590
590
  }
591
591
  return successResponse(`Mailbox renamed from "${oldName}" to "${newName}"`);
592
592
  }, "Error renaming mailbox"));
@@ -36,6 +36,25 @@ export declare function splitSearchDiagnostics(output: string, account: string):
36
36
  payload: string;
37
37
  diagnostics: SearchDiagnostics;
38
38
  };
39
+ /**
40
+ * True if `resolvedPath` is one of the allowed roots or strictly inside one.
41
+ *
42
+ * Uses a path-segment boundary check rather than a bare `startsWith`, which
43
+ * would let a sibling whose name merely shares the prefix slip through —
44
+ * `/Volumes-evil` startsWith `/Volumes`, `/Users/robother` startsWith
45
+ * `/Users/rob` (audit finding #12). `resolvedPath` must already be absolute
46
+ * (caller passes `resolve(...)` output).
47
+ */
48
+ export declare function isPathWithinAllowedRoots(resolvedPath: string): boolean;
49
+ /**
50
+ * Turn a raw mailbox delete/rename failure into an actionable, non-retryable
51
+ * message when it's the known server-side-mailbox limitation (#42); otherwise
52
+ * return the raw error unchanged.
53
+ *
54
+ * Exported for unit testing.
55
+ */
56
+ export declare function describeMailboxOpError(op: "delete" | "rename", raw: string): string;
57
+ export declare function escapeForAppleScript(text: string): string;
39
58
  /**
40
59
  * Emits AppleScript that builds a date into the variable `varName` from numeric
41
60
  * components.
@@ -317,7 +336,21 @@ export declare class AppleMailManager {
317
336
  /**
318
337
  * Delete a message.
319
338
  */
320
- deleteMessage(id: string): boolean;
339
+ deleteMessage(id: string): {
340
+ success: boolean;
341
+ error?: string;
342
+ };
343
+ /**
344
+ * Classify a failed message mutation (delete/move) into an actionable error.
345
+ *
346
+ * Mail.app's scripting bridge cannot delete or move drafts, and cannot mutate
347
+ * messages in some server-side special mailboxes — it throws `AppleEvent
348
+ * handler failed` rather than a useful message (#42). When that pattern is
349
+ * seen, look up the message's mailbox (cheap, indexed `whose id is`) to give a
350
+ * draft-specific or server-specific hint. Other errors (e.g. "Message not
351
+ * found", "ambiguous destination") pass through unchanged.
352
+ */
353
+ private classifyMessageMutationError;
321
354
  /**
322
355
  * Move a message to a different mailbox.
323
356
  */
@@ -340,7 +373,10 @@ export declare class AppleMailManager {
340
373
  * (destination not found / ambiguous / message not found).
341
374
  */
342
375
  private moveMessageInternal;
343
- moveMessage(id: string, mailbox: string, account?: string): boolean;
376
+ moveMessage(id: string, mailbox: string, account?: string): {
377
+ success: boolean;
378
+ error?: string;
379
+ };
344
380
  /**
345
381
  * Run one operation over many message IDs in a SINGLE osascript invocation.
346
382
  *
@@ -413,11 +449,17 @@ export declare class AppleMailManager {
413
449
  /**
414
450
  * Delete a mailbox.
415
451
  */
416
- deleteMailbox(name: string, account?: string): boolean;
452
+ deleteMailbox(name: string, account?: string): {
453
+ success: boolean;
454
+ error?: string;
455
+ };
417
456
  /**
418
457
  * Rename a mailbox by creating a new one, moving messages, and deleting the old one.
419
458
  */
420
- renameMailbox(oldName: string, newName: string, account?: string): boolean;
459
+ renameMailbox(oldName: string, newName: string, account?: string): {
460
+ success: boolean;
461
+ error?: string;
462
+ };
421
463
  /**
422
464
  * List all mail accounts (uses cache).
423
465
  */
@@ -1 +1 @@
1
- {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,OAAO,EACP,OAAO,EACP,UAAU,EACV,iBAAiB,EACjB,SAAS,EAET,oBAAoB,EACpB,UAAU,EACV,qBAAqB,EACrB,QAAQ,EACR,OAAO,EACP,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACb,MAAM,YAAY,CAAC;AA2DpB;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAK7F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,iBAAiB,CAAA;CAAE,CAsCrD;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAKtE;AAWD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOnF;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAczD;AAiED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAWrE;AA2CD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAoB5E;AAED,qBAAa,gBAAgB;IAC3B;;OAEG;IACH,OAAO,CAAC,cAAc,CAAuB;IAE7C;;;;OAIG;IACH,OAAO,CAAC,KAAK,CAGX;IAEF,8CAA8C;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IAEvC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAW7B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAKvB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAwCtB;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,cAAc;IAyCtB;;;;;;;;OAQG;IACH,cAAc,CACZ,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,KAAK,SAAK,EACV,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,OAAO,GAClB,OAAO,EAAE;IAeZ;;;;;;;;;;;;;;;;;OAiBG;IACH,6BAA6B,CAC3B,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,KAAK,SAAK,EACV,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,OAAO,GAClB,YAAY;IAgMf;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,mBAAmB,UAAQ,GAAG,OAAO,GAAG,IAAI;IA4EvE;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,cAAc,GAAG,IAAI;IAyDzE;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA6BvC;;;;;;;OAOG;IACH,YAAY,CACV,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,KAAK,SAAK,EACV,IAAI,CAAC,EAAE,MAAM,EACb,MAAM,SAAI,GACT,OAAO,EAAE;IAIZ;;;;;;;;;;;OAWG;IACH,2BAA2B,CACzB,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,KAAK,SAAK,EACV,IAAI,CAAC,EAAE,MAAM,EACb,MAAM,SAAI,GACT,YAAY;IAgKf;;;;;;;;;;OAUG;IACH,OAAO,CAAC,gBAAgB;IAoCxB;;;;;;;;;;OAUG;IAuBH,SAAS,CACP,EAAE,EAAE,MAAM,EAAE,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,EAAE,CAAC,EAAE,MAAM,EAAE,EACb,GAAG,CAAC,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO;IA0DV;;;;;;;;;;;;OAYG;IACH,eAAe,CACb,UAAU,EAAE,oBAAoB,EAAE,EAClC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,MAAY,GACpB,iBAAiB,EAAE;IAgDtB;;;;;;;;;;OAUG;IACH,WAAW,CACT,EAAE,EAAE,MAAM,EAAE,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,EAAE,CAAC,EAAE,MAAM,EAAE,EACb,GAAG,CAAC,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO;IAwDV;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,UAAQ,EAAE,IAAI,UAAO,GAAG,OAAO;IAqChF;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,UAAO,GAAG,OAAO;IA2C7E;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAsBzB;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAY/B;;OAEG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAYjC;;OAEG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAYhC;;OAEG;IACH,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAYlC;;OAEG;IACH,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAgB/D;;;;;;;;;OASG;IACH,OAAO,CAAC,4BAA4B;IAkBpC;;OAEG;IACH;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,mBAAmB;IAwD3B,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAgBhG;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,iBAAiB;IAoGzB;;OAEG;IACH,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,oBAAoB,EAAE;IAI1D;;;;;;OAMG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,oBAAoB,EAAE;IAsB3F;;OAEG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,oBAAoB,EAAE;IAItD;;OAEG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,oBAAoB,EAAE;IAIxD;;OAEG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,oBAAoB,EAAE;IAIxD;;OAEG;IACH,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,oBAAoB,EAAE;IAI1D;;;;OAIG;IACH,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,EAAE;IAiEzC;;;;OAIG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAmF7E;;OAEG;IACH,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE;IA8C1C;;OAEG;IACH,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IAgC1D;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO;IAyBtD;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IA8BnF;;OAEG;IACH,aAAa,CACX,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAyFvC;;OAEG;IACH,YAAY,IAAI,OAAO,EAAE;IAIzB;;;OAGG;IACH,OAAO,CAAC,aAAa;IA2CrB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAwBzB;;OAEG;IACH,SAAS,IAAI,QAAQ,EAAE;IAiCvB;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;IA+B3D;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE;IAgExC,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,cAAc,CAAK;IAE3B;;OAEG;IACH,aAAa,IAAI,aAAa,EAAE;IAIhC;;OAEG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAI7C;;OAEG;IACH,YAAY,CACV,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,EAAE,CAAC,EAAE,MAAM,EAAE,EACb,EAAE,CAAC,EAAE,MAAM,EAAE,EACb,EAAE,CAAC,EAAE,MAAM,GACV,aAAa;IAOhB;;OAEG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAInC;;OAEG;IACH,WAAW,CACT,EAAE,EAAE,MAAM,EACV,SAAS,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5E,OAAO;IAqBV;;OAEG;IACH,WAAW,IAAI,iBAAiB;IA8EhC;;OAEG;IACH,YAAY,IAAI,SAAS;IA4CzB;;;;;;;OAOG;IACH,wBAAwB,IAAI,qBAAqB;IA6DjD;;;;;;;;;OASG;IACH,aAAa,IAAI,UAAU;CAiE5B"}
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { spawnSync } from "child_process";
16
16
  import { existsSync, writeFileSync } from "fs";
17
- import { isAbsolute, resolve } from "path";
17
+ import { isAbsolute, resolve, sep } from "path";
18
18
  import { homedir } from "os";
19
19
  import { executeAppleScript } from "../utils/applescript.js";
20
20
  import { parseMimeAttachments, extractMimeAttachment, extractHtmlBody } from "../utils/mimeParse.js";
@@ -139,10 +139,61 @@ export function splitSearchDiagnostics(output, account) {
139
139
  * @param text - Raw text to escape
140
140
  * @returns Text safe for AppleScript string embedding
141
141
  */
142
- function escapeForAppleScript(text) {
142
+ /**
143
+ * Roots under which `save-attachment` is permitted to write.
144
+ */
145
+ const ALLOWED_SAVE_ROOTS = [homedir(), "/tmp", "/private/tmp", "/Volumes"];
146
+ /**
147
+ * True if `resolvedPath` is one of the allowed roots or strictly inside one.
148
+ *
149
+ * Uses a path-segment boundary check rather than a bare `startsWith`, which
150
+ * would let a sibling whose name merely shares the prefix slip through —
151
+ * `/Volumes-evil` startsWith `/Volumes`, `/Users/robother` startsWith
152
+ * `/Users/rob` (audit finding #12). `resolvedPath` must already be absolute
153
+ * (caller passes `resolve(...)` output).
154
+ */
155
+ export function isPathWithinAllowedRoots(resolvedPath) {
156
+ return ALLOWED_SAVE_ROOTS.some((root) => {
157
+ const base = root.endsWith(sep) ? root.slice(0, -1) : root;
158
+ return resolvedPath === base || resolvedPath.startsWith(base + sep);
159
+ });
160
+ }
161
+ /**
162
+ * Pattern in a raw AppleScript error that indicates an operation Mail.app's
163
+ * scripting interface simply cannot perform on this target — most often a
164
+ * server-side (IMAP / Gmail / Workspace / iCloud / Exchange) mailbox or a draft.
165
+ * Mail throws `AppleEvent handler failed` (-10000) for these; the GUI can do
166
+ * them, the scripting bridge cannot. See issue #42 and the audit doc.
167
+ */
168
+ const UNSUPPORTED_APPLESCRIPT_OP = /AppleEvent handler failed|-10000/i;
169
+ /**
170
+ * Turn a raw mailbox delete/rename failure into an actionable, non-retryable
171
+ * message when it's the known server-side-mailbox limitation (#42); otherwise
172
+ * return the raw error unchanged.
173
+ *
174
+ * Exported for unit testing.
175
+ */
176
+ export function describeMailboxOpError(op, raw) {
177
+ const trimmed = (raw || "").trim();
178
+ if (UNSUPPORTED_APPLESCRIPT_OP.test(trimmed)) {
179
+ const verb = op === "delete" ? "Delete" : "Rename";
180
+ return `Mail.app cannot ${op} server-side (IMAP / Gmail / Workspace / iCloud / Exchange) mailboxes via AppleScript — only local "On My Mac" mailboxes support this. ${verb} it in Mail.app directly. (Mail.app error: ${trimmed})`;
181
+ }
182
+ return trimmed || `Failed to ${op} mailbox`;
183
+ }
184
+ export function escapeForAppleScript(text) {
143
185
  if (!text)
144
186
  return "";
145
- return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
187
+ // Escape backslash and double-quote for the AppleScript string literal, and
188
+ // strip ASCII control characters. An AppleScript double-quoted literal cannot
189
+ // contain a raw newline, so an interpolated value with a `\n` (or other
190
+ // control char) would terminate the literal early and could inject a
191
+ // statement; stripping them closes that gap (audit finding #10).
192
+ return (text
193
+ .replace(/\\/g, "\\\\")
194
+ .replace(/"/g, '\\"')
195
+ // eslint-disable-next-line no-control-regex
196
+ .replace(/[\x00-\x1f\x7f]/g, ""));
146
197
  }
147
198
  /**
148
199
  * Validates attachment file paths and builds AppleScript commands to attach them.
@@ -1397,11 +1448,41 @@ export class AppleMailManager {
1397
1448
  deleteMessage(id) {
1398
1449
  const script = this.findMessageScript(id, "delete msg");
1399
1450
  const result = executeAppleScript(script, { timeoutMs: 60000 });
1400
- if (!result.success || result.output.startsWith("error:")) {
1401
- console.error(`Failed to delete message: ${result.error || result.output}`);
1402
- return false;
1451
+ if (result.success && !result.output.startsWith("error:")) {
1452
+ return { success: true };
1403
1453
  }
1404
- return true;
1454
+ const raw = result.success
1455
+ ? result.output.replace(/^error:/, "")
1456
+ : result.error || "Unknown error";
1457
+ const error = this.classifyMessageMutationError(id, raw, "delete");
1458
+ console.error(`Failed to delete message: ${error}`);
1459
+ return { success: false, error };
1460
+ }
1461
+ /**
1462
+ * Classify a failed message mutation (delete/move) into an actionable error.
1463
+ *
1464
+ * Mail.app's scripting bridge cannot delete or move drafts, and cannot mutate
1465
+ * messages in some server-side special mailboxes — it throws `AppleEvent
1466
+ * handler failed` rather than a useful message (#42). When that pattern is
1467
+ * seen, look up the message's mailbox (cheap, indexed `whose id is`) to give a
1468
+ * draft-specific or server-specific hint. Other errors (e.g. "Message not
1469
+ * found", "ambiguous destination") pass through unchanged.
1470
+ */
1471
+ classifyMessageMutationError(id, raw, op) {
1472
+ const trimmed = (raw || "").trim();
1473
+ if (!UNSUPPORTED_APPLESCRIPT_OP.test(trimmed))
1474
+ return trimmed || `Failed to ${op} message`;
1475
+ let mailbox = "";
1476
+ try {
1477
+ mailbox = this.getMessageById(id)?.mailbox ?? "";
1478
+ }
1479
+ catch {
1480
+ /* best-effort; fall through to the generic server-side message */
1481
+ }
1482
+ if (/draft/i.test(mailbox)) {
1483
+ return `Mail.app cannot ${op} drafts via AppleScript; ${op} it in Mail.app directly. (Mail.app error: ${trimmed})`;
1484
+ }
1485
+ return `Mail.app cannot ${op} this message via AppleScript (server-side or special mailbox${mailbox ? ` "${mailbox}"` : ""}); ${op} it in Mail.app directly. (Mail.app error: ${trimmed})`;
1405
1486
  }
1406
1487
  /**
1407
1488
  * Move a message to a different mailbox.
@@ -1473,11 +1554,12 @@ export class AppleMailManager {
1473
1554
  return { success: true };
1474
1555
  }
1475
1556
  moveMessage(id, mailbox, account) {
1476
- const { success, error } = this.moveMessageInternal(id, mailbox, account);
1477
- if (!success) {
1478
- console.error(`Failed to move message: ${error}`);
1479
- }
1480
- return success;
1557
+ const res = this.moveMessageInternal(id, mailbox, account);
1558
+ if (res.success)
1559
+ return { success: true };
1560
+ const error = this.classifyMessageMutationError(id, res.error || "Failed to move message", "move");
1561
+ console.error(`Failed to move message: ${error}`);
1562
+ return { success: false, error };
1481
1563
  }
1482
1564
  // ===========================================================================
1483
1565
  // Batch Operations
@@ -1730,9 +1812,7 @@ export class AppleMailManager {
1730
1812
  }
1731
1813
  // Resolve the save path to prevent symlink / ".." traversal bypass
1732
1814
  const resolvedPath = resolve(savePath);
1733
- const allowedPrefixes = [homedir(), "/tmp", "/private/tmp", "/Volumes"];
1734
- const isAllowed = allowedPrefixes.some((prefix) => resolvedPath.startsWith(prefix));
1735
- if (!isAllowed) {
1815
+ if (!isPathWithinAllowedRoots(resolvedPath)) {
1736
1816
  console.error(`Save path "${savePath}" is outside allowed directories`);
1737
1817
  return false;
1738
1818
  }
@@ -1783,8 +1863,7 @@ export class AppleMailManager {
1783
1863
  try {
1784
1864
  const outPath = resolve(resolvedPath, attachmentName);
1785
1865
  // Verify the resolved output path is still within allowed directories
1786
- const isOutAllowed = allowedPrefixes.some((prefix) => outPath.startsWith(prefix));
1787
- if (!isOutAllowed) {
1866
+ if (!isPathWithinAllowedRoots(outPath)) {
1788
1867
  console.error(`Output path "${outPath}" is outside allowed directories`);
1789
1868
  return false;
1790
1869
  }
@@ -1816,7 +1895,10 @@ export class AppleMailManager {
1816
1895
  return mailboxList as text
1817
1896
  `;
1818
1897
  const script = buildAccountScopedScript(targetAccount, listCommand);
1819
- const result = executeAppleScript(script);
1898
+ // Counts every mailbox's message total, so it needs more than the default
1899
+ // 30s on accounts with many/large mailboxes; a timeout here silently
1900
+ // returned an empty list (audit finding #8).
1901
+ const result = executeAppleScript(script, { timeoutMs: 60000 });
1820
1902
  if (!result.success) {
1821
1903
  console.error(`Failed to list mailboxes: ${result.error}`);
1822
1904
  return [];
@@ -1860,7 +1942,9 @@ export class AppleMailManager {
1860
1942
  `;
1861
1943
  }
1862
1944
  const script = buildAccountScopedScript(targetAccount, command);
1863
- const result = executeAppleScript(script);
1945
+ // Summing unread across every mailbox can exceed the default 30s; a timeout
1946
+ // previously degraded silently to 0 ("all read") — audit finding #8.
1947
+ const result = executeAppleScript(script, { timeoutMs: 60000 });
1864
1948
  if (!result.success) {
1865
1949
  console.error(`Failed to get unread count: ${result.error}`);
1866
1950
  return 0;
@@ -1908,11 +1992,15 @@ export class AppleMailManager {
1908
1992
  `);
1909
1993
  const result = executeAppleScript(script);
1910
1994
  if (!result.success || result.output.startsWith("error:")) {
1911
- console.error(`Failed to delete mailbox: ${result.error || result.output}`);
1912
- return false;
1995
+ const raw = result.success
1996
+ ? result.output.replace(/^error:/, "")
1997
+ : result.error || "Unknown error";
1998
+ const error = describeMailboxOpError("delete", raw);
1999
+ console.error(`Failed to delete mailbox: ${error}`);
2000
+ return { success: false, error };
1913
2001
  }
1914
2002
  this.invalidateCache();
1915
- return true;
2003
+ return { success: true };
1916
2004
  }
1917
2005
  /**
1918
2006
  * Rename a mailbox by creating a new one, moving messages, and deleting the old one.
@@ -1921,7 +2009,10 @@ export class AppleMailManager {
1921
2009
  const targetAccount = this.resolveAccount(account);
1922
2010
  // Create the new mailbox
1923
2011
  if (!this.createMailbox(newName, targetAccount)) {
1924
- return false;
2012
+ return {
2013
+ success: false,
2014
+ error: `Could not create the destination mailbox "${newName}" needed for the rename.`,
2015
+ };
1925
2016
  }
1926
2017
  // Move all messages from old to new
1927
2018
  const resolvedOld = this.resolveMailbox(oldName, targetAccount);
@@ -1967,19 +2058,27 @@ export class AppleMailManager {
1967
2058
  // check), so a truncated move is recoverable rather than lossy.
1968
2059
  const result = executeAppleScript(moveScript, { timeoutMs: 120000 });
1969
2060
  if (!result.success || result.output.startsWith("error:")) {
1970
- console.error(`Failed to rename mailbox: ${result.error || result.output}`);
1971
- return false;
2061
+ const raw = result.success
2062
+ ? result.output.replace(/^error:/, "")
2063
+ : result.error || "Unknown error";
2064
+ // The empty destination mailbox we just created is now an orphan; the
2065
+ // source is untouched (delete only runs after a verified-empty move).
2066
+ const error = describeMailboxOpError("rename", raw);
2067
+ console.error(`Failed to rename mailbox: ${error}`);
2068
+ this.invalidateCache();
2069
+ return { success: false, error };
1972
2070
  }
1973
2071
  if (result.output.startsWith("partial")) {
1974
2072
  const parts = result.output.split(FIELD_SEP);
1975
2073
  const remaining = parts[3] ?? "?";
1976
2074
  const total = parts[2] ?? "?";
1977
- console.error(`Failed to rename mailbox: only ${parts[1] ?? "?"} of ${total} messages moved, ${remaining} remain in "${resolvedOld}"; source was NOT deleted (both mailboxes left intact). Retry to move the rest.`);
2075
+ const error = `Only ${parts[1] ?? "?"} of ${total} messages moved, ${remaining} remain in "${resolvedOld}"; the source was NOT deleted (both mailboxes left intact). Retry to move the rest.`;
2076
+ console.error(`Failed to rename mailbox: ${error}`);
1978
2077
  this.invalidateCache(); // the new mailbox now exists and holds the moved messages
1979
- return false;
2078
+ return { success: false, error };
1980
2079
  }
1981
2080
  this.invalidateCache();
1982
- return true;
2081
+ return { success: true };
1983
2082
  }
1984
2083
  // ===========================================================================
1985
2084
  // Account Operations
@@ -2127,18 +2226,22 @@ export class AppleMailManager {
2127
2226
  set pid to id of p
2128
2227
  if seenIds does not contain pid then
2129
2228
  set end of seenIds to pid
2130
- set pName to name of p
2131
- set pEmails to ""
2132
- repeat with e in emails of p
2133
- if pEmails is not "" then set pEmails to pEmails & ","
2134
- set pEmails to pEmails & (value of e)
2135
- end repeat
2136
- set pPhones to ""
2137
- repeat with ph in phones of p
2138
- if pPhones is not "" then set pPhones to pPhones & ","
2139
- set pPhones to pPhones & (value of ph)
2140
- end repeat
2141
- set end of matchedContacts to pName & "${FIELD_SEP}" & pEmails & "${FIELD_SEP}" & pPhones
2229
+ -- Per-person try: one malformed contact (e.g. no emails) must not
2230
+ -- abort the whole search and return an empty list (audit finding #13).
2231
+ try
2232
+ set pName to name of p
2233
+ set pEmails to ""
2234
+ repeat with e in emails of p
2235
+ if pEmails is not "" then set pEmails to pEmails & ","
2236
+ set pEmails to pEmails & (value of e)
2237
+ end repeat
2238
+ set pPhones to ""
2239
+ repeat with ph in phones of p
2240
+ if pPhones is not "" then set pPhones to pPhones & ","
2241
+ set pPhones to pPhones & (value of ph)
2242
+ end repeat
2243
+ set end of matchedContacts to pName & "${FIELD_SEP}" & pEmails & "${FIELD_SEP}" & pPhones
2244
+ end try
2142
2245
  end if
2143
2246
  end repeat
2144
2247
 
@@ -2203,10 +2306,13 @@ export class AppleMailManager {
2203
2306
  const template = this.templates.get(id);
2204
2307
  if (!template)
2205
2308
  return false;
2206
- const to = overrides?.to || template.to || [];
2207
- const cc = overrides?.cc || template.cc;
2208
- const subject = overrides?.subject || template.subject;
2209
- const body = overrides?.body || template.body;
2309
+ // Use `??` (not `||`) for subject/body so an intentional empty-string
2310
+ // override is honored rather than falling back to the template value
2311
+ // (audit finding #14).
2312
+ const to = overrides?.to ?? template.to ?? [];
2313
+ const cc = overrides?.cc ?? template.cc;
2314
+ const subject = overrides?.subject ?? template.subject;
2315
+ const body = overrides?.body ?? template.body;
2210
2316
  if (to.length === 0)
2211
2317
  return false;
2212
2318
  return this.createDraft(to, subject, body, cc);
@@ -2429,7 +2535,9 @@ export class AppleMailManager {
2429
2535
 
2430
2536
  return "running${FIELD_SEP}" & accountCount & "${FIELD_SEP}" & totalMailboxes
2431
2537
  `);
2432
- const result = executeAppleScript(script);
2538
+ // Counts mailboxes across every account; give it headroom over the 30s
2539
+ // default so a slow account doesn't silently report "not syncing" (#8).
2540
+ const result = executeAppleScript(script, { timeoutMs: 60000 });
2433
2541
  if (!result.success) {
2434
2542
  return {
2435
2543
  syncDetected: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "1.6.8",
3
+ "version": "1.6.10",
4
4
  "description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude",
5
5
  "type": "module",
6
6
  "main": "build/index.js",