apple-mail-mcp 2.20.14 → 2.20.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 +33 -28
- package/build/index.js +74 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -227,21 +227,21 @@ This section documents all available tools. AI agents should use these tool name
|
|
|
227
227
|
|
|
228
228
|
Search for messages matching criteria. Searches all accounts by default.
|
|
229
229
|
|
|
230
|
-
| Parameter | Type | Required | Description
|
|
231
|
-
| ----------- | ------- | -------- |
|
|
232
|
-
| `query` | string | No | Text to search in subject/sender
|
|
233
|
-
| `body` | string | No | Text to search in the message body. IMAP backend only (server-side `BODY` search); AppleScript-only accounts are reported as not searched
|
|
234
|
-
| `from` | string | No | Filter by sender email address
|
|
235
|
-
| `subject` | string | No | Filter by subject line
|
|
236
|
-
| `mailbox` | string | No | Mailbox to search in (omit to search all mailboxes)
|
|
237
|
-
| `account` | string | No | Account to search in (omit to search all accounts)
|
|
238
|
-
| `isRead` | boolean | No | Filter by read status
|
|
239
|
-
| `isFlagged` | boolean | No | Filter by flagged status
|
|
230
|
+
| Parameter | Type | Required | Description |
|
|
231
|
+
| ----------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
232
|
+
| `query` | string | No | Text to search in subject/sender |
|
|
233
|
+
| `body` | string | No | Text to search in the message body. IMAP backend only (server-side `BODY` search); AppleScript-only accounts are reported as not searched |
|
|
234
|
+
| `from` | string | No | Filter by sender email address |
|
|
235
|
+
| `subject` | string | No | Filter by subject line |
|
|
236
|
+
| `mailbox` | string | No | Mailbox to search in (omit to search all mailboxes) |
|
|
237
|
+
| `account` | string | No | Account to search in (omit to search all accounts) |
|
|
238
|
+
| `isRead` | boolean | No | Filter by read status |
|
|
239
|
+
| `isFlagged` | boolean | No | Filter by flagged status |
|
|
240
240
|
| `flagColor` | string | No | Filter to messages flagged with exactly this color — see [`flag-message`](#flag-message--unflag-message) for the palette. Implies `isFlagged: true`; pairing this with `isFlagged: false` is rejected. See the flag-color note below. |
|
|
241
|
-
| `dateFrom` | string | No | Start date filter (e.g., "January 1, 2026")
|
|
242
|
-
| `dateTo` | string | No | End date filter (e.g., "March 1, 2026")
|
|
243
|
-
| `limit` | number | No | Max results, 1–500 (default: 50)
|
|
244
|
-
| `offset` | number | No | Newest matches to skip, ≥ 0 (for pagination). IMAP accounts only; without a `mailbox`, `offset` + `limit` may not exceed 5,000
|
|
241
|
+
| `dateFrom` | string | No | Start date filter (e.g., "January 1, 2026") |
|
|
242
|
+
| `dateTo` | string | No | End date filter (e.g., "March 1, 2026") |
|
|
243
|
+
| `limit` | number | No | Max results, 1–500 (default: 50) |
|
|
244
|
+
| `offset` | number | No | Newest matches to skip, ≥ 0 (for pagination). IMAP accounts only; without a `mailbox`, `offset` + `limit` may not exceed 5,000 |
|
|
245
245
|
|
|
246
246
|
**Returns:** List of matching messages with ID, date, subject, sender, read state,
|
|
247
247
|
and `flagColor` when flagged with an explicit color.
|
|
@@ -436,19 +436,19 @@ Send a new email immediately.
|
|
|
436
436
|
|
|
437
437
|
**⚠️ Safety:** Sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling.
|
|
438
438
|
|
|
439
|
-
| Parameter | Type | Required | Description
|
|
440
|
-
| ------------- | --------------------------------------- | -------- |
|
|
441
|
-
| `to` | string[] | Yes | Recipient addresses
|
|
442
|
-
| `subject` | string | Yes | Email subject
|
|
443
|
-
| `body` | string | Yes | Email body (plain text)
|
|
444
|
-
| `cc` | string[] | No | CC recipients
|
|
445
|
-
| `bcc` | string[] | No | BCC recipients
|
|
446
|
-
| `account` | string | No | Mail.app account name or exact sender address (AppleScript path: resolved to one address of an enabled account and verified against Mail's `sender` before sending — no match, or Mail would use another sender, is an error and nothing is sent; the sender used is returned as `from`), or an email-form SMTP From override. An SMTP override must match `APPLE_MAIL_MCP_SMTP_USER`, `APPLE_MAIL_MCP_SMTP_FROM`, or an address in `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM`
|
|
447
|
-
| `attachments` | (string \| {filename, contentBase64})[] | No | Up to 20 attachments: absolute file paths inside the configured read roots (e.g., `"/Users/me/Documents/report.pdf"`) and/or inline `{filename, contentBase64}` objects up to 25 MiB decoded each
|
|
448
|
-
| `transport` | `"applescript"` \| `"smtp"` | No | Send transport. If omitted, **SMTP is used automatically when configured** (otherwise AppleScript). Pass `"smtp"` to require clean MIME, or `"applescript"` to force the Mail.app path — see [SMTP transport](#smtp-transport)
|
|
449
|
-
| `replyTo` | string | No | SMTP only: `Reply-To` header, when replies should go somewhere other than the From address. Ignored on the AppleScript transport
|
|
450
|
-
| `inReplyTo` | string | No | SMTP only (2.20.0): thread the message as a reply — the parent's `Message-ID` token with angle brackets, e.g. `"<abc@mail.example.com>"`. Emitted verbatim, never RFC 2047-encoded. See [Threading headers](#threading-headers-inreplyto--references)
|
|
451
|
-
| `references` | string[] | No | SMTP only (2.20.0): the `References` chain, oldest first (the parent's own `References` + its `Message-ID`). Defaults to `[inReplyTo]` when omitted
|
|
439
|
+
| Parameter | Type | Required | Description |
|
|
440
|
+
| ------------- | --------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
441
|
+
| `to` | string[] | Yes | Recipient addresses |
|
|
442
|
+
| `subject` | string | Yes | Email subject |
|
|
443
|
+
| `body` | string | Yes | Email body (plain text) |
|
|
444
|
+
| `cc` | string[] | No | CC recipients |
|
|
445
|
+
| `bcc` | string[] | No | BCC recipients |
|
|
446
|
+
| `account` | string | No | Mail.app account name or exact sender address (AppleScript path: resolved to one address of an enabled account and verified against Mail's `sender` before sending — no match, or Mail would use another sender, is an error and nothing is sent; the sender used is returned as `from`), or an email-form SMTP From override. An SMTP override must match `APPLE_MAIL_MCP_SMTP_USER`, `APPLE_MAIL_MCP_SMTP_FROM`, or an address in `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM` |
|
|
447
|
+
| `attachments` | (string \| {filename, contentBase64})[] | No | Up to 20 attachments: absolute file paths inside the configured read roots (e.g., `"/Users/me/Documents/report.pdf"`) and/or inline `{filename, contentBase64}` objects up to 25 MiB decoded each |
|
|
448
|
+
| `transport` | `"applescript"` \| `"smtp"` | No | Send transport. If omitted, **SMTP is used automatically when configured** (otherwise AppleScript). Pass `"smtp"` to require clean MIME, or `"applescript"` to force the Mail.app path — see [SMTP transport](#smtp-transport) |
|
|
449
|
+
| `replyTo` | string | No | SMTP only: `Reply-To` header, when replies should go somewhere other than the From address. Ignored on the AppleScript transport |
|
|
450
|
+
| `inReplyTo` | string | No | SMTP only (2.20.0): thread the message as a reply — the parent's `Message-ID` token with angle brackets, e.g. `"<abc@mail.example.com>"`. Emitted verbatim, never RFC 2047-encoded. See [Threading headers](#threading-headers-inreplyto--references) |
|
|
451
|
+
| `references` | string[] | No | SMTP only (2.20.0): the `References` chain, oldest first (the parent's own `References` + its `Message-ID`). Defaults to `[inReplyTo]` when omitted |
|
|
452
452
|
|
|
453
453
|
**Example:**
|
|
454
454
|
|
|
@@ -954,12 +954,17 @@ nodemailer path `send-email` uses, so the Sent-folder copy (`sentCopy`) carries
|
|
|
954
954
|
On the AppleScript transport they are added with `make new attachment` **after** the
|
|
955
955
|
reply content is set and before `save`/`send` (inline items are written to `0600` temp
|
|
956
956
|
files and removed afterwards), so a `send: false` draft contains both the attachments and
|
|
957
|
-
the quoted original.
|
|
957
|
+
the quoted original. `make new attachment` returns before Mail has loaded the file, so the
|
|
958
|
+
script waits until Mail counts every file (bounded at 15 s) before saving or sending
|
|
959
|
+
(2.20.16, #293; the same wait applies to `send-email` and `create-draft`). Results report
|
|
960
|
+
`attachmentCount`.
|
|
958
961
|
|
|
959
962
|
**Delivery:** `imap:` IDs are read directly from their encoded account, mailbox, and UID. Numeric IDs are read through Mail.app. With SMTP configured, replies use clean MIME with the original `Message-ID` in `In-Reply-To` and the full `References` chain. Only the original plain-text body is quoted; the new text is not. Pass `transport: "smtp"` to require this path, or `transport: "applescript"` to use Mail.app explicitly.
|
|
960
963
|
|
|
961
964
|
**AppleScript transport quoting (v2.19.12):** every draft (`send: false`) and any send that falls back to AppleScript (SMTP not configured) also gets the original quoted — an "On \<date\>, \<sender\> wrote:" attribution line followed by the `> `-prefixed original body, built the same way as the SMTP path. Mail's own `reply`/`forward` AppleScript commands generate this quote internally but expose no way to read it back, so it's built here instead of relying on Mail's (unreadable) copy. Unlike SMTP, this path has no sending-identity restriction — it's read-only source lookup, not sending.
|
|
962
965
|
|
|
966
|
+
**Sender on the AppleScript transport (2.20.15, #294):** Mail's `reply`/`forward` commands set no sender, so Mail would answer from its default sending account. The reply or forward is now sent from the account the original sits in: the original's to/cc address that belongs to that account — or, for a message you sent, its own From address — else the account's first address. The sender is read back before saving or sending; a mismatch deletes the compose and returns an error. A message in an "On My Mac" mailbox has no account, so its recipients are matched against every enabled account and, with no match, the sender is left to Mail. The same applies to `forward-message`.
|
|
967
|
+
|
|
963
968
|
**Failure behavior:** once SMTP is selected, a missing password, unreadable source, missing reply address or `Message-ID`, or SMTP failure returns an error. It does not silently switch to AppleScript or create an unthreaded new message. With SMTP unconfigured and transport omitted, AppleScript remains available. `send: false` saves a Mail.app draft; combining it with `transport: "smtp"` is rejected before composing.
|
|
964
969
|
|
|
965
970
|
The direct IMAP source read is bounded to 25 MiB, including MIME attachments. Its account login must match the SMTP login, configured From, or an explicitly configured `APPLE_MAIL_MCP_SMTP_ALLOWED_FROM` identity; otherwise the call fails rather than sending from an unrelated account. The SMTP configuration still represents a single sending identity, not a per-account SMTP registry.
|
package/build/index.js
CHANGED
|
@@ -83216,6 +83216,50 @@ function stripStreamDelimiters(value) {
|
|
|
83216
83216
|
}
|
|
83217
83217
|
var AMBIGUOUS_ID_PREFIX = "Message id ";
|
|
83218
83218
|
var AMBIGUOUS_ID_BATCH = "This message id is present in more than one mailbox ";
|
|
83219
|
+
var senderFragment = (compose, verb) => `set _rsAddrs to {}
|
|
83220
|
+
set _rsOwn to true
|
|
83221
|
+
try
|
|
83222
|
+
set _rsAddrs to email addresses of (account of (mailbox of msg))
|
|
83223
|
+
set _rsN to count of _rsAddrs
|
|
83224
|
+
on error
|
|
83225
|
+
set _rsOwn to false
|
|
83226
|
+
set _rsAddrs to {}
|
|
83227
|
+
repeat with _rsAc in (every account whose enabled is true)
|
|
83228
|
+
try
|
|
83229
|
+
set _rsAddrs to _rsAddrs & (email addresses of _rsAc)
|
|
83230
|
+
end try
|
|
83231
|
+
end repeat
|
|
83232
|
+
end try
|
|
83233
|
+
if _rsOwn and (count of _rsAddrs) is 0 then error "The account this message arrived in has no email address to ${verb} from."
|
|
83234
|
+
set _rsFrom to ""
|
|
83235
|
+
if _rsOwn then set _rsFrom to (item 1 of _rsAddrs) as text
|
|
83236
|
+
set _rsCands to {}
|
|
83237
|
+
repeat with _rsR in ((to recipients of msg) & (cc recipients of msg))
|
|
83238
|
+
set end of _rsCands to (address of _rsR) as text
|
|
83239
|
+
end repeat
|
|
83240
|
+
try
|
|
83241
|
+
set end of _rsCands to (extract address from (sender of msg)) as text
|
|
83242
|
+
end try
|
|
83243
|
+
set _rsDone to false
|
|
83244
|
+
repeat with _rsC in _rsCands
|
|
83245
|
+
if not _rsDone then
|
|
83246
|
+
repeat with _rsA in _rsAddrs
|
|
83247
|
+
if (_rsC as text) is (_rsA as text) then
|
|
83248
|
+
set _rsFrom to (_rsA as text)
|
|
83249
|
+
set _rsDone to true
|
|
83250
|
+
exit repeat
|
|
83251
|
+
end if
|
|
83252
|
+
end repeat
|
|
83253
|
+
end if
|
|
83254
|
+
end repeat
|
|
83255
|
+
if _rsFrom is not "" then
|
|
83256
|
+
set sender of ${compose} to _rsFrom
|
|
83257
|
+
set _rsGot to sender of ${compose} as text
|
|
83258
|
+
if not (_rsGot is _rsFrom or _rsGot ends with "<" & _rsFrom & ">") then
|
|
83259
|
+
delete ${compose}
|
|
83260
|
+
error "Mail.app would ${verb} as " & _rsGot & " instead of " & _rsFrom & ", so nothing was sent or saved."
|
|
83261
|
+
end if
|
|
83262
|
+
end if`;
|
|
83219
83263
|
function normalizeRfcMessageId(mid) {
|
|
83220
83264
|
return (mid || "").trim().replace(/^<+/, "").replace(/>+$/, "").trim();
|
|
83221
83265
|
}
|
|
@@ -83330,10 +83374,30 @@ function buildAttachmentCommands(attachments) {
|
|
|
83330
83374
|
}
|
|
83331
83375
|
return commands;
|
|
83332
83376
|
}
|
|
83377
|
+
var ATTACHMENT_SETTLE_TIMEOUT_S = 15;
|
|
83378
|
+
var ATTACHMENT_SETTLE_GRACE_S = 1;
|
|
83379
|
+
function attachmentSettleBlock(msgVar, expected) {
|
|
83380
|
+
if (expected <= 0) return "";
|
|
83381
|
+
return `
|
|
83382
|
+
set attachDeadline to (current date) + ${ATTACHMENT_SETTLE_TIMEOUT_S}
|
|
83383
|
+
repeat
|
|
83384
|
+
set attachCount to 0
|
|
83385
|
+
try
|
|
83386
|
+
set attachCount to count of attachments of content of ${msgVar}
|
|
83387
|
+
end try
|
|
83388
|
+
if attachCount >= ${expected} then exit repeat
|
|
83389
|
+
if (current date) > attachDeadline then exit repeat
|
|
83390
|
+
delay 0.25
|
|
83391
|
+
end repeat
|
|
83392
|
+
delay ${ATTACHMENT_SETTLE_GRACE_S}`;
|
|
83393
|
+
}
|
|
83394
|
+
function countAttachmentCommands(commands) {
|
|
83395
|
+
return (commands.match(/make new attachment/g) || []).length;
|
|
83396
|
+
}
|
|
83333
83397
|
function composeAttachmentBlock(msgVar, paths) {
|
|
83334
83398
|
const commands = buildAttachmentCommands(paths);
|
|
83335
83399
|
return commands ? `tell ${msgVar}
|
|
83336
|
-
${commands}end tell` : "";
|
|
83400
|
+
${commands}end tell${attachmentSettleBlock(msgVar, paths.length)}` : "";
|
|
83337
83401
|
}
|
|
83338
83402
|
function asDateToString(varName) {
|
|
83339
83403
|
return `((year of ${varName}) as string) & "-" & ((month of ${varName} as integer) as string) & "-" & ((day of ${varName}) as string) & "-" & ((hours of ${varName}) as string) & "-" & ((minutes of ${varName}) as string) & "-" & ((seconds of ${varName}) as string)`;
|
|
@@ -85686,12 +85750,15 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
85686
85750
|
}
|
|
85687
85751
|
/**
|
|
85688
85752
|
* Build and run the AppleScript compose for send-email (`send`) and
|
|
85689
|
-
* create-draft (`draft`): pin the sender when `account` is given,
|
|
85690
|
-
*
|
|
85753
|
+
* create-draft (`draft`): pin the sender when `account` is given, wait for
|
|
85754
|
+
* any attachments to load (attachmentSettleBlock), verify the sender before
|
|
85755
|
+
* the message leaves, and report the sender Mail actually used. A draft is
|
|
85756
|
+
* saved and closed explicitly rather than left to Mail's autosave (#293).
|
|
85691
85757
|
*/
|
|
85692
85758
|
composeWithCommands(mode, recipientCommands, safeSubject, safeBody, account, attachmentCommands) {
|
|
85693
85759
|
const sender = this.resolveSender(account);
|
|
85694
85760
|
if ("error" in sender) return { success: false, error: sender.error };
|
|
85761
|
+
const settle2 = attachmentSettleBlock("newMessage", countAttachmentCommands(attachmentCommands));
|
|
85695
85762
|
const prefix = mode === "send" ? SENT_PREFIX : DRAFT_PREFIX;
|
|
85696
85763
|
const script = buildAppLevelScript(`
|
|
85697
85764
|
set newMessage to make new outgoing message with properties {subject:"${safeSubject}", content:"${safeBody}", visible:${mode === "send"}}
|
|
@@ -85699,9 +85766,9 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
85699
85766
|
${recipientCommands}
|
|
85700
85767
|
${sender.setSender}
|
|
85701
85768
|
${attachmentCommands}
|
|
85702
|
-
end tell
|
|
85769
|
+
end tell${settle2}
|
|
85703
85770
|
${sender.guard}
|
|
85704
|
-
${mode === "send" ? "send newMessage" : ""}
|
|
85771
|
+
${mode === "send" ? "send newMessage" : "save newMessage\n close newMessage saving yes"}
|
|
85705
85772
|
return "${prefix}" & actualSender
|
|
85706
85773
|
`);
|
|
85707
85774
|
const result = executeAppleScript(script, { timeoutMs: 6e4, maxRetries: 1 });
|
|
@@ -85858,6 +85925,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
85858
85925
|
id,
|
|
85859
85926
|
`
|
|
85860
85927
|
set theReply to reply msg without opening window${replyAllClause}
|
|
85928
|
+
${senderFragment("theReply", "reply")}
|
|
85861
85929
|
set content of theReply to "${safeBody}"
|
|
85862
85930
|
${composeAttachmentBlock("theReply", mat.paths)}
|
|
85863
85931
|
${finalAction}`
|
|
@@ -85891,6 +85959,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
85891
85959
|
id,
|
|
85892
85960
|
`
|
|
85893
85961
|
set theForward to forward msg without opening window
|
|
85962
|
+
${senderFragment("theForward", "forward")}
|
|
85894
85963
|
${recipientCommands}
|
|
85895
85964
|
${safeBody ? `set content of theForward to "${safeBody}"` : ""}
|
|
85896
85965
|
${composeAttachmentBlock("theForward", mat.paths)}
|
package/package.json
CHANGED