apple-mail-mcp 1.5.1 → 1.5.3

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
@@ -823,4 +823,6 @@ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for gui
823
823
 
824
824
  ## Related Projects
825
825
 
826
- - [apple-notes-mcp](https://github.com/sweetrb/apple-notes-mcp) - MCP server for Apple Notes
826
+ - [apple-notes-mcp](https://github.com/sweetrb/apple-notes-mcp) MCP server for Apple Notes
827
+ - [apple-numbers-mcp](https://github.com/sweetrb/apple-numbers-mcp) — MCP server for Apple Numbers spreadsheets
828
+ - [apple-photos-mcp](https://github.com/sweetrb/apple-photos-mcp) — MCP server for Apple Photos
package/build/index.js CHANGED
@@ -103,8 +103,11 @@ function withErrorHandling(handler, errorPrefix) {
103
103
  // --- search-messages ---
104
104
  server.tool("search-messages", {
105
105
  query: z.string().optional().describe("Text to search for in subject, sender, or content"),
106
- from: z.string().optional().describe("Filter by sender email address"),
107
- subject: z.string().optional().describe("Filter by subject line"),
106
+ from: z
107
+ .string()
108
+ .optional()
109
+ .describe("Filter by sender (substring match against the full sender string, i.e. display name + address — not an exact address match)"),
110
+ subject: z.string().optional().describe("Filter by subject line (substring match)"),
108
111
  mailbox: z
109
112
  .string()
110
113
  .optional()
@@ -115,8 +118,8 @@ server.tool("search-messages", {
115
118
  dateFrom: DATE_FILTER_SCHEMA.describe("Start date filter (e.g., 'January 1, 2026')"),
116
119
  dateTo: DATE_FILTER_SCHEMA.describe("End date filter (e.g., 'March 1, 2026')"),
117
120
  limit: z.number().optional().describe("Maximum number of results (default: 50)"),
118
- }, withErrorHandling(({ query, mailbox, account, limit = 50, dateFrom, dateTo }) => {
119
- const messages = mailManager.searchMessages(query, mailbox, account, limit, dateFrom, dateTo);
121
+ }, withErrorHandling(({ query, mailbox, account, limit = 50, dateFrom, dateTo, from, subject, isRead, isFlagged, }) => {
122
+ const messages = mailManager.searchMessages(query, mailbox, account, limit, dateFrom, dateTo, from, subject, isRead, isFlagged);
120
123
  if (messages.length === 0) {
121
124
  return successResponse("No messages found matching criteria");
122
125
  }
@@ -26,6 +26,27 @@ import type { Message, MessageContent, Mailbox, Account, Attachment, HealthCheck
26
26
  * execution via osascript. Error handling is consistent: methods
27
27
  * return null/false/empty-array on failure rather than throwing.
28
28
  */
29
+ export interface SearchConditionFilters {
30
+ query?: string;
31
+ from?: string;
32
+ subject?: string;
33
+ isRead?: boolean;
34
+ isFlagged?: boolean;
35
+ }
36
+ /**
37
+ * Build the AppleScript `whose` clause for searchMessages from a filter set.
38
+ *
39
+ * - `query` is a subject-OR-sender substring match, parenthesized so it groups
40
+ * correctly when ANDed with other filters.
41
+ * - `from` and `subject` are substring matches (`sender`/`subject` contains).
42
+ * - `isRead` / `isFlagged` are boolean status checks.
43
+ * - Returns "" when no filters are set. Every interpolated value is escaped.
44
+ *
45
+ * Exported for unit testing: the bug this addresses (filters declared in the
46
+ * tool schema but silently dropped) lived in this logic, so it gets direct
47
+ * coverage independent of Mail.app.
48
+ */
49
+ export declare function buildSearchCondition(filters: SearchConditionFilters): string;
29
50
  export declare class AppleMailManager {
30
51
  /**
31
52
  * Default account used when no account is specified.
@@ -88,7 +109,7 @@ export declare class AppleMailManager {
88
109
  * @param limit - Maximum number of results
89
110
  * @returns Array of matching messages
90
111
  */
91
- searchMessages(query?: string, mailbox?: string, account?: string, limit?: number, dateFrom?: string, dateTo?: string): Message[];
112
+ searchMessages(query?: string, mailbox?: string, account?: string, limit?: number, dateFrom?: string, dateTo?: string, from?: string, subject?: string, isRead?: boolean, isFlagged?: boolean): Message[];
92
113
  /**
93
114
  * Get a message by ID.
94
115
  *
@@ -216,6 +237,25 @@ export declare class AppleMailManager {
216
237
  /**
217
238
  * Move a message to a different mailbox.
218
239
  */
240
+ /**
241
+ * Move a message to a destination mailbox, with full nested-mailbox support.
242
+ *
243
+ * Resolving the destination as `mailbox "X" of account "Y"` only finds
244
+ * top-level mailboxes, so nested destinations (e.g. a "Moore" subfolder)
245
+ * silently failed. Instead we walk the target account's full mailbox tree and
246
+ * match by name. Resolution is:
247
+ * - account-scoped (won't move to a same-named mailbox in another account)
248
+ * - ambiguity-aware: if the name matches more than one mailbox in the
249
+ * account we refuse to guess and return an error — silently moving mail to
250
+ * the wrong folder is worse than failing.
251
+ * The source message is located by walking every account's tree breadth-first
252
+ * (top-level mailboxes like Inbox are checked first), so messages in nested
253
+ * mailboxes are found too.
254
+ *
255
+ * Returns a result object so batch callers can surface the specific failure
256
+ * (destination not found / ambiguous / message not found).
257
+ */
258
+ private moveMessageInternal;
219
259
  moveMessage(id: string, mailbox: string, account?: string): boolean;
220
260
  /**
221
261
  * Delete multiple messages at once.
@@ -1 +1 @@
1
- {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
@@ -123,22 +123,40 @@ const MAILBOX_ALIASES = {
123
123
  junk: ["Junk", "Junk Email", "Spam", "JUNK", "junk"],
124
124
  archive: ["Archive", "ARCHIVE", "archive", "All Mail"],
125
125
  };
126
- // =============================================================================
127
- // Apple Mail Manager Class
128
- // =============================================================================
129
126
  /**
130
- * Manager class for Apple Mail operations.
127
+ * Build the AppleScript `whose` clause for searchMessages from a filter set.
131
128
  *
132
- * Provides methods for:
133
- * - Reading and searching messages
134
- * - Sending emails
135
- * - Managing mailboxes
136
- * - Listing accounts
129
+ * - `query` is a subject-OR-sender substring match, parenthesized so it groups
130
+ * correctly when ANDed with other filters.
131
+ * - `from` and `subject` are substring matches (`sender`/`subject` contains).
132
+ * - `isRead` / `isFlagged` are boolean status checks.
133
+ * - Returns "" when no filters are set. Every interpolated value is escaped.
137
134
  *
138
- * All operations are synchronous since they rely on AppleScript
139
- * execution via osascript. Error handling is consistent: methods
140
- * return null/false/empty-array on failure rather than throwing.
135
+ * Exported for unit testing: the bug this addresses (filters declared in the
136
+ * tool schema but silently dropped) lived in this logic, so it gets direct
137
+ * coverage independent of Mail.app.
141
138
  */
139
+ export function buildSearchCondition(filters) {
140
+ const { query, from, subject, isRead, isFlagged } = filters;
141
+ const conditions = [];
142
+ if (query) {
143
+ const safeQuery = escapeForAppleScript(query);
144
+ conditions.push(`(subject contains "${safeQuery}" or sender contains "${safeQuery}")`);
145
+ }
146
+ if (from) {
147
+ conditions.push(`sender contains "${escapeForAppleScript(from)}"`);
148
+ }
149
+ if (subject) {
150
+ conditions.push(`subject contains "${escapeForAppleScript(subject)}"`);
151
+ }
152
+ if (typeof isRead === "boolean") {
153
+ conditions.push(`read status is ${isRead ? "true" : "false"}`);
154
+ }
155
+ if (typeof isFlagged === "boolean") {
156
+ conditions.push(`flagged status is ${isFlagged ? "true" : "false"}`);
157
+ }
158
+ return conditions.length > 0 ? `whose ${conditions.join(" and ")}` : "";
159
+ }
142
160
  export class AppleMailManager {
143
161
  /**
144
162
  * Default account used when no account is specified.
@@ -289,7 +307,7 @@ export class AppleMailManager {
289
307
  * @param limit - Maximum number of results
290
308
  * @returns Array of matching messages
291
309
  */
292
- searchMessages(query, mailbox, account, limit = 50, dateFrom, dateTo) {
310
+ searchMessages(query, mailbox, account, limit = 50, dateFrom, dateTo, from, subject, isRead, isFlagged) {
293
311
  // If no account specified, search across all accounts
294
312
  if (!account) {
295
313
  const accounts = this.listAccounts();
@@ -298,18 +316,16 @@ export class AppleMailManager {
298
316
  if (allMessages.length >= limit)
299
317
  break;
300
318
  const remaining = limit - allMessages.length;
301
- const msgs = this.searchMessages(query, mailbox, acct.name, remaining, dateFrom, dateTo);
319
+ const msgs = this.searchMessages(query, mailbox, acct.name, remaining, dateFrom, dateTo, from, subject, isRead, isFlagged);
302
320
  allMessages.push(...msgs);
303
321
  }
304
322
  return allMessages.slice(0, limit);
305
323
  }
306
324
  const targetAccount = this.resolveAccount(account);
307
- // Build the search condition
308
- let searchCondition = "";
309
- if (query) {
310
- const safeQuery = escapeForAppleScript(query);
311
- searchCondition = `whose subject contains "${safeQuery}" or sender contains "${safeQuery}"`;
312
- }
325
+ // `query` is a subject-OR-sender substring match; from/subject/isRead/isFlagged
326
+ // are additional AND filters. Date filtering stays post-fetch below — `whose`
327
+ // date comparisons are unreliable in Mail.app AppleScript. See buildSearchCondition.
328
+ const searchCondition = buildSearchCondition({ query, from, subject, isRead, isFlagged });
313
329
  // Build date filter AppleScript.
314
330
  // Note: dateFrom/dateTo are already validated by DATE_FILTER_SCHEMA (alphanumeric + safe
315
331
  // punctuation only), so escapeForAppleScript() below is belt-and-suspenders — it won't
@@ -720,6 +736,30 @@ export class AppleMailManager {
720
736
  * @param account - Account to send from
721
737
  * @returns true if sent successfully
722
738
  */
739
+ // ───────────────────────────────────────────────────────────────────
740
+ // KNOWN BUG: outgoing emails sent via AppleScript on macOS 15+ get wrapped
741
+ // in <blockquote type="cite"> under the Apple-Mail-URLShareWrapperClass
742
+ // template, so they render to recipients as quoted/forwarded content.
743
+ // Plain-text alternative gets `>` prefixes on every line.
744
+ //
745
+ // Reproduces with EVERY AppleScript message-creation pattern I tried:
746
+ // • make new outgoing message with properties {content: ..., ...}
747
+ // • make new outgoing message (no content) + `set content of newMessage`
748
+ // • setting `default message format` to plain format first
749
+ //
750
+ // Apple radar FB11734014 (open since Ventura, no movement).
751
+ // Discussion: https://forums.macrumors.com/threads/applescript-creating-a-
752
+ // new-message-in-mail-app-is-causing-weird-formatting-issues.2385052/
753
+ //
754
+ // Workaround for callers who need clean emails today: use SMTP directly
755
+ // (Python smtplib). See e.g. sweetrb/nhl-bracket-tracker's send_email.py.
756
+ //
757
+ // Proper fix is probably to abandon `make new outgoing message` and either:
758
+ // 1. Build the .emlx file ourselves and drop it into a Drafts mailbox.
759
+ // 2. Switch to smtplib-style direct send with Keychain-stored creds.
760
+ // 3. Use Mail.app's NSSharingService rather than AppleScript.
761
+ // Tracking issue: https://github.com/sweetrb/apple-mail-mcp/issues/12
762
+ // ───────────────────────────────────────────────────────────────────
723
763
  sendEmail(to, subject, body, cc, bcc, account, attachments) {
724
764
  const safeSubject = escapeForAppleScript(subject);
725
765
  const safeBody = escapeForAppleScript(body);
@@ -1056,21 +1096,53 @@ export class AppleMailManager {
1056
1096
  /**
1057
1097
  * Move a message to a different mailbox.
1058
1098
  */
1059
- moveMessage(id, mailbox, account) {
1099
+ /**
1100
+ * Move a message to a destination mailbox, with full nested-mailbox support.
1101
+ *
1102
+ * Resolving the destination as `mailbox "X" of account "Y"` only finds
1103
+ * top-level mailboxes, so nested destinations (e.g. a "Moore" subfolder)
1104
+ * silently failed. Instead we walk the target account's full mailbox tree and
1105
+ * match by name. Resolution is:
1106
+ * - account-scoped (won't move to a same-named mailbox in another account)
1107
+ * - ambiguity-aware: if the name matches more than one mailbox in the
1108
+ * account we refuse to guess and return an error — silently moving mail to
1109
+ * the wrong folder is worse than failing.
1110
+ * The source message is located by walking every account's tree breadth-first
1111
+ * (top-level mailboxes like Inbox are checked first), so messages in nested
1112
+ * mailboxes are found too.
1113
+ *
1114
+ * Returns a result object so batch callers can surface the specific failure
1115
+ * (destination not found / ambiguous / message not found).
1116
+ */
1117
+ moveMessageInternal(id, mailbox, account) {
1060
1118
  const targetAccount = this.resolveAccount(account);
1061
1119
  const targetMailbox = this.resolveMailbox(mailbox, targetAccount);
1062
1120
  const safeMailbox = escapeForAppleScript(targetMailbox);
1063
1121
  const safeAccount = escapeForAppleScript(targetAccount);
1064
1122
  const script = buildAppLevelScript(`
1065
1123
  try
1124
+ -- \`mailboxes of account\` is already flat: it includes nested mailboxes
1125
+ -- (named by path, e.g. "Processed/Vendors"). Descending via \`mailboxes of mb\`
1126
+ -- is unreliable (it double-prepends the parent path), so we DON'T recurse —
1127
+ -- we match against this flat list by exact name and use the reference directly
1128
+ -- (addressing \`mailbox "X" of account "Y"\` only finds some top-level mailboxes).
1129
+ set destName to "${safeMailbox}"
1130
+ set destMatches to {}
1131
+ repeat with mb in (mailboxes of account "${safeAccount}")
1132
+ if (name of mb) is destName then set end of destMatches to mb
1133
+ end repeat
1134
+ if (count of destMatches) is 0 then return "error:Destination mailbox \\"" & destName & "\\" not found in account \\"${safeAccount}\\""
1135
+ if (count of destMatches) > 1 then return "error:Destination mailbox \\"" & destName & "\\" is ambiguous (" & (count of destMatches) & " matches) in account \\"${safeAccount}\\"; disambiguate or move by full path"
1136
+ set destMailbox to item 1 of destMatches
1137
+
1138
+ -- Find the message by id. The flat mailbox list already covers nested
1139
+ -- mailboxes, so this reaches messages in subfolders without recursing.
1066
1140
  repeat with acct in accounts
1067
- repeat with mb in mailboxes of acct
1141
+ repeat with mb in (mailboxes of acct)
1068
1142
  try
1069
1143
  set matchingMsgs to (messages of mb whose id is ${Number(id)})
1070
1144
  if (count of matchingMsgs) > 0 then
1071
- set msg to item 1 of matchingMsgs
1072
- set destMailbox to mailbox "${safeMailbox}" of account "${safeAccount}"
1073
- move msg to destMailbox
1145
+ move (item 1 of matchingMsgs) to destMailbox
1074
1146
  return "ok"
1075
1147
  end if
1076
1148
  end try
@@ -1081,12 +1153,21 @@ export class AppleMailManager {
1081
1153
  return "error:" & errMsg
1082
1154
  end try
1083
1155
  `);
1084
- const result = executeAppleScript(script, { timeoutMs: 60000 });
1085
- if (!result.success || result.output.startsWith("error:")) {
1086
- console.error(`Failed to move message: ${result.error || result.output}`);
1087
- return false;
1156
+ const result = executeAppleScript(script, { timeoutMs: 90000 });
1157
+ if (!result.success) {
1158
+ return { success: false, error: result.error || "AppleScript execution failed" };
1088
1159
  }
1089
- return true;
1160
+ if (result.output.startsWith("error:")) {
1161
+ return { success: false, error: result.output.slice("error:".length) };
1162
+ }
1163
+ return { success: true };
1164
+ }
1165
+ moveMessage(id, mailbox, account) {
1166
+ const { success, error } = this.moveMessageInternal(id, mailbox, account);
1167
+ if (!success) {
1168
+ console.error(`Failed to move message: ${error}`);
1169
+ }
1170
+ return success;
1090
1171
  }
1091
1172
  // ===========================================================================
1092
1173
  // Batch Operations
@@ -1120,11 +1201,11 @@ export class AppleMailManager {
1120
1201
  batchMoveMessages(ids, mailbox, account) {
1121
1202
  const results = [];
1122
1203
  for (const id of ids) {
1123
- const success = this.moveMessage(id, mailbox, account);
1204
+ const { success, error } = this.moveMessageInternal(id, mailbox, account);
1124
1205
  results.push({
1125
1206
  id,
1126
1207
  success,
1127
- error: success ? undefined : "Failed to move message",
1208
+ error: success ? undefined : error || "Failed to move message",
1128
1209
  });
1129
1210
  }
1130
1211
  return results;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude",
5
5
  "type": "module",
6
6
  "main": "build/index.js",