apple-mail-mcp 2.19.11 → 2.19.12

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 CHANGED
@@ -826,7 +826,7 @@ Reply to an existing message.
826
826
  | Parameter | Type | Required | Description |
827
827
  | ----------- | ------- | -------- | ---------------------------------------------------------------------------------------------- |
828
828
  | `id` | string | Yes | Message ID to reply to |
829
- | `body` | string | Yes | Reply body |
829
+ | `body` | string | Yes | Reply body (plain text; HTML tags such as `<br>` are not rendered) |
830
830
  | `replyAll` | boolean | No | Reply to all recipients (default: false) |
831
831
  | `send` | boolean | No | Send immediately (default: true, false = save as draft) |
832
832
  | `transport` | string | No | `smtp` or `applescript`; omitted prefers configured SMTP when sending. Drafts use AppleScript. |
@@ -853,6 +853,8 @@ Reply to an existing message.
853
853
 
854
854
  **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.
855
855
 
856
+ **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.
857
+
856
858
  **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.
857
859
 
858
860
  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.
@@ -871,11 +873,11 @@ Forward a message to new recipients.
871
873
  | ----------- | -------- | -------- | ---------------------------------------------------------------------------------------------- |
872
874
  | `id` | string | Yes | Message ID to forward |
873
875
  | `to` | string[] | Yes | Recipients to forward to |
874
- | `body` | string | No | Message to prepend |
876
+ | `body` | string | No | Message to prepend (plain text) |
875
877
  | `send` | boolean | No | Send immediately (default: true, false = save as draft) |
876
878
  | `transport` | string | No | `smtp` or `applescript`; omitted prefers configured SMTP when sending. Drafts use AppleScript. |
877
879
 
878
- **Delivery:** uses the same source lookup, transport selection, 25 MiB source limit, account-identity check, and failure behavior as `reply-to-message`. A forward deliberately starts a new conversation, so it has no `In-Reply-To` or `References` headers. The existing plain-text forwarding behavior is unchanged: original attachments are not reattached. See [SMTP transport](#smtp-transport).
880
+ **Delivery:** uses the same source lookup, transport selection, 25 MiB source limit, account-identity check, and failure behavior as `reply-to-message`. A forward deliberately starts a new conversation, so it has no `In-Reply-To` or `References` headers. The existing plain-text forwarding behavior is unchanged: original attachments are not reattached. See [SMTP transport](#smtp-transport). On the AppleScript transport, a `body` is merged with the forwarded-message header block and original text the same way the SMTP path builds it (see `reply-to-message`'s AppleScript transport quoting note); with no `body`, Mail's own forward content is left untouched.
879
881
 
880
882
  SMTP forwarding requires a readable plain-text original. HTML-only IMAP messages and failed Mail.app body reads return an error before sending instead of silently omitting the original content. Explicitly select `transport: "applescript"` to forward these with Mail.app; no automatic fallback occurs. An intentionally empty plain-text message is still valid.
881
883
 
package/build/index.js CHANGED
@@ -86847,10 +86847,6 @@ function buildReplyOptions(args) {
86847
86847
  );
86848
86848
  cc = extra.length ? extra : void 0;
86849
86849
  }
86850
- const attribution = buildAttribution(original);
86851
- const quoted = originalPlainText.trim() ? `
86852
-
86853
- ${attribution}${quoteBody(originalPlainText)}` : "";
86854
86850
  const references = dedupe(
86855
86851
  original.messageId ? [...original.references, original.messageId] : original.references
86856
86852
  );
@@ -86858,12 +86854,19 @@ ${attribution}${quoteBody(originalPlainText)}` : "";
86858
86854
  to,
86859
86855
  cc,
86860
86856
  subject: withSubjectPrefix(original.subject, "Re:"),
86861
- body: `${body}${quoted}`,
86857
+ body: buildReplyBody(body, original, originalPlainText),
86862
86858
  inReplyTo: original.messageId,
86863
86859
  references: references.length ? references : void 0,
86864
86860
  from
86865
86861
  };
86866
86862
  }
86863
+ function buildReplyBody(body, original, originalPlainText) {
86864
+ const attribution = buildAttribution(original);
86865
+ const quoted = originalPlainText.trim() ? `
86866
+
86867
+ ${attribution}${quoteBody(originalPlainText)}` : "";
86868
+ return `${body}${quoted}`;
86869
+ }
86867
86870
  function buildAttribution(original) {
86868
86871
  const who = original.from[0] ?? original.replyTo[0] ?? "the sender";
86869
86872
  return original.date ? `On ${original.date}, ${who} wrote:
@@ -86872,6 +86875,14 @@ function buildAttribution(original) {
86872
86875
  }
86873
86876
  function buildForwardOptions(args) {
86874
86877
  const { original, originalPlainText, to, body, from } = args;
86878
+ return {
86879
+ to: dedupe(to),
86880
+ subject: withSubjectPrefix(original.subject, "Fwd:"),
86881
+ body: buildForwardBody(original, originalPlainText, body),
86882
+ from
86883
+ };
86884
+ }
86885
+ function buildForwardBody(original, originalPlainText, body) {
86875
86886
  const headerBlock = [
86876
86887
  "---------- Forwarded message ----------",
86877
86888
  original.from.length ? `From: ${original.from.join(", ")}` : "",
@@ -86883,14 +86894,9 @@ function buildForwardOptions(args) {
86883
86894
  const prefix = body?.trim() ? `${body}
86884
86895
 
86885
86896
  ` : "";
86886
- return {
86887
- to: dedupe(to),
86888
- subject: withSubjectPrefix(original.subject, "Fwd:"),
86889
- body: `${prefix}${headerBlock}
86897
+ return `${prefix}${headerBlock}
86890
86898
 
86891
- ${originalPlainText}`,
86892
- from
86893
- };
86899
+ ${originalPlainText}`;
86894
86900
  }
86895
86901
 
86896
86902
  // src/tools/compose.ts
@@ -87028,6 +87034,31 @@ async function readOriginal(deps, id, cfg) {
87028
87034
  const content = deps.mail.getMessageContent(id);
87029
87035
  return { original: parseOriginalHeaders(raw), plainText: content?.plainText ?? null };
87030
87036
  }
87037
+ async function readOriginalForQuote(deps, id) {
87038
+ if (decodeImapId(id)) {
87039
+ try {
87040
+ const source = await deps.imapSource(id);
87041
+ const original = parseOriginalHeaders(source.raw);
87042
+ if (source.subject !== void 0) original.subject = source.subject;
87043
+ return { original, plainText: extractTextBody(source.raw) ?? "" };
87044
+ } catch {
87045
+ return null;
87046
+ }
87047
+ }
87048
+ const raw = deps.mail.getRawSource(id);
87049
+ if (!raw) return null;
87050
+ const content = deps.mail.getMessageContent(id);
87051
+ return { original: parseOriginalHeaders(raw), plainText: content?.plainText ?? "" };
87052
+ }
87053
+ async function replyComposeBody(deps, id, body) {
87054
+ const source = await readOriginalForQuote(deps, id);
87055
+ return source ? buildReplyBody(body, source.original, source.plainText) : body;
87056
+ }
87057
+ async function forwardComposeBody(deps, id, body) {
87058
+ if (!body) return body;
87059
+ const source = await readOriginalForQuote(deps, id);
87060
+ return source ? buildForwardBody(source.original, source.plainText, body) : body;
87061
+ }
87031
87062
  async function runCompose(deps, args) {
87032
87063
  const { id, send, transport: transport2 } = args;
87033
87064
  const verb = args.kind === "reply" ? "reply to" : "forward";
@@ -87098,7 +87129,17 @@ async function runCompose(deps, args) {
87098
87129
  return errorResponse(
87099
87130
  `Failed to ${verb} message "${id}": ${resolved.error ?? "message not found"}`
87100
87131
  );
87101
- const outcome = args.kind === "reply" ? deps.mail.replyToMessage(resolved.numericId, args.body, args.replyAll, send) : deps.mail.forwardMessage(resolved.numericId, args.to, args.body, send);
87132
+ const outcome = args.kind === "reply" ? deps.mail.replyToMessage(
87133
+ resolved.numericId,
87134
+ await replyComposeBody(deps, id, args.body),
87135
+ args.replyAll,
87136
+ send
87137
+ ) : deps.mail.forwardMessage(
87138
+ resolved.numericId,
87139
+ args.to,
87140
+ await forwardComposeBody(deps, id, args.body),
87141
+ send
87142
+ );
87102
87143
  if (!outcome.success)
87103
87144
  return errorResponse(
87104
87145
  `Failed to ${verb} message "${id}": ${outcome.error ?? "Mail.app compose failed"}`
@@ -88858,7 +88899,7 @@ registerTool(
88858
88899
  inputSchema: {
88859
88900
  id: MESSAGE_ID_SCHEMA,
88860
88901
  transport: COMPOSE_TRANSPORT_SCHEMA,
88861
- body: external_exports.string().min(1, "Reply body is required"),
88902
+ body: external_exports.string().min(1, "Reply body is required").describe("Reply body (plain text; HTML tags such as <br> are not rendered)"),
88862
88903
  replyAll: external_exports.boolean().optional().default(false).describe("Reply to all recipients"),
88863
88904
  send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)")
88864
88905
  },
@@ -88882,7 +88923,7 @@ registerTool(
88882
88923
  id: MESSAGE_ID_SCHEMA,
88883
88924
  transport: COMPOSE_TRANSPORT_SCHEMA,
88884
88925
  to: external_exports.array(external_exports.string()).min(1, "At least one recipient is required"),
88885
- body: external_exports.string().optional().describe("Optional message to prepend"),
88926
+ body: external_exports.string().optional().describe("Optional message to prepend (plain text)"),
88886
88927
  send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)")
88887
88928
  },
88888
88929
  outputSchema: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.19.11",
3
+ "version": "2.19.12",
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",