apple-mail-mcp 2.11.2 → 2.13.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 (3) hide show
  1. package/README.md +29 -3
  2. package/build/index.js +113 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1430,9 +1430,35 @@ Three more honesty rules:
1430
1430
  than list positions — and `expected` stays comparable with the mailbox instead
1431
1431
  of double-counting a duplicate into a false `over`.
1432
1432
 
1433
- `imap:` ids are not reconciled. An IMAP UID names exactly one message in exactly
1434
- one mailbox, so the mis-targeting class this exists for cannot occur there; a
1435
- batch of only `imap:` ids returns no `countDelta` rather than a fabricated one.
1433
+ `imap:` ids are not reconciled by `countDelta`. An IMAP UID names exactly one
1434
+ message in exactly one mailbox, so the mis-targeting class this exists for cannot
1435
+ occur there; a batch of only `imap:` ids returns no `countDelta` rather than a
1436
+ fabricated one.
1437
+
1438
+ They carry their own post-condition check instead. `delete-message` and
1439
+ `move-message` on an `imap:` id return a **`verification`** object in
1440
+ `structuredContent`:
1441
+
1442
+ ```json
1443
+ {
1444
+ "verification": {
1445
+ "verdict": "verified",
1446
+ "how": "COPYUID: UID 5 arrived in \"Archive\" as UID 91"
1447
+ }
1448
+ }
1449
+ ```
1450
+
1451
+ | `verdict` | Meaning |
1452
+ |---|---|
1453
+ | `verified` | The effect was **observed** — either the server's UIDPLUS `COPYUID` named the message's new UID in the destination, or the UID is no longer in the source mailbox. |
1454
+ | `unverified` | The server **accepted** the command and nothing could confirm the effect. Populates `why`. |
1455
+
1456
+ `unverified` is **not a failure** and must not be rendered as one — it means
1457
+ "accepted, no observation either way". It is reported rather than hidden because
1458
+ an absent verification must never read as a successful one, the same rule the
1459
+ collateral diff follows. A message that is still in the source mailbox after an
1460
+ accepted move is reported `unverified` rather than failed, because a Gmail label
1461
+ store can legitimately keep a message visible in an all-mail view after a move.
1436
1462
 
1437
1463
  ### `APPLE_MAIL_MCP_AUDIT_LOG` — opt-in forensic log
1438
1464
 
package/build/index.js CHANGED
@@ -83838,6 +83838,31 @@ function imapMailStats(deps = {}) {
83838
83838
  function errText(e) {
83839
83839
  return e instanceof Error ? e.message : String(e);
83840
83840
  }
83841
+ function assertMutated(result, what) {
83842
+ if (!result) throw new Error(`${what}: server rejected the command (IMAP NO/BAD)`);
83843
+ return result;
83844
+ }
83845
+ async function verifyMoved(client, moved, uid, srcPath, destPath) {
83846
+ const newUid = moved.uidMap?.get(uid);
83847
+ if (newUid !== void 0) {
83848
+ return {
83849
+ verdict: "verified",
83850
+ how: `COPYUID: UID ${uid} arrived in "${destPath}" as UID ${newUid}`
83851
+ };
83852
+ }
83853
+ try {
83854
+ const stillThere = await client.fetchOne(String(uid), { uid: true }, { uid: true });
83855
+ if (!stillThere) {
83856
+ return { verdict: "verified", how: `UID ${uid} is no longer present in "${srcPath}"` };
83857
+ }
83858
+ return {
83859
+ verdict: "unverified",
83860
+ why: `the server accepted the MOVE, but UID ${uid} is still present in "${srcPath}" and this server does not advertise UIDPLUS, so arrival in "${destPath}" could not be confirmed. A Gmail label store can legitimately keep a message in an all-mail view after a move, so this is not reported as a failure`
83861
+ };
83862
+ } catch (e) {
83863
+ return { verdict: "unverified", why: `the post-move check could not run: ${errText(e)}` };
83864
+ }
83865
+ }
83841
83866
  var poolConnect = defaultConnect;
83842
83867
  var pools = /* @__PURE__ */ new Map();
83843
83868
  function poolKey(cfg) {
@@ -84176,8 +84201,16 @@ async function imapMoveMessageById(id, destMailbox, deps = {}) {
84176
84201
  const destPath = dest.kind === "found" ? dest.path : resolveMailboxPath(destMailbox, "list");
84177
84202
  const lock = await client.getMailboxLock(ref.path);
84178
84203
  try {
84179
- await client.messageMove([ref.uid], destPath, { uid: true });
84180
- return { success: true, info: `Moved UID ${ref.uid} to "${destPath}" via IMAP.` };
84204
+ const moved = assertMutated(
84205
+ await client.messageMove([ref.uid], destPath, { uid: true }),
84206
+ `IMAP move of UID ${ref.uid} to "${destPath}"`
84207
+ );
84208
+ const verification = await verifyMoved(client, moved, ref.uid, ref.path, destPath);
84209
+ return {
84210
+ success: true,
84211
+ info: verification.verdict === "verified" ? `Moved UID ${ref.uid} to "${destPath}" via IMAP (verified: ${verification.how}).` : `Moved UID ${ref.uid} to "${destPath}" via IMAP \u2014 UNVERIFIED: ${verification.why}.`,
84212
+ verification
84213
+ };
84181
84214
  } catch (e) {
84182
84215
  return {
84183
84216
  success: false,
@@ -84188,9 +84221,12 @@ async function imapMoveMessageById(id, destMailbox, deps = {}) {
84188
84221
  }
84189
84222
  });
84190
84223
  }
84224
+ var FALLBACK_TRASH_PATH = "Trash";
84191
84225
  async function resolveTrashPath(client) {
84226
+ let listed = false;
84192
84227
  try {
84193
84228
  const boxes = await client.list();
84229
+ listed = true;
84194
84230
  const special = boxes.find((b) => b.specialUse === "\\Trash");
84195
84231
  if (special) return special.path;
84196
84232
  const named = boxes.find(
@@ -84199,26 +84235,55 @@ async function resolveTrashPath(client) {
84199
84235
  if (named) return named.path;
84200
84236
  } catch {
84201
84237
  }
84202
- return resolveMailboxPath("trash", "list");
84238
+ if (!listed) return resolveMailboxPath("trash", "list");
84239
+ try {
84240
+ const created = await client.mailboxCreate(FALLBACK_TRASH_PATH);
84241
+ return created?.path || FALLBACK_TRASH_PATH;
84242
+ } catch {
84243
+ return FALLBACK_TRASH_PATH;
84244
+ }
84203
84245
  }
84204
84246
  async function trashUids(client, uids, srcPath) {
84205
84247
  const dest = await resolveTrashPath(client);
84206
84248
  if (srcPath.trim().toLowerCase() === dest.trim().toLowerCase()) {
84207
- await client.messageDelete(uids, { uid: true });
84249
+ assertMutated(
84250
+ await client.messageDelete(uids, { uid: true }),
84251
+ `IMAP expunge of ${uids.length} message(s) from "${srcPath}"`
84252
+ );
84208
84253
  return { dest, expunged: true };
84209
84254
  }
84210
- await client.messageMove(uids, dest, { uid: true });
84211
- return { dest, expunged: false };
84255
+ const moved = assertMutated(
84256
+ await client.messageMove(uids, dest, { uid: true }),
84257
+ `IMAP move of ${uids.length} message(s) from "${srcPath}" to "${dest}"`
84258
+ );
84259
+ return { dest, expunged: false, moved };
84260
+ }
84261
+ async function verifyExpunged(client, uid, path) {
84262
+ try {
84263
+ const stillThere = await client.fetchOne(String(uid), { uid: true }, { uid: true });
84264
+ if (!stillThere) {
84265
+ return { verdict: "verified", how: `UID ${uid} is no longer present in "${path}"` };
84266
+ }
84267
+ return {
84268
+ verdict: "unverified",
84269
+ why: `the server accepted the EXPUNGE but UID ${uid} is still present in "${path}"`
84270
+ };
84271
+ } catch (e) {
84272
+ return { verdict: "unverified", why: `the post-delete check could not run: ${errText(e)}` };
84273
+ }
84212
84274
  }
84213
84275
  async function imapDeleteMessageById(id, deps = {}) {
84214
84276
  const ref = decodeImapId(id);
84215
84277
  if (!ref) return { success: false, error: `Not an IMAP message id: "${id}".` };
84216
84278
  return withMailbox(ref.path, depsForMessageRef(ref, deps), async (client) => {
84217
84279
  try {
84218
- const { dest, expunged } = await trashUids(client, [ref.uid], ref.path);
84280
+ const { dest, expunged, moved } = await trashUids(client, [ref.uid], ref.path);
84281
+ const verification = expunged || !moved ? await verifyExpunged(client, ref.uid, ref.path) : await verifyMoved(client, moved, ref.uid, ref.path, dest);
84282
+ const what = expunged ? `Permanently deleted UID ${ref.uid} from Trash ("${ref.path}") via IMAP` : `Moved UID ${ref.uid} to Trash ("${dest}") via IMAP`;
84219
84283
  return {
84220
84284
  success: true,
84221
- info: expunged ? `Permanently deleted UID ${ref.uid} from Trash ("${ref.path}") via IMAP.` : `Moved UID ${ref.uid} to Trash ("${dest}") via IMAP.`
84285
+ info: verification.verdict === "verified" ? `${what} (verified: ${verification.how}).` : `${what} \u2014 UNVERIFIED: ${verification.why}.`,
84286
+ verification
84222
84287
  };
84223
84288
  } catch (e) {
84224
84289
  return { success: false, error: `IMAP delete failed for UID ${ref.uid}: ${errText(e)}` };
@@ -84347,22 +84412,37 @@ async function imapBatch(ids, deps, op) {
84347
84412
  return { success, failed, errors };
84348
84413
  }
84349
84414
  var imapBatchMarkRead = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
84350
- await c.messageFlagsAdd(uids, ["\\Seen"], { uid: true });
84415
+ assertMutated(
84416
+ await c.messageFlagsAdd(uids, ["\\Seen"], { uid: true }),
84417
+ `IMAP mark-read of ${uids.length} message(s)`
84418
+ );
84351
84419
  });
84352
84420
  var imapBatchMarkUnread = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
84353
- await c.messageFlagsRemove(uids, ["\\Seen"], { uid: true });
84421
+ assertMutated(
84422
+ await c.messageFlagsRemove(uids, ["\\Seen"], { uid: true }),
84423
+ `IMAP mark-unread of ${uids.length} message(s)`
84424
+ );
84354
84425
  });
84355
84426
  var imapBatchFlag = (ids, colorIndex, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
84356
84427
  if (colorIndex === void 0) {
84357
- await c.messageFlagsAdd(uids, ["\\Flagged"], { uid: true });
84428
+ assertMutated(
84429
+ await c.messageFlagsAdd(uids, ["\\Flagged"], { uid: true }),
84430
+ `IMAP flag of ${uids.length} message(s)`
84431
+ );
84358
84432
  return;
84359
84433
  }
84360
84434
  const { set, clear } = mailFlagBitsFor(colorIndex);
84361
- await c.messageFlagsAdd(uids, ["\\Flagged", ...set], { uid: true });
84435
+ assertMutated(
84436
+ await c.messageFlagsAdd(uids, ["\\Flagged", ...set], { uid: true }),
84437
+ `IMAP flag of ${uids.length} message(s)`
84438
+ );
84362
84439
  if (clear.length) await c.messageFlagsRemove(uids, clear, { uid: true });
84363
84440
  });
84364
84441
  var imapBatchUnflag = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
84365
- await c.messageFlagsRemove(uids, ["\\Flagged", ...MAIL_FLAG_BITS], { uid: true });
84442
+ assertMutated(
84443
+ await c.messageFlagsRemove(uids, ["\\Flagged", ...MAIL_FLAG_BITS], { uid: true }),
84444
+ `IMAP unflag of ${uids.length} message(s)`
84445
+ );
84366
84446
  });
84367
84447
  var imapBatchDelete = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids, path) => {
84368
84448
  await trashUids(c, uids, path);
@@ -84370,7 +84450,10 @@ var imapBatchDelete = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids, p
84370
84450
  function imapBatchMove(ids, destMailbox, deps = {}) {
84371
84451
  return imapBatch(ids, deps, async (c, uids) => {
84372
84452
  const dest = await findMailboxPathOrThrow(c, destMailbox) ?? resolveMailboxPath(destMailbox, "list");
84373
- await c.messageMove(uids, dest, { uid: true });
84453
+ assertMutated(
84454
+ await c.messageMove(uids, dest, { uid: true }),
84455
+ `IMAP move of ${uids.length} message(s) to "${dest}"`
84456
+ );
84374
84457
  });
84375
84458
  }
84376
84459
  function senderName(from) {
@@ -84780,10 +84863,12 @@ function formatMergedRows(rows, showReadState = true) {
84780
84863
  async function routeMessage(id, opts) {
84781
84864
  if (decodeImapId(id)) {
84782
84865
  const r = await opts.imap();
84783
- return r.success ? successResponse(
84866
+ if (!r.success) return errorResponse(r.error ?? opts.fail);
84867
+ const structured = opts.structuredFromResult ? opts.structuredFromResult(r) : opts.structured;
84868
+ return successResponse(
84784
84869
  r.info ?? opts.ok,
84785
- opts.structuredFromResult ? opts.structuredFromResult(r) : opts.structured
84786
- ) : errorResponse(r.error ?? opts.fail);
84870
+ r.verification && structured ? { ...structured, verification: r.verification } : structured
84871
+ );
84787
84872
  }
84788
84873
  return opts.apple();
84789
84874
  }
@@ -85347,6 +85432,13 @@ var COUNT_DELTA_OUTPUT_SCHEMA = external_exports.array(
85347
85432
  note: external_exports.string().optional()
85348
85433
  })
85349
85434
  ).optional();
85435
+ var VERIFICATION_OUTPUT_SCHEMA = external_exports.object({
85436
+ verdict: external_exports.enum(["verified", "unverified"]),
85437
+ /** Present on `verified`: what was observed. */
85438
+ how: external_exports.string().optional(),
85439
+ /** Present on `unverified`: why no observation was possible. */
85440
+ why: external_exports.string().optional()
85441
+ }).optional();
85350
85442
  var CHECK_ITEM_SCHEMA = external_exports.object({}).passthrough();
85351
85443
  var require2 = createRequire(import.meta.url);
85352
85444
  var { version: version2 } = require2("../package.json");
@@ -86199,7 +86291,8 @@ registerTool(
86199
86291
  outputSchema: {
86200
86292
  ok: external_exports.boolean().optional(),
86201
86293
  id: external_exports.string().optional(),
86202
- countDelta: COUNT_DELTA_OUTPUT_SCHEMA
86294
+ countDelta: COUNT_DELTA_OUTPUT_SCHEMA,
86295
+ verification: VERIFICATION_OUTPUT_SCHEMA
86203
86296
  }
86204
86297
  },
86205
86298
  withErrorHandling(
@@ -86239,7 +86332,8 @@ registerTool(
86239
86332
  ok: external_exports.boolean().optional(),
86240
86333
  id: external_exports.string().optional(),
86241
86334
  mailbox: external_exports.string().optional(),
86242
- countDelta: COUNT_DELTA_OUTPUT_SCHEMA
86335
+ countDelta: COUNT_DELTA_OUTPUT_SCHEMA,
86336
+ verification: VERIFICATION_OUTPUT_SCHEMA
86243
86337
  }
86244
86338
  },
86245
86339
  withErrorHandling(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.11.2",
3
+ "version": "2.13.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",