apple-mail-mcp 2.12.0 → 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 +65 -11
  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
@@ -83842,6 +83842,27 @@ function assertMutated(result, what) {
83842
83842
  if (!result) throw new Error(`${what}: server rejected the command (IMAP NO/BAD)`);
83843
83843
  return result;
83844
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
+ }
83845
83866
  var poolConnect = defaultConnect;
83846
83867
  var pools = /* @__PURE__ */ new Map();
83847
83868
  function poolKey(cfg) {
@@ -84180,11 +84201,16 @@ async function imapMoveMessageById(id, destMailbox, deps = {}) {
84180
84201
  const destPath = dest.kind === "found" ? dest.path : resolveMailboxPath(destMailbox, "list");
84181
84202
  const lock = await client.getMailboxLock(ref.path);
84182
84203
  try {
84183
- assertMutated(
84204
+ const moved = assertMutated(
84184
84205
  await client.messageMove([ref.uid], destPath, { uid: true }),
84185
84206
  `IMAP move of UID ${ref.uid} to "${destPath}"`
84186
84207
  );
84187
- return { success: true, info: `Moved UID ${ref.uid} to "${destPath}" via IMAP.` };
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
+ };
84188
84214
  } catch (e) {
84189
84215
  return {
84190
84216
  success: false,
@@ -84226,21 +84252,38 @@ async function trashUids(client, uids, srcPath) {
84226
84252
  );
84227
84253
  return { dest, expunged: true };
84228
84254
  }
84229
- assertMutated(
84255
+ const moved = assertMutated(
84230
84256
  await client.messageMove(uids, dest, { uid: true }),
84231
84257
  `IMAP move of ${uids.length} message(s) from "${srcPath}" to "${dest}"`
84232
84258
  );
84233
- return { dest, expunged: false };
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
+ }
84234
84274
  }
84235
84275
  async function imapDeleteMessageById(id, deps = {}) {
84236
84276
  const ref = decodeImapId(id);
84237
84277
  if (!ref) return { success: false, error: `Not an IMAP message id: "${id}".` };
84238
84278
  return withMailbox(ref.path, depsForMessageRef(ref, deps), async (client) => {
84239
84279
  try {
84240
- 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`;
84241
84283
  return {
84242
84284
  success: true,
84243
- 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
84244
84287
  };
84245
84288
  } catch (e) {
84246
84289
  return { success: false, error: `IMAP delete failed for UID ${ref.uid}: ${errText(e)}` };
@@ -84820,10 +84863,12 @@ function formatMergedRows(rows, showReadState = true) {
84820
84863
  async function routeMessage(id, opts) {
84821
84864
  if (decodeImapId(id)) {
84822
84865
  const r = await opts.imap();
84823
- 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(
84824
84869
  r.info ?? opts.ok,
84825
- opts.structuredFromResult ? opts.structuredFromResult(r) : opts.structured
84826
- ) : errorResponse(r.error ?? opts.fail);
84870
+ r.verification && structured ? { ...structured, verification: r.verification } : structured
84871
+ );
84827
84872
  }
84828
84873
  return opts.apple();
84829
84874
  }
@@ -85387,6 +85432,13 @@ var COUNT_DELTA_OUTPUT_SCHEMA = external_exports.array(
85387
85432
  note: external_exports.string().optional()
85388
85433
  })
85389
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();
85390
85442
  var CHECK_ITEM_SCHEMA = external_exports.object({}).passthrough();
85391
85443
  var require2 = createRequire(import.meta.url);
85392
85444
  var { version: version2 } = require2("../package.json");
@@ -86239,7 +86291,8 @@ registerTool(
86239
86291
  outputSchema: {
86240
86292
  ok: external_exports.boolean().optional(),
86241
86293
  id: external_exports.string().optional(),
86242
- countDelta: COUNT_DELTA_OUTPUT_SCHEMA
86294
+ countDelta: COUNT_DELTA_OUTPUT_SCHEMA,
86295
+ verification: VERIFICATION_OUTPUT_SCHEMA
86243
86296
  }
86244
86297
  },
86245
86298
  withErrorHandling(
@@ -86279,7 +86332,8 @@ registerTool(
86279
86332
  ok: external_exports.boolean().optional(),
86280
86333
  id: external_exports.string().optional(),
86281
86334
  mailbox: external_exports.string().optional(),
86282
- countDelta: COUNT_DELTA_OUTPUT_SCHEMA
86335
+ countDelta: COUNT_DELTA_OUTPUT_SCHEMA,
86336
+ verification: VERIFICATION_OUTPUT_SCHEMA
86283
86337
  }
86284
86338
  },
86285
86339
  withErrorHandling(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.12.0",
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",