apple-mail-mcp 2.6.1 → 2.6.2
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 +17 -3
- package/build/index.js +20 -3
- package/build/utils/mimeParse.d.ts.map +1 -1
- package/build/utils/mimeParse.js +14 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -186,7 +186,7 @@ Search for messages matching criteria. Searches all accounts by default.
|
|
|
186
186
|
| `isFlagged` | boolean | No | Filter by flagged status |
|
|
187
187
|
| `dateFrom` | string | No | Start date filter (e.g., "January 1, 2026") |
|
|
188
188
|
| `dateTo` | string | No | End date filter (e.g., "March 1, 2026") |
|
|
189
|
-
| `limit` | number | No | Max results (default: 50) |
|
|
189
|
+
| `limit` | number | No | Max results, 1–500 (default: 50) |
|
|
190
190
|
|
|
191
191
|
**Large mailboxes & partial results.** Apple Mail's AppleScript bridge cannot
|
|
192
192
|
search very large IMAP/Gmail mailboxes (tens of thousands of messages) before
|
|
@@ -222,6 +222,13 @@ Get the full content of a message.
|
|
|
222
222
|
|
|
223
223
|
**Returns:** Subject line and message body (plain text by default, HTML if `preferHtml` is true and HTML content is available).
|
|
224
224
|
|
|
225
|
+
> **Large messages / attachments:** reading a full message routes through
|
|
226
|
+
> `osascript`, whose captured output buffer defaults to **64 MB**. Override it
|
|
227
|
+
> with the `APPLE_MAIL_MCP_MAX_BUFFER` environment variable (in **bytes**) if you
|
|
228
|
+
> work with messages whose raw MIME (e.g. a large embedded attachment) exceeds
|
|
229
|
+
> that — a value below the message size makes the read fail with a buffer-overflow
|
|
230
|
+
> error rather than truncating ([#27](https://github.com/sweetrb/apple-mail-mcp/issues/27)).
|
|
231
|
+
|
|
225
232
|
---
|
|
226
233
|
|
|
227
234
|
#### `list-messages`
|
|
@@ -232,8 +239,8 @@ List messages in a mailbox.
|
|
|
232
239
|
|-----------|------|----------|-------------|
|
|
233
240
|
| `mailbox` | string | No | Mailbox name (omit to list from all mailboxes) |
|
|
234
241
|
| `account` | string | No | Account name |
|
|
235
|
-
| `limit` | number | No | Max messages (default: 50) |
|
|
236
|
-
| `offset` | number | No | Number of messages to skip (for pagination) |
|
|
242
|
+
| `limit` | number | No | Max messages, 1–500 (default: 50) |
|
|
243
|
+
| `offset` | number | No | Number of messages to skip, ≥ 0 (for pagination) |
|
|
237
244
|
| `from` | string | No | Filter by sender email address or name |
|
|
238
245
|
| `unreadOnly` | boolean | No | Only show unread messages |
|
|
239
246
|
|
|
@@ -1056,6 +1063,13 @@ AI: [calls list-messages with account="Work Exchange", mailbox="INBOX"]
|
|
|
1056
1063
|
"Your Work account has 5 unread messages..."
|
|
1057
1064
|
```
|
|
1058
1065
|
|
|
1066
|
+
To pin which account is used when a tool call omits `account`, set the
|
|
1067
|
+
`APPLE_MAIL_MCP_DEFAULT_ACCOUNT` environment variable to an account **name or
|
|
1068
|
+
email**. When unset (the default), the server falls back to Mail.app's
|
|
1069
|
+
default-send account if it is enabled, otherwise the first enabled account. A
|
|
1070
|
+
**disabled** account is never selected implicitly — this env var (an explicit,
|
|
1071
|
+
deliberate pin) is one of the few ways to target one ([#47](https://github.com/sweetrb/apple-mail-mcp/issues/47)).
|
|
1072
|
+
|
|
1059
1073
|
### Sending Emails Safely
|
|
1060
1074
|
|
|
1061
1075
|
```
|
package/build/index.js
CHANGED
|
@@ -265,7 +265,13 @@ server.registerTool("search-messages", {
|
|
|
265
265
|
isFlagged: z.boolean().optional().describe("Filter by flagged status"),
|
|
266
266
|
dateFrom: DATE_FILTER_SCHEMA.describe("Start date filter (e.g., 'January 1, 2026')"),
|
|
267
267
|
dateTo: DATE_FILTER_SCHEMA.describe("End date filter (e.g., 'March 1, 2026')"),
|
|
268
|
-
limit: z
|
|
268
|
+
limit: z
|
|
269
|
+
.number()
|
|
270
|
+
.int()
|
|
271
|
+
.min(1)
|
|
272
|
+
.max(500)
|
|
273
|
+
.optional()
|
|
274
|
+
.describe("Maximum number of results (default: 50, max: 500)"),
|
|
269
275
|
},
|
|
270
276
|
outputSchema: LIST_OUTPUT_SCHEMA,
|
|
271
277
|
}, withErrorHandling(async ({ query, mailbox, account, limit = 50, dateFrom, dateTo, from, subject, isRead, isFlagged, }) => {
|
|
@@ -485,8 +491,19 @@ server.registerTool("list-messages", {
|
|
|
485
491
|
.optional()
|
|
486
492
|
.describe("Mailbox to list messages from. Omit to list from all mailboxes."),
|
|
487
493
|
account: z.string().optional().describe("Account to list messages from"),
|
|
488
|
-
limit: z
|
|
489
|
-
|
|
494
|
+
limit: z
|
|
495
|
+
.number()
|
|
496
|
+
.int()
|
|
497
|
+
.min(1)
|
|
498
|
+
.max(500)
|
|
499
|
+
.optional()
|
|
500
|
+
.describe("Maximum number of messages (default: 50, max: 500)"),
|
|
501
|
+
offset: z
|
|
502
|
+
.number()
|
|
503
|
+
.int()
|
|
504
|
+
.min(0)
|
|
505
|
+
.optional()
|
|
506
|
+
.describe("Number of messages to skip (for pagination)"),
|
|
490
507
|
from: z.string().optional().describe("Filter by sender email address or name"),
|
|
491
508
|
unreadOnly: z.boolean().optional().describe("Only show unread messages"),
|
|
492
509
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mimeParse.d.ts","sourceRoot":"","sources":["../../src/utils/mimeParse.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;
|
|
1
|
+
{"version":3,"file":"mimeParse.d.ts","sourceRoot":"","sources":["../../src/utils/mimeParse.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAqMD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAyBzE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB7D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,kBAAkB,GAAG,IAAI,CAwB3B"}
|
package/build/utils/mimeParse.js
CHANGED
|
@@ -107,19 +107,30 @@ function splitMimeParts(source, boundary) {
|
|
|
107
107
|
}
|
|
108
108
|
return parts;
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Maximum nesting depth for multipart MIME traversal. Bounds recursion so a
|
|
112
|
+
* pathologically (or maliciously) deeply nested multipart tree can't blow the
|
|
113
|
+
* stack — past this depth we stop descending and treat the container as a leaf.
|
|
114
|
+
* Real mail nests only a few levels (e.g. mixed → related → alternative).
|
|
115
|
+
*/
|
|
116
|
+
const MAX_MIME_DEPTH = 20;
|
|
110
117
|
/**
|
|
111
118
|
* Walk a multipart MIME block and return all non-multipart leaf parts,
|
|
112
119
|
* descending into nested multipart/* containers (alternative, related, mixed).
|
|
120
|
+
*
|
|
121
|
+
* Descent is bounded by MAX_MIME_DEPTH; once reached, deeper multipart
|
|
122
|
+
* containers are returned as-is (as leaf parts) rather than recursed into,
|
|
123
|
+
* yielding what's parsed so far instead of overflowing the stack.
|
|
113
124
|
*/
|
|
114
|
-
function walkLeafParts(source, boundary) {
|
|
125
|
+
function walkLeafParts(source, boundary, depth = 0) {
|
|
115
126
|
const result = [];
|
|
116
127
|
const parts = splitMimeParts(source, boundary);
|
|
117
128
|
for (const part of parts) {
|
|
118
129
|
const ct = getHeader(part.headers, "Content-Type");
|
|
119
|
-
if (ct && /^multipart\//i.test(ct)) {
|
|
130
|
+
if (ct && /^multipart\//i.test(ct) && depth < MAX_MIME_DEPTH) {
|
|
120
131
|
const nestedBoundary = extractBoundary(ct);
|
|
121
132
|
if (nestedBoundary) {
|
|
122
|
-
result.push(...walkLeafParts(part.body, nestedBoundary));
|
|
133
|
+
result.push(...walkLeafParts(part.body, nestedBoundary, depth + 1));
|
|
123
134
|
continue;
|
|
124
135
|
}
|
|
125
136
|
}
|
package/package.json
CHANGED