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.
- package/README.md +29 -3
- package/build/index.js +113 -19
- 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
|
|
1434
|
-
one mailbox, so the mis-targeting class this exists for cannot
|
|
1435
|
-
batch of only `imap:` ids returns no `countDelta` rather than a
|
|
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
|
-
|
|
84180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84211
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84786
|
-
)
|
|
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