apple-mail-mcp 2.10.15 → 2.10.16
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 +21 -0
- package/build/index.js +238 -142
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -838,11 +838,26 @@ it**. An id the server hasn't seen listed is accepted only when exactly one mail
|
|
|
838
838
|
if several do, that id fails with the candidate mailboxes named instead of being applied to an
|
|
839
839
|
arbitrary copy. `imap:…` ids carry their own account + mailbox + UID and are never ambiguous.
|
|
840
840
|
|
|
841
|
+
**Say which mailbox with `sourceMailbox` / `sourceAccount`.** The binding above is remembered
|
|
842
|
+
per running server, so a client that reconnects, restarts, or replays a saved list of ids has
|
|
843
|
+
nothing recorded and every id takes the slower whole-tree path — where, on a label store, it is
|
|
844
|
+
likely to be refused as ambiguous. Passing the source mailbox explicitly is the reliable way to
|
|
845
|
+
stay scoped, and it overrides the remembered location. These parameters name where the ids **came
|
|
846
|
+
from**; for `batch-move-messages` that is distinct from `mailbox`, the destination.
|
|
847
|
+
|
|
848
|
+
`sourceMailbox` is the one that scopes, and it works on its own: omitting `sourceAccount` means
|
|
849
|
+
that mailbox **in the default account**, exactly as an omitted `account` does elsewhere. If the
|
|
850
|
+
default account cannot be determined, the ids fail with an error asking for an explicit
|
|
851
|
+
`sourceAccount` — a scope the server can't honor is never quietly downgraded to the guess-the-copy
|
|
852
|
+
walk. `sourceAccount` by itself pins nothing, since the mailbox is what an id is scoped to.
|
|
853
|
+
|
|
841
854
|
#### `batch-delete-messages`
|
|
842
855
|
|
|
843
856
|
| Parameter | Type | Required | Description |
|
|
844
857
|
|-----------|------|----------|-------------|
|
|
845
858
|
| `ids` | string[] | Yes | Message IDs to delete (max 100) |
|
|
859
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
860
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Defaults to the default account; on its own it pins nothing — pair it with `sourceMailbox`. |
|
|
846
861
|
|
|
847
862
|
**⚠️ Safety:** Destructive. Requires explicit user confirmation; search/list first to confirm the message ids.
|
|
848
863
|
|
|
@@ -853,12 +868,16 @@ arbitrary copy. `imap:…` ids carry their own account + mailbox + UID and are n
|
|
|
853
868
|
| `ids` | string[] | Yes | Message IDs to move (max 100) |
|
|
854
869
|
| `mailbox` | string | Yes | Destination mailbox |
|
|
855
870
|
| `account` | string | No | Account containing mailbox |
|
|
871
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
872
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Defaults to the default account; on its own it pins nothing — pair it with `sourceMailbox`. |
|
|
856
873
|
|
|
857
874
|
#### `batch-mark-as-read` / `batch-mark-as-unread`
|
|
858
875
|
|
|
859
876
|
| Parameter | Type | Required | Description |
|
|
860
877
|
|-----------|------|----------|-------------|
|
|
861
878
|
| `ids` | string[] | Yes | Message IDs (max 100) |
|
|
879
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
880
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Defaults to the default account; on its own it pins nothing — pair it with `sourceMailbox`. |
|
|
862
881
|
|
|
863
882
|
#### `batch-flag-messages` / `batch-unflag-messages`
|
|
864
883
|
|
|
@@ -866,6 +885,8 @@ arbitrary copy. `imap:…` ids carry their own account + mailbox + UID and are n
|
|
|
866
885
|
|-----------|------|----------|-------------|
|
|
867
886
|
| `ids` | string[] | Yes | Message IDs (max 100) |
|
|
868
887
|
| `color` | string | No | (`batch-flag-messages` only) Flag color — see [`flag-message`](#flag-message--unflag-message). Applied on both routes, so a mixed batch of numeric and `imap:` ids all end up colored. |
|
|
888
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
889
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Defaults to the default account; on its own it pins nothing — pair it with `sourceMailbox`. |
|
|
869
890
|
|
|
870
891
|
---
|
|
871
892
|
|
package/build/index.js
CHANGED
|
@@ -78581,6 +78581,18 @@ var AppleMailManager = class {
|
|
|
78581
78581
|
locationFor(id) {
|
|
78582
78582
|
return this.idLocationIndex.get(String(id));
|
|
78583
78583
|
}
|
|
78584
|
+
/**
|
|
78585
|
+
* Publicly record where a message id lives.
|
|
78586
|
+
*
|
|
78587
|
+
* The index fills itself from list/search results, but that is per-process
|
|
78588
|
+
* state: a caller that carried ids across a process boundary (a stored triage
|
|
78589
|
+
* list, a scheduled job resuming) starts with an empty index, so every id is
|
|
78590
|
+
* "unlocated" and a label-store id gets refused as ambiguous. Registering the
|
|
78591
|
+
* known location restores scoped resolution.
|
|
78592
|
+
*/
|
|
78593
|
+
noteMessageLocation(id, account, mailbox) {
|
|
78594
|
+
this.rememberLocation(id, account, mailbox);
|
|
78595
|
+
}
|
|
78584
78596
|
/**
|
|
78585
78597
|
* AppleScript fragment resolving `account` + `mailbox` into `_tmb`, leaving
|
|
78586
78598
|
* `_tmb` as `missing value` when it can't be pinned down. Exact-name match
|
|
@@ -79815,10 +79827,12 @@ var AppleMailManager = class {
|
|
|
79815
79827
|
* Mail.app numeric ids are per-mailbox, and on a label store (Gmail, iCloud)
|
|
79816
79828
|
* ONE message is present in several mailboxes under the SAME id — INBOX,
|
|
79817
79829
|
* "Important" and "All Mail" all report id 75816 for the same mail. This used
|
|
79818
|
-
* to walk every account's every mailbox and mutate the FIRST hit
|
|
79819
|
-
* `mailboxes of account
|
|
79820
|
-
*
|
|
79821
|
-
*
|
|
79830
|
+
* to walk every account's every mailbox and mutate the FIRST hit, so whichever
|
|
79831
|
+
* copy `mailboxes of <account>` happened to reach first won and the mailbox the
|
|
79832
|
+
* id was listed from lost whenever an alias came earlier in that (store-
|
|
79833
|
+
* dependent) order — the op reported success while the copy the caller meant
|
|
79834
|
+
* stayed put and a different one was moved/deleted (#152). See
|
|
79835
|
+
* runBatchOperation for the observed ordering on the reporting account.
|
|
79822
79836
|
*
|
|
79823
79837
|
* Which mailbox a mutation lands in is semantic — deleting the INBOX copy and
|
|
79824
79838
|
* deleting the "All Mail" copy are different operations — so scope to the
|
|
@@ -80116,6 +80130,43 @@ var AppleMailManager = class {
|
|
|
80116
80130
|
// ===========================================================================
|
|
80117
80131
|
// Batch Operations
|
|
80118
80132
|
// ===========================================================================
|
|
80133
|
+
/**
|
|
80134
|
+
* Turn a caller-supplied batch source scope into an account+mailbox pair.
|
|
80135
|
+
*
|
|
80136
|
+
* `mailbox` is what does the scoping, and it works ON ITS OWN: an omitted
|
|
80137
|
+
* `account` means "the default account" here exactly as it does for every
|
|
80138
|
+
* other tool in this server (see resolveAccount), never "ignore the argument
|
|
80139
|
+
* you were given". Requiring both used to make a lone `sourceMailbox` a silent
|
|
80140
|
+
* no-op — the ids fell back to the whole-tree path and were then refused as
|
|
80141
|
+
* ambiguous, which is the very failure the parameter exists to prevent.
|
|
80142
|
+
*
|
|
80143
|
+
* The safety property is absolute: when the account cannot be determined there
|
|
80144
|
+
* is NO fallback to the scan-and-guess walk. The caller gets an error naming
|
|
80145
|
+
* the mailbox it asked for, so it can retry with an explicit `sourceAccount`.
|
|
80146
|
+
* (An `account` with no `mailbox` cannot pin anything, so it scopes nothing —
|
|
80147
|
+
* those ids still go through the index / ambiguity-checked path.)
|
|
80148
|
+
*/
|
|
80149
|
+
resolveBatchScope(scope) {
|
|
80150
|
+
const mailbox = scope?.mailbox?.trim();
|
|
80151
|
+
if (!mailbox) return { kind: "none" };
|
|
80152
|
+
const account = scope?.account?.trim();
|
|
80153
|
+
if (account) return { kind: "scoped", account, mailbox };
|
|
80154
|
+
const known = this.getCachedAccounts();
|
|
80155
|
+
if (known.length === 0) {
|
|
80156
|
+
return {
|
|
80157
|
+
kind: "unresolvable",
|
|
80158
|
+
error: `Cannot scope to source mailbox "${mailbox}": no sourceAccount was given and no Mail account could be read (Mail returned none, or the AppleScript transport failed). Retry with an explicit sourceAccount.`
|
|
80159
|
+
};
|
|
80160
|
+
}
|
|
80161
|
+
const chosen = this.resolveAccount();
|
|
80162
|
+
if (!known.some((a) => a.name === chosen)) {
|
|
80163
|
+
return {
|
|
80164
|
+
kind: "unresolvable",
|
|
80165
|
+
error: `Cannot scope to source mailbox "${mailbox}": no sourceAccount was given and the default account could not be determined. Retry with an explicit sourceAccount (available: ${known.map((a) => a.name).join(", ")}).`
|
|
80166
|
+
};
|
|
80167
|
+
}
|
|
80168
|
+
return { kind: "scoped", account: chosen, mailbox };
|
|
80169
|
+
}
|
|
80119
80170
|
/**
|
|
80120
80171
|
* Run one operation over many message IDs in a SINGLE osascript invocation.
|
|
80121
80172
|
*
|
|
@@ -80132,18 +80183,23 @@ var AppleMailManager = class {
|
|
|
80132
80183
|
* numeric id is unique only within a mailbox, and a label store (Gmail,
|
|
80133
80184
|
* iCloud) exposes one message in several mailboxes under the same id — INBOX,
|
|
80134
80185
|
* "Important" and "All Mail" all report id 75816 for the same mail. The old
|
|
80135
|
-
* tree walk applied `operation` to the FIRST mailbox that matched
|
|
80136
|
-
* `mailboxes of account
|
|
80137
|
-
*
|
|
80138
|
-
*
|
|
80139
|
-
*
|
|
80140
|
-
*
|
|
80141
|
-
*
|
|
80186
|
+
* tree walk applied `operation` to the FIRST mailbox that matched while
|
|
80187
|
+
* iterating `mailboxes of <account>`, so whichever copy that iteration reached
|
|
80188
|
+
* first won — and the ids' real source mailbox lost whenever an alias came
|
|
80189
|
+
* earlier. Observed on the reporting account (`list-mailboxes`, 2026-08-13):
|
|
80190
|
+
* INBOX 1, "[Gmail]/All Mail" 5, "[Gmail]/Important" 9, "Sales Spam" 12 — so a
|
|
80191
|
+
* batch listed from "Sales Spam" was applied to the All Mail copies while the
|
|
80192
|
+
* Sales Spam messages stayed put, and every id still reported `ok`. That order
|
|
80193
|
+
* is a property of the store, not a guarantee: do not rely on it in either
|
|
80194
|
+
* direction — any mailbox the walk reaches late loses the same way. Grouping by
|
|
80195
|
+
* recorded source mailbox makes the op land on the copy the caller actually
|
|
80196
|
+
* listed; ids with no recorded mailbox are refused when ambiguous rather than
|
|
80197
|
+
* applied to an arbitrary copy.
|
|
80142
80198
|
*
|
|
80143
80199
|
* `setup` runs once up front (used by move to resolve the destination); it may
|
|
80144
80200
|
* bail the whole batch by returning a `BATCH_FATAL`-prefixed string.
|
|
80145
80201
|
*/
|
|
80146
|
-
runBatchOperation(ids, operation, setup = "") {
|
|
80202
|
+
runBatchOperation(ids, operation, setup = "", scope) {
|
|
80147
80203
|
const valid = [];
|
|
80148
80204
|
for (const id of ids) {
|
|
80149
80205
|
const num = Number(id);
|
|
@@ -80152,11 +80208,16 @@ var AppleMailManager = class {
|
|
|
80152
80208
|
if (valid.length === 0) {
|
|
80153
80209
|
return ids.map((id) => ({ id, success: false, error: "Invalid message ID" }));
|
|
80154
80210
|
}
|
|
80211
|
+
const resolved = this.resolveBatchScope(scope);
|
|
80212
|
+
if (resolved.kind === "unresolvable") {
|
|
80213
|
+
return ids.map((id) => ({ id, success: false, error: resolved.error }));
|
|
80214
|
+
}
|
|
80215
|
+
const callerScope = resolved.kind === "scoped" ? resolved : void 0;
|
|
80155
80216
|
const groups = /* @__PURE__ */ new Map();
|
|
80156
80217
|
const unlocated = [];
|
|
80157
80218
|
valid.forEach((v, i) => {
|
|
80158
80219
|
const pos = i + 1;
|
|
80159
|
-
const loc = this.locationFor(v.id);
|
|
80220
|
+
const loc = callerScope ?? this.locationFor(v.id);
|
|
80160
80221
|
if (!loc) {
|
|
80161
80222
|
unlocated.push({ num: v.num, pos });
|
|
80162
80223
|
return;
|
|
@@ -80284,8 +80345,8 @@ var AppleMailManager = class {
|
|
|
80284
80345
|
/**
|
|
80285
80346
|
* Delete multiple messages at once (single tree walk — see runBatchOperation).
|
|
80286
80347
|
*/
|
|
80287
|
-
batchDeleteMessages(ids) {
|
|
80288
|
-
return this.runBatchOperation(ids, "delete _msg");
|
|
80348
|
+
batchDeleteMessages(ids, scope) {
|
|
80349
|
+
return this.runBatchOperation(ids, "delete _msg", "", scope);
|
|
80289
80350
|
}
|
|
80290
80351
|
/**
|
|
80291
80352
|
* Move multiple messages to a mailbox at once (single tree walk).
|
|
@@ -80294,7 +80355,7 @@ var AppleMailManager = class {
|
|
|
80294
80355
|
* matching more than one mailbox fails the whole batch rather than guessing),
|
|
80295
80356
|
* then every matched message is moved in the same walk.
|
|
80296
80357
|
*/
|
|
80297
|
-
batchMoveMessages(ids, mailbox, account) {
|
|
80358
|
+
batchMoveMessages(ids, mailbox, account, scope) {
|
|
80298
80359
|
const targetAccount = this.resolveAccount(account);
|
|
80299
80360
|
const targetMailbox = this.resolveMailbox(mailbox, targetAccount);
|
|
80300
80361
|
const safeMailbox = escapeForAppleScript(targetMailbox);
|
|
@@ -80308,31 +80369,31 @@ var AppleMailManager = class {
|
|
|
80308
80369
|
if (count of destMatches) is 0 then return "${BATCH_FATAL}Destination mailbox \\"" & destName & "\\" not found in account \\"${safeAccount}\\""
|
|
80309
80370
|
if (count of destMatches) > 1 then return "${BATCH_FATAL}Destination mailbox \\"" & destName & "\\" is ambiguous (" & (count of destMatches) & " matches) in account \\"${safeAccount}\\"; move by full path"
|
|
80310
80371
|
set destMailbox to item 1 of destMatches`;
|
|
80311
|
-
return this.runBatchOperation(ids, "move _msg to destMailbox", setup);
|
|
80372
|
+
return this.runBatchOperation(ids, "move _msg to destMailbox", setup, scope);
|
|
80312
80373
|
}
|
|
80313
80374
|
/**
|
|
80314
80375
|
* Mark multiple messages as read at once (single tree walk).
|
|
80315
80376
|
*/
|
|
80316
|
-
batchMarkAsRead(ids) {
|
|
80317
|
-
return this.runBatchOperation(ids, "set read status of _msg to true");
|
|
80377
|
+
batchMarkAsRead(ids, scope) {
|
|
80378
|
+
return this.runBatchOperation(ids, "set read status of _msg to true", "", scope);
|
|
80318
80379
|
}
|
|
80319
80380
|
/**
|
|
80320
80381
|
* Mark multiple messages as unread at once (single tree walk).
|
|
80321
80382
|
*/
|
|
80322
|
-
batchMarkAsUnread(ids) {
|
|
80323
|
-
return this.runBatchOperation(ids, "set read status of _msg to false");
|
|
80383
|
+
batchMarkAsUnread(ids, scope) {
|
|
80384
|
+
return this.runBatchOperation(ids, "set read status of _msg to false", "", scope);
|
|
80324
80385
|
}
|
|
80325
80386
|
/**
|
|
80326
80387
|
* Flag multiple messages at once (single tree walk).
|
|
80327
80388
|
*/
|
|
80328
|
-
batchFlagMessages(ids, colorIndex) {
|
|
80329
|
-
return this.runBatchOperation(ids, this.flagOperation("_msg", colorIndex));
|
|
80389
|
+
batchFlagMessages(ids, colorIndex, scope) {
|
|
80390
|
+
return this.runBatchOperation(ids, this.flagOperation("_msg", colorIndex), "", scope);
|
|
80330
80391
|
}
|
|
80331
80392
|
/**
|
|
80332
80393
|
* Unflag multiple messages at once (single tree walk).
|
|
80333
80394
|
*/
|
|
80334
|
-
batchUnflagMessages(ids) {
|
|
80335
|
-
return this.runBatchOperation(ids, "set flagged status of _msg to false");
|
|
80395
|
+
batchUnflagMessages(ids, scope) {
|
|
80396
|
+
return this.runBatchOperation(ids, "set flagged status of _msg to false", "", scope);
|
|
80336
80397
|
}
|
|
80337
80398
|
/**
|
|
80338
80399
|
* List attachments for a message.
|
|
@@ -82997,6 +83058,57 @@ function withErrorHandling(handler, errorPrefix) {
|
|
|
82997
83058
|
};
|
|
82998
83059
|
}
|
|
82999
83060
|
|
|
83061
|
+
// src/tools/batchResults.ts
|
|
83062
|
+
async function hybridBatchCounts(ids, appleFn, imapFn) {
|
|
83063
|
+
const imapIds = ids.filter((i) => i.startsWith("imap:"));
|
|
83064
|
+
const numericIds = ids.filter((i) => !i.startsWith("imap:"));
|
|
83065
|
+
let success = 0;
|
|
83066
|
+
let fail = 0;
|
|
83067
|
+
const errors = [];
|
|
83068
|
+
if (numericIds.length > 0) {
|
|
83069
|
+
const res = appleFn(numericIds);
|
|
83070
|
+
const s = res.filter((r) => r.success).length;
|
|
83071
|
+
success += s;
|
|
83072
|
+
fail += res.length - s;
|
|
83073
|
+
errors.push(...res.filter((r) => !r.success && r.error).map((r) => r.error));
|
|
83074
|
+
}
|
|
83075
|
+
if (imapIds.length > 0) {
|
|
83076
|
+
const r = await imapFn(imapIds);
|
|
83077
|
+
success += r.success;
|
|
83078
|
+
fail += r.failed;
|
|
83079
|
+
errors.push(...r.errors);
|
|
83080
|
+
}
|
|
83081
|
+
return { success, fail, errors };
|
|
83082
|
+
}
|
|
83083
|
+
function distinctErrors(errors) {
|
|
83084
|
+
return [...new Set(errors.filter(Boolean))];
|
|
83085
|
+
}
|
|
83086
|
+
function formatBatchErrors(errors, max = 5) {
|
|
83087
|
+
const distinct = distinctErrors(errors);
|
|
83088
|
+
if (distinct.length === 0) return "";
|
|
83089
|
+
const shown = distinct.slice(0, max);
|
|
83090
|
+
const more = distinct.length - shown.length;
|
|
83091
|
+
return `: ${shown.join("; ")}${more > 0 ? ` (+${more} more)` : ""}`;
|
|
83092
|
+
}
|
|
83093
|
+
var MAX_STRUCTURED_BATCH_ERRORS = 20;
|
|
83094
|
+
function batchResponse(counts, messages, extra = {}) {
|
|
83095
|
+
const { success, fail, errors } = counts;
|
|
83096
|
+
const distinct = distinctErrors(errors);
|
|
83097
|
+
const reported = distinct.slice(0, MAX_STRUCTURED_BATCH_ERRORS);
|
|
83098
|
+
const structured = {
|
|
83099
|
+
ok: fail === 0,
|
|
83100
|
+
success,
|
|
83101
|
+
failed: fail,
|
|
83102
|
+
...extra,
|
|
83103
|
+
...reported.length > 0 ? { errors: reported } : {},
|
|
83104
|
+
...distinct.length > reported.length ? { errorsTruncated: true } : {}
|
|
83105
|
+
};
|
|
83106
|
+
const suffix = formatBatchErrors(distinct);
|
|
83107
|
+
if (fail === 0) return successResponse(messages.allSucceeded(success), structured);
|
|
83108
|
+
if (success === 0) return errorResponse(`${messages.allFailed(fail)}${suffix}`, structured);
|
|
83109
|
+
return successResponse(`${messages.partial(success, fail)}${suffix}`, structured);
|
|
83110
|
+
}
|
|
83111
|
+
|
|
83000
83112
|
// src/services/imapMultiAccount.ts
|
|
83001
83113
|
function normalizeMessageId2(row) {
|
|
83002
83114
|
const raw = typeof row.messageId === "string" ? row.messageId.trim() : "";
|
|
@@ -83588,6 +83700,12 @@ function withJsonSchema2020_12(transport2) {
|
|
|
83588
83700
|
loadFileConfig();
|
|
83589
83701
|
var MESSAGE_ID_SCHEMA = external_exports.string().regex(/^(\d+|imap:[A-Za-z0-9_-]+)$/, "Message ID must be numeric or an IMAP id (imap:\u2026)");
|
|
83590
83702
|
var BATCH_IDS_SCHEMA = external_exports.array(MESSAGE_ID_SCHEMA).min(1, "At least one message ID is required").max(100, "Cannot process more than 100 messages in a single batch");
|
|
83703
|
+
var BATCH_SOURCE_MAILBOX_SCHEMA = external_exports.string().optional().describe(
|
|
83704
|
+
"Mailbox the numeric ids were listed from (e.g. 'INBOX'). Pins each id to that mailbox \u2014 strongly recommended, since one numeric id can match in several mailboxes. Works on its own: without sourceAccount it means that mailbox in the default account. Ignored for imap: ids."
|
|
83705
|
+
);
|
|
83706
|
+
var BATCH_SOURCE_ACCOUNT_SCHEMA = external_exports.string().optional().describe(
|
|
83707
|
+
"Account the numeric ids were listed from. Defaults to the default account. On its own it pins nothing \u2014 pair it with sourceMailbox."
|
|
83708
|
+
);
|
|
83591
83709
|
var FLAG_COLOR_INDEX = {
|
|
83592
83710
|
red: 0,
|
|
83593
83711
|
orange: 1,
|
|
@@ -83637,7 +83755,19 @@ var BATCH_COUNT_OUTPUT_SCHEMA = {
|
|
|
83637
83755
|
ok: external_exports.boolean().optional(),
|
|
83638
83756
|
success: external_exports.number().optional(),
|
|
83639
83757
|
failed: external_exports.number().optional(),
|
|
83640
|
-
mailbox: external_exports.string().optional()
|
|
83758
|
+
mailbox: external_exports.string().optional(),
|
|
83759
|
+
// Declared so the failure channel is part of the tool's advertised CONTRACT:
|
|
83760
|
+
// a client can rely on `errors` being string[] and code against it, and it
|
|
83761
|
+
// shows up in generated types and docs. Declaring is not what makes it
|
|
83762
|
+
// deliverable — registerTool() wraps every outputSchema in
|
|
83763
|
+
// `z.object(shape).passthrough()`, so these tools advertise
|
|
83764
|
+
// `additionalProperties: true` (verified against the built server) and an
|
|
83765
|
+
// undeclared key would be carried through, not rejected. Enumerating it is a
|
|
83766
|
+
// promise to callers, not a workaround for a validator.
|
|
83767
|
+
errors: external_exports.array(external_exports.string()).optional(),
|
|
83768
|
+
// Set when `errors` was capped (MAX_STRUCTURED_BATCH_ERRORS distinct reasons),
|
|
83769
|
+
// so a short list is never mistaken for the complete one.
|
|
83770
|
+
errorsTruncated: external_exports.boolean().optional()
|
|
83641
83771
|
};
|
|
83642
83772
|
var CHECK_ITEM_SCHEMA = external_exports.object({}).passthrough();
|
|
83643
83773
|
var require2 = createRequire(import.meta.url);
|
|
@@ -83718,26 +83848,6 @@ function registerTool(name, config2, cb) {
|
|
|
83718
83848
|
}
|
|
83719
83849
|
var mailManager = new AppleMailManager();
|
|
83720
83850
|
registerResourcesAndPrompts(server, mailManager);
|
|
83721
|
-
async function hybridBatchCounts(ids, appleFn, imapFn) {
|
|
83722
|
-
const imapIds = ids.filter((i) => i.startsWith("imap:"));
|
|
83723
|
-
const numericIds = ids.filter((i) => !i.startsWith("imap:"));
|
|
83724
|
-
let success = 0;
|
|
83725
|
-
let fail = 0;
|
|
83726
|
-
const errors = [];
|
|
83727
|
-
if (numericIds.length > 0) {
|
|
83728
|
-
const res = appleFn(numericIds);
|
|
83729
|
-
const s = res.filter((r) => r.success).length;
|
|
83730
|
-
success += s;
|
|
83731
|
-
fail += res.length - s;
|
|
83732
|
-
}
|
|
83733
|
-
if (imapIds.length > 0) {
|
|
83734
|
-
const r = await imapFn(imapIds);
|
|
83735
|
-
success += r.success;
|
|
83736
|
-
fail += r.failed;
|
|
83737
|
-
errors.push(...r.errors);
|
|
83738
|
-
}
|
|
83739
|
-
return { success, fail, errors };
|
|
83740
|
-
}
|
|
83741
83851
|
registerTool(
|
|
83742
83852
|
"search-messages",
|
|
83743
83853
|
{
|
|
@@ -84515,173 +84625,159 @@ registerTool(
|
|
|
84515
84625
|
registerTool(
|
|
84516
84626
|
"batch-delete-messages",
|
|
84517
84627
|
{
|
|
84518
|
-
description: "Use when: deleting multiple messages in one call (1\u2013100 ids; moves them to Trash).\nReturns: counts of how many were deleted and how many failed.\nDo not use when: deleting just one (use delete-message) or filing messages away (use batch-move-messages).\nSafety: destructive and applies to many messages at once \u2014 require explicit user confirmation, and search-messages/list-messages first to confirm every id is correct before deleting.",
|
|
84628
|
+
description: "Use when: deleting multiple messages in one call (1\u2013100 ids; moves them to Trash).\nReturns: counts of how many were deleted and how many failed, plus the distinct reasons for any failures.\nDo not use when: deleting just one (use delete-message) or filing messages away (use batch-move-messages).\nSafety: destructive and applies to many messages at once \u2014 require explicit user confirmation, and search-messages/list-messages first to confirm every id is correct before deleting. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84519
84629
|
inputSchema: {
|
|
84520
|
-
ids: BATCH_IDS_SCHEMA
|
|
84630
|
+
ids: BATCH_IDS_SCHEMA,
|
|
84631
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84632
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84521
84633
|
},
|
|
84522
84634
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84523
84635
|
},
|
|
84524
|
-
withErrorHandling(async ({ ids }) => {
|
|
84525
|
-
const
|
|
84636
|
+
withErrorHandling(async ({ ids, sourceMailbox, sourceAccount }) => {
|
|
84637
|
+
const counts = await hybridBatchCounts(
|
|
84526
84638
|
ids,
|
|
84527
|
-
(n) => mailManager.batchDeleteMessages(n),
|
|
84639
|
+
(n) => mailManager.batchDeleteMessages(n, { account: sourceAccount, mailbox: sourceMailbox }),
|
|
84528
84640
|
(im) => imapBatchDelete(im)
|
|
84529
84641
|
);
|
|
84530
|
-
|
|
84531
|
-
|
|
84532
|
-
|
|
84533
|
-
|
|
84534
|
-
|
|
84535
|
-
} else {
|
|
84536
|
-
return successResponse(`Deleted ${successCount} message(s), ${failCount} failed`, structured);
|
|
84537
|
-
}
|
|
84642
|
+
return batchResponse(counts, {
|
|
84643
|
+
allSucceeded: (n) => `Successfully deleted ${n} message(s)`,
|
|
84644
|
+
allFailed: (n) => `Failed to delete all ${n} message(s)`,
|
|
84645
|
+
partial: (ok, failed) => `Deleted ${ok} message(s), ${failed} failed`
|
|
84646
|
+
});
|
|
84538
84647
|
}, "Error batch deleting messages")
|
|
84539
84648
|
);
|
|
84540
84649
|
registerTool(
|
|
84541
84650
|
"batch-move-messages",
|
|
84542
84651
|
{
|
|
84543
|
-
description: "Use when: moving multiple messages (1\u2013100 ids) into the same destination mailbox/folder in one call, e.g. bulk archiving.\nReturns: counts of how many were moved and how many failed.\nDo not use when: moving just one (use move-message) or deleting (use batch-delete-messages). Use list-mailboxes to confirm the destination name exists.\nSafety: moves many real messages at once \u2014 confirm the destination mailbox, and search-messages/list-messages first to confirm the ids.",
|
|
84652
|
+
description: "Use when: moving multiple messages (1\u2013100 ids) into the same destination mailbox/folder in one call, e.g. bulk archiving.\nReturns: counts of how many were moved and how many failed, plus the distinct reasons for any failures.\nDo not use when: moving just one (use move-message) or deleting (use batch-delete-messages). Use list-mailboxes to confirm the destination name exists.\nSafety: moves many real messages at once \u2014 confirm the destination mailbox, and search-messages/list-messages first to confirm the ids. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from \u2014 not the destination) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84544
84653
|
inputSchema: {
|
|
84545
84654
|
ids: BATCH_IDS_SCHEMA,
|
|
84546
84655
|
mailbox: external_exports.string().min(1, "Destination mailbox is required"),
|
|
84547
|
-
account: external_exports.string().optional().describe("Account containing the destination mailbox")
|
|
84656
|
+
account: external_exports.string().optional().describe("Account containing the destination mailbox"),
|
|
84657
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84658
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84548
84659
|
},
|
|
84549
84660
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84550
84661
|
},
|
|
84551
|
-
withErrorHandling(async ({ ids, mailbox, account }) => {
|
|
84552
|
-
const
|
|
84662
|
+
withErrorHandling(async ({ ids, mailbox, account, sourceMailbox, sourceAccount }) => {
|
|
84663
|
+
const counts = await hybridBatchCounts(
|
|
84553
84664
|
ids,
|
|
84554
|
-
(n) => mailManager.batchMoveMessages(n, mailbox, account
|
|
84665
|
+
(n) => mailManager.batchMoveMessages(n, mailbox, account, {
|
|
84666
|
+
account: sourceAccount,
|
|
84667
|
+
mailbox: sourceMailbox
|
|
84668
|
+
}),
|
|
84555
84669
|
(im) => imapBatchMove(im, mailbox, { account })
|
|
84556
84670
|
);
|
|
84557
|
-
|
|
84558
|
-
|
|
84559
|
-
|
|
84560
|
-
`Successfully moved ${
|
|
84561
|
-
|
|
84562
|
-
|
|
84563
|
-
|
|
84564
|
-
|
|
84565
|
-
|
|
84566
|
-
return successResponse(
|
|
84567
|
-
`Moved ${successCount} message(s) to "${mailbox}", ${failCount} failed`,
|
|
84568
|
-
structured
|
|
84569
|
-
);
|
|
84570
|
-
}
|
|
84671
|
+
return batchResponse(
|
|
84672
|
+
counts,
|
|
84673
|
+
{
|
|
84674
|
+
allSucceeded: (n) => `Successfully moved ${n} message(s) to "${mailbox}"`,
|
|
84675
|
+
allFailed: (n) => `Failed to move all ${n} message(s)`,
|
|
84676
|
+
partial: (ok, failed) => `Moved ${ok} message(s) to "${mailbox}", ${failed} failed`
|
|
84677
|
+
},
|
|
84678
|
+
{ mailbox }
|
|
84679
|
+
);
|
|
84571
84680
|
}, "Error batch moving messages")
|
|
84572
84681
|
);
|
|
84573
84682
|
registerTool(
|
|
84574
84683
|
"batch-mark-as-read",
|
|
84575
84684
|
{
|
|
84576
|
-
description: "Use when: marking multiple messages (1\u2013100 ids) as read in one call.\nReturns: counts of how many were marked read and how many failed.\nDo not use when: marking just one (use mark-as-read) or marking unread (use batch-mark-as-unread). Get the ids from search-messages or list-messages first.",
|
|
84685
|
+
description: "Use when: marking multiple messages (1\u2013100 ids) as read in one call.\nReturns: counts of how many were marked read and how many failed.\nDo not use when: marking just one (use mark-as-read) or marking unread (use batch-mark-as-unread). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84577
84686
|
inputSchema: {
|
|
84578
|
-
ids: BATCH_IDS_SCHEMA
|
|
84687
|
+
ids: BATCH_IDS_SCHEMA,
|
|
84688
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84689
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84579
84690
|
},
|
|
84580
84691
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84581
84692
|
},
|
|
84582
|
-
withErrorHandling(async ({ ids }) => {
|
|
84583
|
-
const
|
|
84693
|
+
withErrorHandling(async ({ ids, sourceMailbox, sourceAccount }) => {
|
|
84694
|
+
const counts = await hybridBatchCounts(
|
|
84584
84695
|
ids,
|
|
84585
|
-
(n) => mailManager.batchMarkAsRead(n),
|
|
84696
|
+
(n) => mailManager.batchMarkAsRead(n, { account: sourceAccount, mailbox: sourceMailbox }),
|
|
84586
84697
|
(im) => imapBatchMarkRead(im)
|
|
84587
84698
|
);
|
|
84588
|
-
|
|
84589
|
-
|
|
84590
|
-
|
|
84591
|
-
|
|
84592
|
-
|
|
84593
|
-
} else {
|
|
84594
|
-
return successResponse(
|
|
84595
|
-
`Marked ${successCount} message(s) as read, ${failCount} failed`,
|
|
84596
|
-
structured
|
|
84597
|
-
);
|
|
84598
|
-
}
|
|
84699
|
+
return batchResponse(counts, {
|
|
84700
|
+
allSucceeded: (n) => `Successfully marked ${n} message(s) as read`,
|
|
84701
|
+
allFailed: (n) => `Failed to mark all ${n} message(s) as read`,
|
|
84702
|
+
partial: (ok, failed) => `Marked ${ok} message(s) as read, ${failed} failed`
|
|
84703
|
+
});
|
|
84599
84704
|
}, "Error batch marking messages as read")
|
|
84600
84705
|
);
|
|
84601
84706
|
registerTool(
|
|
84602
84707
|
"batch-mark-as-unread",
|
|
84603
84708
|
{
|
|
84604
|
-
description: "Use when: marking multiple messages (1\u2013100 ids) as unread in one call.\nReturns: counts of how many were marked unread and how many failed.\nDo not use when: marking just one (use mark-as-unread) or marking read (use batch-mark-as-read). Get the ids from search-messages or list-messages first.",
|
|
84709
|
+
description: "Use when: marking multiple messages (1\u2013100 ids) as unread in one call.\nReturns: counts of how many were marked unread and how many failed.\nDo not use when: marking just one (use mark-as-unread) or marking read (use batch-mark-as-read). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84605
84710
|
inputSchema: {
|
|
84606
|
-
ids: BATCH_IDS_SCHEMA
|
|
84711
|
+
ids: BATCH_IDS_SCHEMA,
|
|
84712
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84713
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84607
84714
|
},
|
|
84608
84715
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84609
84716
|
},
|
|
84610
|
-
withErrorHandling(async ({ ids }) => {
|
|
84611
|
-
const
|
|
84717
|
+
withErrorHandling(async ({ ids, sourceMailbox, sourceAccount }) => {
|
|
84718
|
+
const counts = await hybridBatchCounts(
|
|
84612
84719
|
ids,
|
|
84613
|
-
(n) => mailManager.batchMarkAsUnread(n),
|
|
84720
|
+
(n) => mailManager.batchMarkAsUnread(n, { account: sourceAccount, mailbox: sourceMailbox }),
|
|
84614
84721
|
(im) => imapBatchMarkUnread(im)
|
|
84615
84722
|
);
|
|
84616
|
-
|
|
84617
|
-
|
|
84618
|
-
|
|
84619
|
-
|
|
84620
|
-
|
|
84621
|
-
);
|
|
84622
|
-
} else if (successCount === 0) {
|
|
84623
|
-
return errorResponse(`Failed to mark all ${failCount} message(s) as unread`);
|
|
84624
|
-
} else {
|
|
84625
|
-
return successResponse(
|
|
84626
|
-
`Marked ${successCount} message(s) as unread, ${failCount} failed`,
|
|
84627
|
-
structured
|
|
84628
|
-
);
|
|
84629
|
-
}
|
|
84723
|
+
return batchResponse(counts, {
|
|
84724
|
+
allSucceeded: (n) => `Successfully marked ${n} message(s) as unread`,
|
|
84725
|
+
allFailed: (n) => `Failed to mark all ${n} message(s) as unread`,
|
|
84726
|
+
partial: (ok, failed) => `Marked ${ok} message(s) as unread, ${failed} failed`
|
|
84727
|
+
});
|
|
84630
84728
|
}, "Error batch marking messages as unread")
|
|
84631
84729
|
);
|
|
84632
84730
|
registerTool(
|
|
84633
84731
|
"batch-flag-messages",
|
|
84634
84732
|
{
|
|
84635
|
-
description: "Use when: flagging multiple messages (1\u2013100 ids) in one call, optionally with a color (red/orange/yellow/green/blue/purple/gray).\nReturns: counts of how many were flagged and how many failed.\nDo not use when: flagging just one (use flag-message) or removing flags (use batch-unflag-messages). Get the ids from search-messages or list-messages first.\nNote: the color is applied on both routes \u2014 AppleScript sets the flag index, IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads \u2014 so a mixed batch of numeric and `imap:` ids all end up colored.",
|
|
84733
|
+
description: "Use when: flagging multiple messages (1\u2013100 ids) in one call, optionally with a color (red/orange/yellow/green/blue/purple/gray).\nReturns: counts of how many were flagged and how many failed.\nDo not use when: flagging just one (use flag-message) or removing flags (use batch-unflag-messages). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.\nNote: the color is applied on both routes \u2014 AppleScript sets the flag index, IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads \u2014 so a mixed batch of numeric and `imap:` ids all end up colored.",
|
|
84636
84734
|
inputSchema: {
|
|
84637
84735
|
ids: BATCH_IDS_SCHEMA,
|
|
84638
|
-
color: FLAG_COLOR_SCHEMA
|
|
84736
|
+
color: FLAG_COLOR_SCHEMA,
|
|
84737
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84738
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84639
84739
|
},
|
|
84640
84740
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84641
84741
|
},
|
|
84642
|
-
withErrorHandling(async ({ ids, color }) => {
|
|
84742
|
+
withErrorHandling(async ({ ids, color, sourceMailbox, sourceAccount }) => {
|
|
84643
84743
|
const colorIndex = color ? FLAG_COLOR_INDEX[color] : void 0;
|
|
84644
|
-
const
|
|
84744
|
+
const counts = await hybridBatchCounts(
|
|
84645
84745
|
ids,
|
|
84646
|
-
(n) => mailManager.batchFlagMessages(n, colorIndex
|
|
84746
|
+
(n) => mailManager.batchFlagMessages(n, colorIndex, {
|
|
84747
|
+
account: sourceAccount,
|
|
84748
|
+
mailbox: sourceMailbox
|
|
84749
|
+
}),
|
|
84647
84750
|
(im) => imapBatchFlag(im, colorIndex)
|
|
84648
84751
|
);
|
|
84649
|
-
|
|
84650
|
-
|
|
84651
|
-
|
|
84652
|
-
|
|
84653
|
-
|
|
84654
|
-
} else {
|
|
84655
|
-
return successResponse(`Flagged ${successCount} message(s), ${failCount} failed`, structured);
|
|
84656
|
-
}
|
|
84752
|
+
return batchResponse(counts, {
|
|
84753
|
+
allSucceeded: (n) => `Successfully flagged ${n} message(s)`,
|
|
84754
|
+
allFailed: (n) => `Failed to flag all ${n} message(s)`,
|
|
84755
|
+
partial: (ok, failed) => `Flagged ${ok} message(s), ${failed} failed`
|
|
84756
|
+
});
|
|
84657
84757
|
}, "Error batch flagging messages")
|
|
84658
84758
|
);
|
|
84659
84759
|
registerTool(
|
|
84660
84760
|
"batch-unflag-messages",
|
|
84661
84761
|
{
|
|
84662
|
-
description: "Use when: removing flags from multiple messages (1\u2013100 ids) in one call.\nReturns: counts of how many were unflagged and how many failed.\nDo not use when: unflagging just one (use unflag-message) or adding flags (use batch-flag-messages). Get the ids from search-messages or list-messages first.",
|
|
84762
|
+
description: "Use when: removing flags from multiple messages (1\u2013100 ids) in one call.\nReturns: counts of how many were unflagged and how many failed.\nDo not use when: unflagging just one (use unflag-message) or adding flags (use batch-flag-messages). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84663
84763
|
inputSchema: {
|
|
84664
|
-
ids: BATCH_IDS_SCHEMA
|
|
84764
|
+
ids: BATCH_IDS_SCHEMA,
|
|
84765
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84766
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84665
84767
|
},
|
|
84666
84768
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84667
84769
|
},
|
|
84668
|
-
withErrorHandling(async ({ ids }) => {
|
|
84669
|
-
const
|
|
84770
|
+
withErrorHandling(async ({ ids, sourceMailbox, sourceAccount }) => {
|
|
84771
|
+
const counts = await hybridBatchCounts(
|
|
84670
84772
|
ids,
|
|
84671
|
-
(n) => mailManager.batchUnflagMessages(n),
|
|
84773
|
+
(n) => mailManager.batchUnflagMessages(n, { account: sourceAccount, mailbox: sourceMailbox }),
|
|
84672
84774
|
(im) => imapBatchUnflag(im)
|
|
84673
84775
|
);
|
|
84674
|
-
|
|
84675
|
-
|
|
84676
|
-
|
|
84677
|
-
|
|
84678
|
-
|
|
84679
|
-
} else {
|
|
84680
|
-
return successResponse(
|
|
84681
|
-
`Unflagged ${successCount} message(s), ${failCount} failed`,
|
|
84682
|
-
structured
|
|
84683
|
-
);
|
|
84684
|
-
}
|
|
84776
|
+
return batchResponse(counts, {
|
|
84777
|
+
allSucceeded: (n) => `Successfully unflagged ${n} message(s)`,
|
|
84778
|
+
allFailed: (n) => `Failed to unflag all ${n} message(s)`,
|
|
84779
|
+
partial: (ok, failed) => `Unflagged ${ok} message(s), ${failed} failed`
|
|
84780
|
+
});
|
|
84685
84781
|
}, "Error batch unflagging messages")
|
|
84686
84782
|
);
|
|
84687
84783
|
registerTool(
|
package/package.json
CHANGED