apple-mail-mcp 2.20.13 → 2.20.15
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 -27
- package/build/index.js +94 -7
- 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
|
|
|
@@ -960,6 +960,8 @@ the quoted original. Results report `attachmentCount`.
|
|
|
960
960
|
|
|
961
961
|
**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
962
|
|
|
963
|
+
**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`.
|
|
964
|
+
|
|
963
965
|
**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
966
|
|
|
965
967
|
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
|
@@ -65211,6 +65211,30 @@ function isInlineDisposition(headers) {
|
|
|
65211
65211
|
if (!dispHeader) return false;
|
|
65212
65212
|
return dispHeader.toLowerCase().startsWith("inline");
|
|
65213
65213
|
}
|
|
65214
|
+
function extractContentId(headers) {
|
|
65215
|
+
const raw = getHeader(headers, "Content-ID");
|
|
65216
|
+
if (!raw) return null;
|
|
65217
|
+
const id = raw.replace(/^<+/, "").replace(/>+$/, "").trim().toLowerCase();
|
|
65218
|
+
return id || null;
|
|
65219
|
+
}
|
|
65220
|
+
function isEmbeddedResource(headers, htmlBodies) {
|
|
65221
|
+
if (!isInlineDisposition(headers)) return false;
|
|
65222
|
+
const cid = extractContentId(headers);
|
|
65223
|
+
if (!cid) return false;
|
|
65224
|
+
const needle = `cid:${cid}`;
|
|
65225
|
+
return htmlBodies.some((html) => html.includes(needle));
|
|
65226
|
+
}
|
|
65227
|
+
function collectHtmlBodies(parts) {
|
|
65228
|
+
const bodies = [];
|
|
65229
|
+
for (const part of parts) {
|
|
65230
|
+
if (extractMimeType(part.headers) !== "text/html") continue;
|
|
65231
|
+
const encoding = getHeader(part.headers, "Content-Transfer-Encoding");
|
|
65232
|
+
bodies.push(
|
|
65233
|
+
decodePartText(decodeBody(part.body, encoding), extractCharset(part.headers)).toLowerCase()
|
|
65234
|
+
);
|
|
65235
|
+
}
|
|
65236
|
+
return bodies;
|
|
65237
|
+
}
|
|
65214
65238
|
function extractSize(headers) {
|
|
65215
65239
|
const dispHeader = getHeader(headers, "Content-Disposition");
|
|
65216
65240
|
if (dispHeader) {
|
|
@@ -65316,11 +65340,12 @@ function parseMimeAttachments(source) {
|
|
|
65316
65340
|
const boundary = extractBoundary(source);
|
|
65317
65341
|
if (!boundary) return [];
|
|
65318
65342
|
const parts = walkLeafParts(source, boundary);
|
|
65343
|
+
const htmlBodies = collectHtmlBodies(parts);
|
|
65319
65344
|
const attachments = [];
|
|
65320
65345
|
for (const part of parts) {
|
|
65321
65346
|
const filename = extractFilename(part.headers);
|
|
65322
65347
|
if (!filename) continue;
|
|
65323
|
-
if (
|
|
65348
|
+
if (isEmbeddedResource(part.headers, htmlBodies)) continue;
|
|
65324
65349
|
const encoding = getHeader(part.headers, "Content-Transfer-Encoding");
|
|
65325
65350
|
attachments.push({
|
|
65326
65351
|
name: filename,
|
|
@@ -83191,6 +83216,50 @@ function stripStreamDelimiters(value) {
|
|
|
83191
83216
|
}
|
|
83192
83217
|
var AMBIGUOUS_ID_PREFIX = "Message id ";
|
|
83193
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`;
|
|
83194
83263
|
function normalizeRfcMessageId(mid) {
|
|
83195
83264
|
return (mid || "").trim().replace(/^<+/, "").replace(/>+$/, "").trim();
|
|
83196
83265
|
}
|
|
@@ -85833,6 +85902,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
85833
85902
|
id,
|
|
85834
85903
|
`
|
|
85835
85904
|
set theReply to reply msg without opening window${replyAllClause}
|
|
85905
|
+
${senderFragment("theReply", "reply")}
|
|
85836
85906
|
set content of theReply to "${safeBody}"
|
|
85837
85907
|
${composeAttachmentBlock("theReply", mat.paths)}
|
|
85838
85908
|
${finalAction}`
|
|
@@ -85866,6 +85936,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
85866
85936
|
id,
|
|
85867
85937
|
`
|
|
85868
85938
|
set theForward to forward msg without opening window
|
|
85939
|
+
${senderFragment("theForward", "forward")}
|
|
85869
85940
|
${recipientCommands}
|
|
85870
85941
|
${safeBody ? `set content of theForward to "${safeBody}"` : ""}
|
|
85871
85942
|
${composeAttachmentBlock("theForward", mat.paths)}
|
|
@@ -86686,13 +86757,29 @@ ${this.errorEmit(" ")}
|
|
|
86686
86757
|
set msg to item 1 of matchingMsgs
|
|
86687
86758
|
set outputText to ""
|
|
86688
86759
|
set attCount to 0
|
|
86760
|
+
-- Each property is read inside its own try: \`MIME type of att\` throws
|
|
86761
|
+
-- "AppleEvent handler failed" for attachments Apple Mail itself composed,
|
|
86762
|
+
-- and one unguarded throw discards the whole list, so the MIME fallback
|
|
86763
|
+
-- answers "no attachments". An unreadable type or size degrades to a
|
|
86764
|
+
-- default; only a missing name drops the entry (it cannot be addressed).
|
|
86689
86765
|
repeat with att in mail attachments of msg
|
|
86690
|
-
set attName to
|
|
86691
|
-
set attType to
|
|
86692
|
-
set attSize to
|
|
86693
|
-
|
|
86694
|
-
|
|
86695
|
-
|
|
86766
|
+
set attName to ""
|
|
86767
|
+
set attType to "application/octet-stream"
|
|
86768
|
+
set attSize to "0"
|
|
86769
|
+
try
|
|
86770
|
+
set attName to name of att
|
|
86771
|
+
end try
|
|
86772
|
+
try
|
|
86773
|
+
set attType to MIME type of att
|
|
86774
|
+
end try
|
|
86775
|
+
try
|
|
86776
|
+
set attSize to file size of att as string
|
|
86777
|
+
end try
|
|
86778
|
+
if attName is not "" then
|
|
86779
|
+
if attCount > 0 then set outputText to outputText & "${RECORD_SEP}"
|
|
86780
|
+
set outputText to outputText & attName & "${FIELD_SEP}" & attType & "${FIELD_SEP}" & attSize
|
|
86781
|
+
set attCount to attCount + 1
|
|
86782
|
+
end if
|
|
86696
86783
|
end repeat
|
|
86697
86784
|
return outputText
|
|
86698
86785
|
end if
|
package/package.json
CHANGED