@sempervirens-labs/apple-mail-mcp 1.4.0 → 1.5.0
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 +51 -0
- package/package.json +1 -1
- package/src/applescript/mail.ts +111 -5
- package/src/index.ts +22 -0
- package/src/tools.ts +33 -0
package/README.md
CHANGED
|
@@ -181,6 +181,57 @@ Get recent emails from a mailbox.
|
|
|
181
181
|
}
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
+
### `mail_get_emails_by_ids`
|
|
185
|
+
|
|
186
|
+
Get specific emails by their IDs. Useful for retrieving full details of specific emails after browsing with `mail_get_emails` (with `includeContent: false`).
|
|
187
|
+
|
|
188
|
+
| Parameter | Type | Required | Default | Description |
|
|
189
|
+
|-----------|------|----------|---------|-------------|
|
|
190
|
+
| `ids` | number[] | **Yes** | - | Array of email IDs to retrieve |
|
|
191
|
+
| `account` | string | No | - | Account name (helps optimize search) |
|
|
192
|
+
| `mailbox` | string | No | "INBOX" | Mailbox name (helps optimize search) |
|
|
193
|
+
| `includeContent` | boolean | No | true | Include email body |
|
|
194
|
+
|
|
195
|
+
**Example usage:**
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"ids": [12345, 67890],
|
|
199
|
+
"includeContent": true
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Example response:**
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"emails": [
|
|
207
|
+
{
|
|
208
|
+
"id": 12345,
|
|
209
|
+
"subject": "Meeting tomorrow",
|
|
210
|
+
"sender": "John Doe <john@example.com>",
|
|
211
|
+
"to": ["you@example.com"],
|
|
212
|
+
"cc": [],
|
|
213
|
+
"bcc": [],
|
|
214
|
+
"dateSent": "Monday, 10. January 2025 at 09:30:00",
|
|
215
|
+
"isRead": false,
|
|
216
|
+
"content": "Let's meet at 2pm to discuss the project..."
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"id": 67890,
|
|
220
|
+
"subject": "Project update",
|
|
221
|
+
"sender": "Jane Smith <jane@example.com>",
|
|
222
|
+
"to": ["you@example.com"],
|
|
223
|
+
"cc": [],
|
|
224
|
+
"bcc": [],
|
|
225
|
+
"dateSent": "Tuesday, 11. January 2025 at 14:15:00",
|
|
226
|
+
"isRead": true,
|
|
227
|
+
"content": "The project is progressing well..."
|
|
228
|
+
}
|
|
229
|
+
],
|
|
230
|
+
"count": 2,
|
|
231
|
+
"requestedIds": [12345, 67890]
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
184
235
|
### `mail_search`
|
|
185
236
|
|
|
186
237
|
Search emails by subject, sender, or content.
|
package/package.json
CHANGED
package/src/applescript/mail.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { execSync } from "child_process";
|
|
|
3
3
|
/**
|
|
4
4
|
* Execute an AppleScript and return the result
|
|
5
5
|
*/
|
|
6
|
-
export function runAppleScript(script: string): string {
|
|
6
|
+
export function runAppleScript(script: string, timeoutMs: number = 30000): string {
|
|
7
7
|
try {
|
|
8
8
|
// Use osascript with heredoc to handle complex scripts
|
|
9
9
|
const result = execSync(`osascript <<'APPLESCRIPT'
|
|
@@ -11,6 +11,7 @@ ${script}
|
|
|
11
11
|
APPLESCRIPT`, {
|
|
12
12
|
encoding: "utf-8",
|
|
13
13
|
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large results
|
|
14
|
+
timeout: timeoutMs,
|
|
14
15
|
});
|
|
15
16
|
return result.trim();
|
|
16
17
|
} catch (error: any) {
|
|
@@ -206,6 +207,108 @@ end tell
|
|
|
206
207
|
return emails;
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Get specific emails by their IDs
|
|
212
|
+
*/
|
|
213
|
+
export function getEmailsByIds(options: {
|
|
214
|
+
ids: number[];
|
|
215
|
+
account?: string;
|
|
216
|
+
mailbox?: string;
|
|
217
|
+
includeContent?: boolean;
|
|
218
|
+
}): Email[] {
|
|
219
|
+
const { ids, account, mailbox = "INBOX", includeContent = true } = options;
|
|
220
|
+
|
|
221
|
+
if (!ids || ids.length === 0) {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const contentPart = includeContent
|
|
226
|
+
? `set msgContent to content of msg`
|
|
227
|
+
: `set msgContent to ""`;
|
|
228
|
+
|
|
229
|
+
const accountPart = account
|
|
230
|
+
? `mailbox "${mailbox}" of account "${account}"`
|
|
231
|
+
: `mailbox "${mailbox}"`;
|
|
232
|
+
|
|
233
|
+
// Build the ID list for AppleScript
|
|
234
|
+
const idList = ids.join(", ");
|
|
235
|
+
|
|
236
|
+
const script = `
|
|
237
|
+
tell application "Mail"
|
|
238
|
+
set results to ""
|
|
239
|
+
set targetIds to {${idList}}
|
|
240
|
+
try
|
|
241
|
+
set theMailbox to ${accountPart}
|
|
242
|
+
set allMessages to messages of theMailbox
|
|
243
|
+
|
|
244
|
+
repeat with targetId in targetIds
|
|
245
|
+
repeat with msg in allMessages
|
|
246
|
+
if id of msg is targetId then
|
|
247
|
+
set msgId to id of msg
|
|
248
|
+
set msgSubject to subject of msg
|
|
249
|
+
set msgSender to sender of msg
|
|
250
|
+
set msgDate to date sent of msg
|
|
251
|
+
set msgRead to read status of msg
|
|
252
|
+
${contentPart}
|
|
253
|
+
|
|
254
|
+
-- Get recipients
|
|
255
|
+
set toList to ""
|
|
256
|
+
repeat with r in to recipients of msg
|
|
257
|
+
set toList to toList & address of r & ","
|
|
258
|
+
end repeat
|
|
259
|
+
if toList is not "" then set toList to text 1 thru -2 of toList
|
|
260
|
+
|
|
261
|
+
set ccList to ""
|
|
262
|
+
repeat with r in cc recipients of msg
|
|
263
|
+
set ccList to ccList & address of r & ","
|
|
264
|
+
end repeat
|
|
265
|
+
if ccList is not "" then set ccList to text 1 thru -2 of ccList
|
|
266
|
+
|
|
267
|
+
set bccList to ""
|
|
268
|
+
repeat with r in bcc recipients of msg
|
|
269
|
+
set bccList to bccList & address of r & ","
|
|
270
|
+
end repeat
|
|
271
|
+
if bccList is not "" then set bccList to text 1 thru -2 of bccList
|
|
272
|
+
|
|
273
|
+
set results to results & msgId & "<<>>" & msgSubject & "<<>>" & msgSender & "<<>>" & toList & "<<>>" & ccList & "<<>>" & bccList & "<<>>" & (msgDate as string) & "<<>>" & msgRead & "<<>>" & msgContent & "|||"
|
|
274
|
+
exit repeat -- Found the message, move to next ID
|
|
275
|
+
end if
|
|
276
|
+
end repeat
|
|
277
|
+
end repeat
|
|
278
|
+
on error errMsg
|
|
279
|
+
return "ERROR:" & errMsg
|
|
280
|
+
end try
|
|
281
|
+
return results
|
|
282
|
+
end tell
|
|
283
|
+
`;
|
|
284
|
+
|
|
285
|
+
const result = runAppleScript(script);
|
|
286
|
+
|
|
287
|
+
if (result.startsWith("ERROR:")) {
|
|
288
|
+
throw new Error(result.substring(6));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const emails: Email[] = [];
|
|
292
|
+
const parts = result.split("|||").filter(Boolean);
|
|
293
|
+
|
|
294
|
+
for (const part of parts) {
|
|
295
|
+
const [id, subject, sender, to, cc, bcc, dateSent, isRead, content] = part.split("<<>>");
|
|
296
|
+
emails.push({
|
|
297
|
+
id: parseInt(id) || 0,
|
|
298
|
+
subject: subject || "(No Subject)",
|
|
299
|
+
sender: sender || "(Unknown)",
|
|
300
|
+
to: to ? to.split(",").map(s => s.trim()).filter(Boolean) : [],
|
|
301
|
+
cc: cc ? cc.split(",").map(s => s.trim()).filter(Boolean) : [],
|
|
302
|
+
bcc: bcc ? bcc.split(",").map(s => s.trim()).filter(Boolean) : [],
|
|
303
|
+
dateSent: dateSent || "",
|
|
304
|
+
isRead: isRead === "true",
|
|
305
|
+
content: content || undefined,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return emails;
|
|
310
|
+
}
|
|
311
|
+
|
|
209
312
|
/**
|
|
210
313
|
* Search emails by query
|
|
211
314
|
*/
|
|
@@ -655,6 +758,9 @@ export function createDraftReply(options: {
|
|
|
655
758
|
|
|
656
759
|
const replyCommand = replyAll ? "reply theMessage with opening window and reply to all" : "reply theMessage with opening window";
|
|
657
760
|
|
|
761
|
+
// Escape the body for AppleScript - handle quotes and newlines
|
|
762
|
+
const escapedBody = body.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '" & return & "');
|
|
763
|
+
|
|
658
764
|
const script = `
|
|
659
765
|
tell application "Mail"
|
|
660
766
|
try
|
|
@@ -663,9 +769,8 @@ tell application "Mail"
|
|
|
663
769
|
|
|
664
770
|
set replyMessage to ${replyCommand}
|
|
665
771
|
|
|
666
|
-
--
|
|
667
|
-
set
|
|
668
|
-
set content of replyMessage to "${body.replace(/"/g, '\\"').replace(/\n/g, "\\n")}" & return & return & currentContent
|
|
772
|
+
-- Set the reply body content (the quoted original will appear below in the compose window)
|
|
773
|
+
set content of replyMessage to "${escapedBody}"
|
|
669
774
|
|
|
670
775
|
return "Draft reply created successfully"
|
|
671
776
|
on error errMsg
|
|
@@ -675,7 +780,8 @@ end tell
|
|
|
675
780
|
`;
|
|
676
781
|
|
|
677
782
|
try {
|
|
678
|
-
|
|
783
|
+
// Use longer timeout for reply operations as they can be slow
|
|
784
|
+
const result = runAppleScript(script, 60000);
|
|
679
785
|
if (result.startsWith("ERROR:")) {
|
|
680
786
|
return { success: false, message: result.substring(6) };
|
|
681
787
|
}
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
listAccounts,
|
|
13
13
|
listMailboxes,
|
|
14
14
|
getEmails,
|
|
15
|
+
getEmailsByIds,
|
|
15
16
|
searchEmails,
|
|
16
17
|
getUnreadCount,
|
|
17
18
|
sendEmail,
|
|
@@ -90,6 +91,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
case "mail_get_emails_by_ids": {
|
|
95
|
+
const ids = args?.ids as number[];
|
|
96
|
+
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
|
97
|
+
throw new Error("Required field: ids (must be a non-empty array of email IDs)");
|
|
98
|
+
}
|
|
99
|
+
const emails = getEmailsByIds({
|
|
100
|
+
ids,
|
|
101
|
+
account: args?.account as string | undefined,
|
|
102
|
+
mailbox: (args?.mailbox as string) || "INBOX",
|
|
103
|
+
includeContent: (args?.includeContent as boolean) ?? true,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: JSON.stringify({ emails, count: emails.length, requestedIds: ids }, null, 2),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
93
115
|
case "mail_search": {
|
|
94
116
|
const query = args?.query as string;
|
|
95
117
|
if (!query) {
|
package/src/tools.ts
CHANGED
|
@@ -60,6 +60,38 @@ export const MAIL_GET_EMAILS: Tool = {
|
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
export const MAIL_GET_EMAILS_BY_IDS: Tool = {
|
|
64
|
+
name: "mail_get_emails_by_ids",
|
|
65
|
+
description: "Get specific emails by their IDs in Apple Mail. Use this to retrieve full details of specific emails after browsing with mail_get_emails (includeContent: false).",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: "object",
|
|
68
|
+
properties: {
|
|
69
|
+
ids: {
|
|
70
|
+
type: "array",
|
|
71
|
+
items: {
|
|
72
|
+
type: "number",
|
|
73
|
+
},
|
|
74
|
+
description: "Array of email IDs to retrieve (obtained from mail_get_emails or mail_search)",
|
|
75
|
+
},
|
|
76
|
+
account: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "The name of the email account (helps optimize search)",
|
|
79
|
+
},
|
|
80
|
+
mailbox: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "The name of the mailbox/folder where the emails are located (default: INBOX, helps optimize search)",
|
|
83
|
+
default: "INBOX",
|
|
84
|
+
},
|
|
85
|
+
includeContent: {
|
|
86
|
+
type: "boolean",
|
|
87
|
+
description: "Whether to include the email body content (default: true)",
|
|
88
|
+
default: true,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
required: ["ids"],
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
63
95
|
export const MAIL_SEARCH: Tool = {
|
|
64
96
|
name: "mail_search",
|
|
65
97
|
description: "Search emails in Apple Mail by subject, sender, or content",
|
|
@@ -332,6 +364,7 @@ export const tools: Tool[] = [
|
|
|
332
364
|
MAIL_LIST_ACCOUNTS,
|
|
333
365
|
MAIL_LIST_MAILBOXES,
|
|
334
366
|
MAIL_GET_EMAILS,
|
|
367
|
+
MAIL_GET_EMAILS_BY_IDS,
|
|
335
368
|
MAIL_SEARCH,
|
|
336
369
|
MAIL_GET_UNREAD_COUNT,
|
|
337
370
|
MAIL_SEND,
|