apple-mail-mcp 2.10.23 → 2.10.25
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 +10 -9
- package/build/index.js +89 -69
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -852,11 +852,12 @@ likely to be refused as ambiguous. Passing the source mailbox explicitly is the
|
|
|
852
852
|
stay scoped, and it overrides the remembered location. These parameters name where the ids **came
|
|
853
853
|
from**; for `batch-move-messages` that is distinct from `mailbox`, the destination.
|
|
854
854
|
|
|
855
|
-
`sourceMailbox`
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
855
|
+
`sourceMailbox` and `sourceAccount` are an atomic scope pair: provide **both** for numeric ids.
|
|
856
|
+
The server never fills in a missing account from mutable default-send state, because the same
|
|
857
|
+
mailbox name can exist in more than one account and numeric ids are only unique within an account
|
|
858
|
+
and mailbox. A whitespace-only source field is rejected. `sourceAccount` by itself pins nothing,
|
|
859
|
+
since the mailbox is what an id is scoped to; `imap:…` ids ignore both fields because they carry
|
|
860
|
+
their own account, mailbox, and UID identity.
|
|
860
861
|
|
|
861
862
|
**A repeated id is one message.** `ids` is treated as a set: a duplicate names the same message,
|
|
862
863
|
so it is operated on once, and the batch returns **one result per distinct id**. `success` is
|
|
@@ -868,7 +869,7 @@ therefore a count of messages, not of list positions.
|
|
|
868
869
|
|-----------|------|----------|-------------|
|
|
869
870
|
| `ids` | string[] | Yes | Message IDs to delete (max 100) |
|
|
870
871
|
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
871
|
-
| `sourceAccount` | string | No | Account the numeric ids were listed from.
|
|
872
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Required when `sourceMailbox` is supplied; on its own it pins nothing. |
|
|
872
873
|
|
|
873
874
|
`structuredContent` carries `countDelta` — what the batch actually did to each
|
|
874
875
|
source mailbox. See [Auditing destructive operations](#auditing-destructive-operations).
|
|
@@ -883,7 +884,7 @@ source mailbox. See [Auditing destructive operations](#auditing-destructive-oper
|
|
|
883
884
|
| `mailbox` | string | Yes | Destination mailbox |
|
|
884
885
|
| `account` | string | No | Account containing mailbox |
|
|
885
886
|
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
886
|
-
| `sourceAccount` | string | No | Account the numeric ids were listed from.
|
|
887
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Required when `sourceMailbox` is supplied; on its own it pins nothing. |
|
|
887
888
|
|
|
888
889
|
`structuredContent` carries `countDelta` — what the batch actually did to each
|
|
889
890
|
**source** mailbox. See [Auditing destructive operations](#auditing-destructive-operations).
|
|
@@ -894,7 +895,7 @@ source mailbox. See [Auditing destructive operations](#auditing-destructive-oper
|
|
|
894
895
|
|-----------|------|----------|-------------|
|
|
895
896
|
| `ids` | string[] | Yes | Message IDs (max 100) |
|
|
896
897
|
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
897
|
-
| `sourceAccount` | string | No | Account the numeric ids were listed from.
|
|
898
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Required when `sourceMailbox` is supplied; on its own it pins nothing. |
|
|
898
899
|
|
|
899
900
|
#### `batch-flag-messages` / `batch-unflag-messages`
|
|
900
901
|
|
|
@@ -903,7 +904,7 @@ source mailbox. See [Auditing destructive operations](#auditing-destructive-oper
|
|
|
903
904
|
| `ids` | string[] | Yes | Message IDs (max 100) |
|
|
904
905
|
| `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. |
|
|
905
906
|
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
906
|
-
| `sourceAccount` | string | No | Account the numeric ids were listed from.
|
|
907
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Required when `sourceMailbox` is supplied; on its own it pins nothing. |
|
|
907
908
|
|
|
908
909
|
---
|
|
909
910
|
|
package/build/index.js
CHANGED
|
@@ -78300,6 +78300,7 @@ var DIAG_ITEM_SEP = "M";
|
|
|
78300
78300
|
var CONTENT_MARKER = "CONTENT";
|
|
78301
78301
|
var MSGID_MARKER = "MSGID";
|
|
78302
78302
|
var HTML_MARKER = "HTML";
|
|
78303
|
+
var LOOKUP_ERROR_MARKER = "ERR";
|
|
78303
78304
|
var BATCH_FATAL = "FATAL";
|
|
78304
78305
|
var RECON_TAG = "RECON";
|
|
78305
78306
|
var SNAP_TAG = "SNAP";
|
|
@@ -78653,6 +78654,8 @@ var AppleMailManager = class {
|
|
|
78653
78654
|
* misses and falls back to the full scan, so it can never wedge a lookup.
|
|
78654
78655
|
*/
|
|
78655
78656
|
idLocationIndex = /* @__PURE__ */ new Map();
|
|
78657
|
+
/** Error from the most recent numeric message read, if it was refused. */
|
|
78658
|
+
lastMessageLookupError;
|
|
78656
78659
|
/** Cap on the id→location index so a long-lived process can't grow unbounded. */
|
|
78657
78660
|
ID_LOCATION_MAX = 5e3;
|
|
78658
78661
|
/** Record (or refresh) where a message id lives, evicting oldest when full. */
|
|
@@ -78681,6 +78684,12 @@ var AppleMailManager = class {
|
|
|
78681
78684
|
noteMessageLocation(id, account, mailbox) {
|
|
78682
78685
|
this.rememberLocation(id, account, mailbox);
|
|
78683
78686
|
}
|
|
78687
|
+
/** Consume the most recent read refusal so the tool layer can preserve it. */
|
|
78688
|
+
consumeLastMessageLookupError() {
|
|
78689
|
+
const error2 = this.lastMessageLookupError;
|
|
78690
|
+
this.lastMessageLookupError = void 0;
|
|
78691
|
+
return error2;
|
|
78692
|
+
}
|
|
78684
78693
|
/**
|
|
78685
78694
|
* AppleScript fragment resolving `account` + `mailbox` into `_tmb`, leaving
|
|
78686
78695
|
* `_tmb` as `missing value` when it can't be pinned down. Exact-name match
|
|
@@ -79699,6 +79708,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79699
79708
|
* returned the entire raw MIME blob mislabeled as HTML (#32).
|
|
79700
79709
|
*/
|
|
79701
79710
|
getMessageContent(id, includeHtml = false, hint) {
|
|
79711
|
+
this.lastMessageLookupError = void 0;
|
|
79702
79712
|
const sourceFetch = includeHtml ? `set htmlSource to ""
|
|
79703
79713
|
try
|
|
79704
79714
|
set htmlSource to source of msg
|
|
@@ -79724,17 +79734,25 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79724
79734
|
}
|
|
79725
79735
|
const script = buildAppLevelScript(`
|
|
79726
79736
|
try
|
|
79737
|
+
set _hits to {}
|
|
79738
|
+
set _names to ""
|
|
79727
79739
|
repeat with acct in accounts
|
|
79728
79740
|
repeat with mb in mailboxes of acct
|
|
79729
79741
|
try
|
|
79730
79742
|
set matchingMsgs to (messages of mb whose id is ${Number(id)})
|
|
79731
79743
|
if (count of matchingMsgs) > 0 then
|
|
79732
|
-
set
|
|
79733
|
-
|
|
79744
|
+
set end of _hits to item 1 of matchingMsgs
|
|
79745
|
+
set _names to _names & (name of acct) & "/" & (name of mb) & ", "
|
|
79734
79746
|
end if
|
|
79735
79747
|
end try
|
|
79736
79748
|
end repeat
|
|
79737
79749
|
end repeat
|
|
79750
|
+
if (count of _hits) is 0 then return "${LOOKUP_ERROR_MARKER}Message not found"
|
|
79751
|
+
if (count of _hits) > 1 then return "${LOOKUP_ERROR_MARKER}${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the read targets the right copy"
|
|
79752
|
+
if (count of _hits) is 1 then
|
|
79753
|
+
set msg to item 1 of _hits
|
|
79754
|
+
${innerFetch}
|
|
79755
|
+
end if
|
|
79738
79756
|
return ""
|
|
79739
79757
|
on error errMsg
|
|
79740
79758
|
return ""
|
|
@@ -79756,6 +79774,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79756
79774
|
if (!result.success) console.error(`Failed to get message content: ${result.error}`);
|
|
79757
79775
|
return null;
|
|
79758
79776
|
}
|
|
79777
|
+
if (result.output.startsWith(LOOKUP_ERROR_MARKER)) {
|
|
79778
|
+
this.lastMessageLookupError = result.output.slice(LOOKUP_ERROR_MARKER.length).trim();
|
|
79779
|
+
return null;
|
|
79780
|
+
}
|
|
79759
79781
|
const htmlSplit = result.output.split(HTML_MARKER);
|
|
79760
79782
|
const contentPart = htmlSplit[0];
|
|
79761
79783
|
const rawSource = htmlSplit.length > 1 ? htmlSplit[1] : "";
|
|
@@ -79783,6 +79805,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79783
79805
|
* a 20MB attachment can take several seconds over Exchange/IMAP.
|
|
79784
79806
|
*/
|
|
79785
79807
|
getRawSource(id, hint) {
|
|
79808
|
+
this.lastMessageLookupError = void 0;
|
|
79786
79809
|
const loc = hint?.account && hint?.mailbox ? { account: hint.account, mailbox: hint.mailbox } : this.idLocationIndex.get(id.toString());
|
|
79787
79810
|
if (loc) {
|
|
79788
79811
|
const scopedScript = this.scopedByIdScript(
|
|
@@ -79792,21 +79815,34 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79792
79815
|
"return source of msg"
|
|
79793
79816
|
);
|
|
79794
79817
|
const scoped = executeAppleScript(scopedScript, { timeoutMs: 12e4 });
|
|
79795
|
-
if (scoped.success && scoped.output.trim()
|
|
79818
|
+
if (scoped.success && scoped.output.trim() && !scoped.output.startsWith(LOOKUP_ERROR_MARKER)) {
|
|
79819
|
+
return scoped.output;
|
|
79820
|
+
}
|
|
79821
|
+
if (scoped.success && scoped.output.startsWith(LOOKUP_ERROR_MARKER)) {
|
|
79822
|
+
this.lastMessageLookupError = scoped.output.slice(LOOKUP_ERROR_MARKER.length).trim();
|
|
79823
|
+
}
|
|
79796
79824
|
}
|
|
79797
79825
|
const script = buildAppLevelScript(`
|
|
79798
79826
|
try
|
|
79827
|
+
set _hits to {}
|
|
79828
|
+
set _names to ""
|
|
79799
79829
|
repeat with acct in accounts
|
|
79800
79830
|
repeat with mb in mailboxes of acct
|
|
79801
79831
|
try
|
|
79802
79832
|
set matchingMsgs to (messages of mb whose id is ${Number(id)})
|
|
79803
79833
|
if (count of matchingMsgs) > 0 then
|
|
79804
|
-
set
|
|
79805
|
-
|
|
79834
|
+
set end of _hits to item 1 of matchingMsgs
|
|
79835
|
+
set _names to _names & (name of acct) & "/" & (name of mb) & ", "
|
|
79806
79836
|
end if
|
|
79807
79837
|
end try
|
|
79808
79838
|
end repeat
|
|
79809
79839
|
end repeat
|
|
79840
|
+
if (count of _hits) is 0 then return "${LOOKUP_ERROR_MARKER}Message not found"
|
|
79841
|
+
if (count of _hits) > 1 then return "${LOOKUP_ERROR_MARKER}${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the read targets the right copy"
|
|
79842
|
+
if (count of _hits) is 1 then
|
|
79843
|
+
set msg to item 1 of _hits
|
|
79844
|
+
return source of msg
|
|
79845
|
+
end if
|
|
79810
79846
|
return ""
|
|
79811
79847
|
on error errMsg
|
|
79812
79848
|
return ""
|
|
@@ -79816,6 +79852,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79816
79852
|
if (!result.success || !result.output.trim()) {
|
|
79817
79853
|
return null;
|
|
79818
79854
|
}
|
|
79855
|
+
if (result.output.startsWith(LOOKUP_ERROR_MARKER)) {
|
|
79856
|
+
this.lastMessageLookupError = result.output.slice(LOOKUP_ERROR_MARKER.length).trim();
|
|
79857
|
+
return null;
|
|
79858
|
+
}
|
|
79819
79859
|
return result.output;
|
|
79820
79860
|
}
|
|
79821
79861
|
/**
|
|
@@ -80256,27 +80296,13 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80256
80296
|
const safeBody = escapeForAppleScriptBody(body);
|
|
80257
80297
|
const replyAllClause = replyAll ? " with reply to all" : "";
|
|
80258
80298
|
const sendAction = send ? "send theReply" : "";
|
|
80259
|
-
const script =
|
|
80260
|
-
|
|
80261
|
-
|
|
80262
|
-
|
|
80263
|
-
|
|
80264
|
-
|
|
80265
|
-
|
|
80266
|
-
set msg to item 1 of matchingMsgs
|
|
80267
|
-
set theReply to reply msg without opening window${replyAllClause}
|
|
80268
|
-
set content of theReply to "${safeBody}"
|
|
80269
|
-
${sendAction}
|
|
80270
|
-
return "ok"
|
|
80271
|
-
end if
|
|
80272
|
-
end try
|
|
80273
|
-
end repeat
|
|
80274
|
-
end repeat
|
|
80275
|
-
return "error:Message not found"
|
|
80276
|
-
on error errMsg
|
|
80277
|
-
return "error:" & errMsg
|
|
80278
|
-
end try
|
|
80279
|
-
`);
|
|
80299
|
+
const script = this.findMessageScript(
|
|
80300
|
+
id,
|
|
80301
|
+
`
|
|
80302
|
+
set theReply to reply msg without opening window${replyAllClause}
|
|
80303
|
+
set content of theReply to "${safeBody}"
|
|
80304
|
+
${sendAction}`
|
|
80305
|
+
);
|
|
80280
80306
|
const result = executeAppleScript(script, { timeoutMs: 6e4 });
|
|
80281
80307
|
if (!result.success || result.output.startsWith("error:")) {
|
|
80282
80308
|
console.error(`Failed to reply to message: ${result.error || result.output}`);
|
|
@@ -80301,28 +80327,14 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80301
80327
|
recipientCommands += `make new to recipient at end of to recipients of theForward with properties {address:"${escapeForAppleScript(addr)}"}
|
|
80302
80328
|
`;
|
|
80303
80329
|
}
|
|
80304
|
-
const script =
|
|
80305
|
-
|
|
80306
|
-
|
|
80307
|
-
|
|
80308
|
-
|
|
80309
|
-
|
|
80310
|
-
|
|
80311
|
-
|
|
80312
|
-
set theForward to forward msg without opening window
|
|
80313
|
-
${recipientCommands}
|
|
80314
|
-
${safeBody ? `set content of theForward to "${safeBody}"` : ""}
|
|
80315
|
-
${sendAction}
|
|
80316
|
-
return "ok"
|
|
80317
|
-
end if
|
|
80318
|
-
end try
|
|
80319
|
-
end repeat
|
|
80320
|
-
end repeat
|
|
80321
|
-
return "error:Message not found"
|
|
80322
|
-
on error errMsg
|
|
80323
|
-
return "error:" & errMsg
|
|
80324
|
-
end try
|
|
80325
|
-
`);
|
|
80330
|
+
const script = this.findMessageScript(
|
|
80331
|
+
id,
|
|
80332
|
+
`
|
|
80333
|
+
set theForward to forward msg without opening window
|
|
80334
|
+
${recipientCommands}
|
|
80335
|
+
${safeBody ? `set content of theForward to "${safeBody}"` : ""}
|
|
80336
|
+
${sendAction}`
|
|
80337
|
+
);
|
|
80326
80338
|
const result = executeAppleScript(script, { timeoutMs: 6e4 });
|
|
80327
80339
|
if (!result.success || result.output.startsWith("error:")) {
|
|
80328
80340
|
console.error(`Failed to forward message: ${result.error || result.output}`);
|
|
@@ -80734,39 +80746,44 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80734
80746
|
/**
|
|
80735
80747
|
* Turn a caller-supplied batch source scope into an account+mailbox pair.
|
|
80736
80748
|
*
|
|
80737
|
-
*
|
|
80738
|
-
*
|
|
80739
|
-
*
|
|
80740
|
-
*
|
|
80741
|
-
*
|
|
80742
|
-
* ambiguous, which is the very failure the parameter exists to prevent.
|
|
80749
|
+
* A numeric source scope is an account+mailbox pair. A mailbox name alone is
|
|
80750
|
+
* not an identity: the same mailbox can exist in several accounts, and a
|
|
80751
|
+
* numeric Mail id is not globally unique. Resolving a missing account from
|
|
80752
|
+
* mutable default-send state can therefore target the wrong account. Require
|
|
80753
|
+
* both fields so a caller cannot silently cross that account boundary.
|
|
80743
80754
|
*
|
|
80744
80755
|
* The safety property is absolute: when the account cannot be determined there
|
|
80745
80756
|
* is NO fallback to the scan-and-guess walk. The caller gets an error naming
|
|
80746
80757
|
* the mailbox it asked for, so it can retry with an explicit `sourceAccount`.
|
|
80747
80758
|
* (An `account` with no `mailbox` cannot pin anything, so it scopes nothing —
|
|
80748
|
-
* those ids still go through the index / ambiguity-checked path.
|
|
80759
|
+
* those ids still go through the index / ambiguity-checked path. A supplied
|
|
80760
|
+
* whitespace-only field is rejected rather than silently discarded.)
|
|
80749
80761
|
*/
|
|
80750
80762
|
resolveBatchScope(scope) {
|
|
80751
|
-
const
|
|
80752
|
-
|
|
80753
|
-
const
|
|
80754
|
-
|
|
80755
|
-
|
|
80756
|
-
|
|
80763
|
+
const rawMailbox = scope?.mailbox;
|
|
80764
|
+
const rawAccount = scope?.account;
|
|
80765
|
+
const mailbox = rawMailbox?.trim();
|
|
80766
|
+
const account = rawAccount?.trim();
|
|
80767
|
+
if (rawMailbox !== void 0 && !mailbox) {
|
|
80768
|
+
return {
|
|
80769
|
+
kind: "unresolvable",
|
|
80770
|
+
error: "sourceMailbox must contain a mailbox name; whitespace-only scope is not allowed."
|
|
80771
|
+
};
|
|
80772
|
+
}
|
|
80773
|
+
if (rawAccount !== void 0 && !account) {
|
|
80757
80774
|
return {
|
|
80758
80775
|
kind: "unresolvable",
|
|
80759
|
-
error:
|
|
80776
|
+
error: "sourceAccount must contain an account name; whitespace-only scope is not allowed."
|
|
80760
80777
|
};
|
|
80761
80778
|
}
|
|
80762
|
-
|
|
80763
|
-
if (!
|
|
80779
|
+
if (!mailbox) return { kind: "none" };
|
|
80780
|
+
if (!account) {
|
|
80764
80781
|
return {
|
|
80765
80782
|
kind: "unresolvable",
|
|
80766
|
-
error: `Cannot scope to source mailbox "${mailbox}"
|
|
80783
|
+
error: `Cannot scope to source mailbox "${mailbox}" without sourceAccount: numeric Mail ids are only unique within an account and mailbox. Retry with both sourceAccount and sourceMailbox explicitly set.`
|
|
80767
80784
|
};
|
|
80768
80785
|
}
|
|
80769
|
-
return { kind: "scoped", account
|
|
80786
|
+
return { kind: "scoped", account, mailbox };
|
|
80770
80787
|
}
|
|
80771
80788
|
/**
|
|
80772
80789
|
* Run one operation over many message IDs in a SINGLE osascript invocation.
|
|
@@ -84420,10 +84437,10 @@ function withJsonSchema2020_12(transport2) {
|
|
|
84420
84437
|
// src/index.ts
|
|
84421
84438
|
loadFileConfig();
|
|
84422
84439
|
var BATCH_SOURCE_MAILBOX_SCHEMA = external_exports.string().optional().describe(
|
|
84423
|
-
"Mailbox the numeric ids were listed from (e.g. 'INBOX').
|
|
84440
|
+
"Mailbox the numeric ids were listed from (e.g. 'INBOX'). Must be paired with sourceAccount to form an unambiguous scope. Ignored for imap: ids."
|
|
84424
84441
|
);
|
|
84425
84442
|
var BATCH_SOURCE_ACCOUNT_SCHEMA = external_exports.string().optional().describe(
|
|
84426
|
-
"Account the numeric ids were listed from.
|
|
84443
|
+
"Account the numeric ids were listed from. Required when sourceMailbox is supplied; on its own it pins nothing."
|
|
84427
84444
|
);
|
|
84428
84445
|
var FLAG_COLOR_INDEX = {
|
|
84429
84446
|
red: 0,
|
|
@@ -84730,7 +84747,10 @@ Do not use when: you don't yet have an id (use search-messages or list-messages
|
|
|
84730
84747
|
account,
|
|
84731
84748
|
mailbox
|
|
84732
84749
|
});
|
|
84733
|
-
if (!content)
|
|
84750
|
+
if (!content) {
|
|
84751
|
+
const lookupError = mailManager.consumeLastMessageLookupError();
|
|
84752
|
+
return errorResponse(lookupError ?? `Message with ID "${id}" not found`);
|
|
84753
|
+
}
|
|
84734
84754
|
const isHtml = preferHtml === true && !!content.htmlContent;
|
|
84735
84755
|
const body = isHtml ? content.htmlContent : content.plainText;
|
|
84736
84756
|
return successResponse(`Subject: ${content.subject}
|
package/package.json
CHANGED