apple-mail-mcp 2.11.1 → 2.12.0

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.
Files changed (2) hide show
  1. package/build/index.js +118 -40
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -80955,20 +80955,15 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80955
80955
  replyToMessage(id, body, replyAll = false, send = true) {
80956
80956
  const safeBody = escapeForAppleScriptBody(body);
80957
80957
  const replyAllClause = replyAll ? " with reply to all" : "";
80958
- const sendAction = send ? "send theReply" : "";
80958
+ const finalAction = send ? "send theReply" : "save theReply";
80959
80959
  const script = this.findMessageScript(
80960
80960
  id,
80961
80961
  `
80962
80962
  set theReply to reply msg without opening window${replyAllClause}
80963
80963
  set content of theReply to "${safeBody}"
80964
- ${sendAction}`
80964
+ ${finalAction}`
80965
80965
  );
80966
- const result = executeAppleScript(script, { timeoutMs: 6e4 });
80967
- if (!result.success || result.output.startsWith("error:")) {
80968
- console.error(`Failed to reply to message: ${result.error || result.output}`);
80969
- return false;
80970
- }
80971
- return true;
80966
+ return this.runComposeScript(script, "reply to");
80972
80967
  }
80973
80968
  /**
80974
80969
  * Forward a message.
@@ -80981,7 +80976,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80981
80976
  */
80982
80977
  forwardMessage(id, to, body, send = true) {
80983
80978
  const safeBody = body ? escapeForAppleScriptBody(body) : "";
80984
- const sendAction = send ? "send theForward" : "";
80979
+ const finalAction = send ? "send theForward" : "save theForward";
80985
80980
  let recipientCommands = "";
80986
80981
  for (const addr of to) {
80987
80982
  recipientCommands += `make new to recipient at end of to recipients of theForward with properties {address:"${escapeForAppleScript(addr)}"}
@@ -80993,14 +80988,9 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80993
80988
  set theForward to forward msg without opening window
80994
80989
  ${recipientCommands}
80995
80990
  ${safeBody ? `set content of theForward to "${safeBody}"` : ""}
80996
- ${sendAction}`
80991
+ ${finalAction}`
80997
80992
  );
80998
- const result = executeAppleScript(script, { timeoutMs: 6e4 });
80999
- if (!result.success || result.output.startsWith("error:")) {
81000
- console.error(`Failed to forward message: ${result.error || result.output}`);
81001
- return false;
81002
- }
81003
- return true;
80993
+ return this.runComposeScript(script, "forward");
81004
80994
  }
81005
80995
  /**
81006
80996
  * Helper to find and operate on a message by ID, scoped to the mailbox the id
@@ -81025,6 +81015,25 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
81025
81015
  * it immediately, so this is also where the previous operation's forensic
81026
81016
  * report is invalidated — see `beginMutation()`.
81027
81017
  */
81018
+ /**
81019
+ * Run a reply/forward compose script and surface Mail's OWN error text.
81020
+ *
81021
+ * These used to return a bare boolean and log the reason to stderr, so the
81022
+ * tool layer could only say "Failed to reply to message X". That hid the two
81023
+ * failures a caller can actually act on — an id present in several mailboxes
81024
+ * (which names the candidates and tells you to re-list) and a missing id —
81025
+ * behind one indistinguishable message.
81026
+ */
81027
+ runComposeScript(script, verb) {
81028
+ const result = executeAppleScript(script, { timeoutMs: 6e4 });
81029
+ if (!result.success || result.output.startsWith("error:")) {
81030
+ const raw = result.error || result.output;
81031
+ const error2 = raw.startsWith("error:") ? raw.slice("error:".length) : raw;
81032
+ console.error(`Failed to ${verb} message: ${error2}`);
81033
+ return { success: false, error: error2 };
81034
+ }
81035
+ return { success: true };
81036
+ }
81028
81037
  findMessageScript(id, operation, instrument = false) {
81029
81038
  this.beginMutation();
81030
81039
  const loc = this.locationFor(id);
@@ -83829,6 +83838,10 @@ function imapMailStats(deps = {}) {
83829
83838
  function errText(e) {
83830
83839
  return e instanceof Error ? e.message : String(e);
83831
83840
  }
83841
+ function assertMutated(result, what) {
83842
+ if (!result) throw new Error(`${what}: server rejected the command (IMAP NO/BAD)`);
83843
+ return result;
83844
+ }
83832
83845
  var poolConnect = defaultConnect;
83833
83846
  var pools = /* @__PURE__ */ new Map();
83834
83847
  function poolKey(cfg) {
@@ -84167,7 +84180,10 @@ async function imapMoveMessageById(id, destMailbox, deps = {}) {
84167
84180
  const destPath = dest.kind === "found" ? dest.path : resolveMailboxPath(destMailbox, "list");
84168
84181
  const lock = await client.getMailboxLock(ref.path);
84169
84182
  try {
84170
- await client.messageMove([ref.uid], destPath, { uid: true });
84183
+ assertMutated(
84184
+ await client.messageMove([ref.uid], destPath, { uid: true }),
84185
+ `IMAP move of UID ${ref.uid} to "${destPath}"`
84186
+ );
84171
84187
  return { success: true, info: `Moved UID ${ref.uid} to "${destPath}" via IMAP.` };
84172
84188
  } catch (e) {
84173
84189
  return {
@@ -84179,9 +84195,12 @@ async function imapMoveMessageById(id, destMailbox, deps = {}) {
84179
84195
  }
84180
84196
  });
84181
84197
  }
84198
+ var FALLBACK_TRASH_PATH = "Trash";
84182
84199
  async function resolveTrashPath(client) {
84200
+ let listed = false;
84183
84201
  try {
84184
84202
  const boxes = await client.list();
84203
+ listed = true;
84185
84204
  const special = boxes.find((b) => b.specialUse === "\\Trash");
84186
84205
  if (special) return special.path;
84187
84206
  const named = boxes.find(
@@ -84190,15 +84209,27 @@ async function resolveTrashPath(client) {
84190
84209
  if (named) return named.path;
84191
84210
  } catch {
84192
84211
  }
84193
- return resolveMailboxPath("trash", "list");
84212
+ if (!listed) return resolveMailboxPath("trash", "list");
84213
+ try {
84214
+ const created = await client.mailboxCreate(FALLBACK_TRASH_PATH);
84215
+ return created?.path || FALLBACK_TRASH_PATH;
84216
+ } catch {
84217
+ return FALLBACK_TRASH_PATH;
84218
+ }
84194
84219
  }
84195
84220
  async function trashUids(client, uids, srcPath) {
84196
84221
  const dest = await resolveTrashPath(client);
84197
84222
  if (srcPath.trim().toLowerCase() === dest.trim().toLowerCase()) {
84198
- await client.messageDelete(uids, { uid: true });
84223
+ assertMutated(
84224
+ await client.messageDelete(uids, { uid: true }),
84225
+ `IMAP expunge of ${uids.length} message(s) from "${srcPath}"`
84226
+ );
84199
84227
  return { dest, expunged: true };
84200
84228
  }
84201
- await client.messageMove(uids, dest, { uid: true });
84229
+ assertMutated(
84230
+ await client.messageMove(uids, dest, { uid: true }),
84231
+ `IMAP move of ${uids.length} message(s) from "${srcPath}" to "${dest}"`
84232
+ );
84202
84233
  return { dest, expunged: false };
84203
84234
  }
84204
84235
  async function imapDeleteMessageById(id, deps = {}) {
@@ -84338,22 +84369,37 @@ async function imapBatch(ids, deps, op) {
84338
84369
  return { success, failed, errors };
84339
84370
  }
84340
84371
  var imapBatchMarkRead = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
84341
- await c.messageFlagsAdd(uids, ["\\Seen"], { uid: true });
84372
+ assertMutated(
84373
+ await c.messageFlagsAdd(uids, ["\\Seen"], { uid: true }),
84374
+ `IMAP mark-read of ${uids.length} message(s)`
84375
+ );
84342
84376
  });
84343
84377
  var imapBatchMarkUnread = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
84344
- await c.messageFlagsRemove(uids, ["\\Seen"], { uid: true });
84378
+ assertMutated(
84379
+ await c.messageFlagsRemove(uids, ["\\Seen"], { uid: true }),
84380
+ `IMAP mark-unread of ${uids.length} message(s)`
84381
+ );
84345
84382
  });
84346
84383
  var imapBatchFlag = (ids, colorIndex, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
84347
84384
  if (colorIndex === void 0) {
84348
- await c.messageFlagsAdd(uids, ["\\Flagged"], { uid: true });
84385
+ assertMutated(
84386
+ await c.messageFlagsAdd(uids, ["\\Flagged"], { uid: true }),
84387
+ `IMAP flag of ${uids.length} message(s)`
84388
+ );
84349
84389
  return;
84350
84390
  }
84351
84391
  const { set, clear } = mailFlagBitsFor(colorIndex);
84352
- await c.messageFlagsAdd(uids, ["\\Flagged", ...set], { uid: true });
84392
+ assertMutated(
84393
+ await c.messageFlagsAdd(uids, ["\\Flagged", ...set], { uid: true }),
84394
+ `IMAP flag of ${uids.length} message(s)`
84395
+ );
84353
84396
  if (clear.length) await c.messageFlagsRemove(uids, clear, { uid: true });
84354
84397
  });
84355
84398
  var imapBatchUnflag = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
84356
- await c.messageFlagsRemove(uids, ["\\Flagged", ...MAIL_FLAG_BITS], { uid: true });
84399
+ assertMutated(
84400
+ await c.messageFlagsRemove(uids, ["\\Flagged", ...MAIL_FLAG_BITS], { uid: true }),
84401
+ `IMAP unflag of ${uids.length} message(s)`
84402
+ );
84357
84403
  });
84358
84404
  var imapBatchDelete = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids, path) => {
84359
84405
  await trashUids(c, uids, path);
@@ -84361,7 +84407,10 @@ var imapBatchDelete = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids, p
84361
84407
  function imapBatchMove(ids, destMailbox, deps = {}) {
84362
84408
  return imapBatch(ids, deps, async (c, uids) => {
84363
84409
  const dest = await findMailboxPathOrThrow(c, destMailbox) ?? resolveMailboxPath(destMailbox, "list");
84364
- await c.messageMove(uids, dest, { uid: true });
84410
+ assertMutated(
84411
+ await c.messageMove(uids, dest, { uid: true }),
84412
+ `IMAP move of ${uids.length} message(s) to "${dest}"`
84413
+ );
84365
84414
  });
84366
84415
  }
84367
84416
  function senderName(from) {
@@ -85940,6 +85989,23 @@ function resolveSmtpOrFallback() {
85940
85989
  return null;
85941
85990
  }
85942
85991
  }
85992
+ async function toNumericMailId(id) {
85993
+ const ref = decodeImapId(id);
85994
+ if (!ref) return { numericId: id };
85995
+ const messageId = await imapFetchMessageId(id);
85996
+ if (!messageId) {
85997
+ return {
85998
+ error: `could not read the RFC Message-ID for "${id}" over IMAP, which is what maps it to a Mail.app id. Pass the numeric id instead (see the resolve-message-id tool).`
85999
+ };
86000
+ }
86001
+ const numericId = mailManager.findNumericIdByMessageId(messageId, ref.account);
86002
+ if (!numericId) {
86003
+ return {
86004
+ error: `Mail.app has no message with Message-ID <${messageId}> in account "${ref.account}", so this IMAP message has no numeric id to reply to or forward. It may not have synced to Mail.app yet.`
86005
+ };
86006
+ }
86007
+ return { numericId };
86008
+ }
85943
86009
  async function sendReplyViaSmtp(id, body, replyAll) {
85944
86010
  const cfg = resolveSmtpOrFallback();
85945
86011
  if (!cfg) return { sent: false, fallback: true };
@@ -85998,17 +86064,23 @@ registerTool(
85998
86064
  },
85999
86065
  withErrorHandling(async ({ id, body, replyAll, send }) => {
86000
86066
  if (send && isSmtpConfigured()) {
86001
- const outcome = await sendReplyViaSmtp(id, body, replyAll);
86002
- if (outcome.sent) {
86067
+ const outcome2 = await sendReplyViaSmtp(id, body, replyAll);
86068
+ if (outcome2.sent) {
86003
86069
  return successResponse("Reply sent", { ok: true, sent: true, id });
86004
86070
  }
86005
- if (!outcome.fallback) {
86006
- return errorResponse(`Failed to reply to message "${id}" via SMTP: ${outcome.error}`);
86071
+ if (!outcome2.fallback) {
86072
+ return errorResponse(`Failed to reply to message "${id}" via SMTP: ${outcome2.error}`);
86007
86073
  }
86008
86074
  }
86009
- const success = mailManager.replyToMessage(id, body, replyAll, send);
86010
- if (!success) {
86011
- return errorResponse(`Failed to reply to message "${id}"`);
86075
+ const resolvedReply = await toNumericMailId(id);
86076
+ if (!resolvedReply.numericId) {
86077
+ return errorResponse(`Failed to reply to message "${id}": ${resolvedReply.error}`);
86078
+ }
86079
+ const outcome = mailManager.replyToMessage(resolvedReply.numericId, body, replyAll, send);
86080
+ if (!outcome.success) {
86081
+ return errorResponse(
86082
+ outcome.error ? `Failed to reply to message "${id}": ${outcome.error}` : `Failed to reply to message "${id}"`
86083
+ );
86012
86084
  }
86013
86085
  return successResponse(send ? "Reply sent" : "Reply saved as draft", {
86014
86086
  ok: true,
@@ -86036,8 +86108,8 @@ registerTool(
86036
86108
  },
86037
86109
  withErrorHandling(async ({ id, to, body, send }) => {
86038
86110
  if (send && isSmtpConfigured()) {
86039
- const outcome = await sendForwardViaSmtp(id, to, body);
86040
- if (outcome.sent) {
86111
+ const outcome2 = await sendForwardViaSmtp(id, to, body);
86112
+ if (outcome2.sent) {
86041
86113
  return successResponse(`Message forwarded to ${to.join(", ")}`, {
86042
86114
  ok: true,
86043
86115
  sent: true,
@@ -86045,13 +86117,19 @@ registerTool(
86045
86117
  id
86046
86118
  });
86047
86119
  }
86048
- if (!outcome.fallback) {
86049
- return errorResponse(`Failed to forward message "${id}" via SMTP: ${outcome.error}`);
86120
+ if (!outcome2.fallback) {
86121
+ return errorResponse(`Failed to forward message "${id}" via SMTP: ${outcome2.error}`);
86050
86122
  }
86051
86123
  }
86052
- const success = mailManager.forwardMessage(id, to, body, send);
86053
- if (!success) {
86054
- return errorResponse(`Failed to forward message "${id}"`);
86124
+ const resolvedFwd = await toNumericMailId(id);
86125
+ if (!resolvedFwd.numericId) {
86126
+ return errorResponse(`Failed to forward message "${id}": ${resolvedFwd.error}`);
86127
+ }
86128
+ const outcome = mailManager.forwardMessage(resolvedFwd.numericId, to, body, send);
86129
+ if (!outcome.success) {
86130
+ return errorResponse(
86131
+ outcome.error ? `Failed to forward message "${id}": ${outcome.error}` : `Failed to forward message "${id}"`
86132
+ );
86055
86133
  }
86056
86134
  return successResponse(
86057
86135
  send ? `Message forwarded to ${to.join(", ")}` : "Forward saved as draft",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.11.1",
3
+ "version": "2.12.0",
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",