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.
Files changed (3) hide show
  1. package/README.md +29 -27
  2. package/build/index.js +94 -7
  3. 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 (isInlineDisposition(part.headers)) continue;
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 name of att
86691
- set attType to MIME type of att
86692
- set attSize to file size of att as string
86693
- if attCount > 0 then set outputText to outputText & "${RECORD_SEP}"
86694
- set outputText to outputText & attName & "${FIELD_SEP}" & attType & "${FIELD_SEP}" & attSize
86695
- set attCount to attCount + 1
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.20.13",
3
+ "version": "2.20.15",
4
4
  "description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",