apple-mail-mcp 2.10.24 → 2.10.26
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 +5 -0
- package/build/index.js +112 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -824,6 +824,11 @@ List attachments on a message.
|
|
|
824
824
|
|
|
825
825
|
Save a message attachment to disk.
|
|
826
826
|
|
|
827
|
+
The destination must not already exist: `save-attachment` fails closed instead
|
|
828
|
+
of overwriting an existing file. AppleScript and MIME fallback paths stage the
|
|
829
|
+
bytes privately, commit with an exclusive create, and leave the saved file
|
|
830
|
+
owner-readable/writable (`0600`).
|
|
831
|
+
|
|
827
832
|
| Parameter | Type | Required | Description |
|
|
828
833
|
|-----------|------|----------|-------------|
|
|
829
834
|
| `id` | string | Yes | Message ID |
|
package/build/index.js
CHANGED
|
@@ -77516,6 +77516,8 @@ var StdioServerTransport = class {
|
|
|
77516
77516
|
// src/services/appleMailManager.ts
|
|
77517
77517
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
77518
77518
|
import {
|
|
77519
|
+
constants as fsConstants,
|
|
77520
|
+
chmodSync,
|
|
77519
77521
|
existsSync as existsSync3,
|
|
77520
77522
|
writeFileSync as writeFileSync3,
|
|
77521
77523
|
readFileSync as readFileSync2,
|
|
@@ -78300,6 +78302,7 @@ var DIAG_ITEM_SEP = "M";
|
|
|
78300
78302
|
var CONTENT_MARKER = "CONTENT";
|
|
78301
78303
|
var MSGID_MARKER = "MSGID";
|
|
78302
78304
|
var HTML_MARKER = "HTML";
|
|
78305
|
+
var LOOKUP_ERROR_MARKER = "ERR";
|
|
78303
78306
|
var BATCH_FATAL = "FATAL";
|
|
78304
78307
|
var RECON_TAG = "RECON";
|
|
78305
78308
|
var SNAP_TAG = "SNAP";
|
|
@@ -78374,8 +78377,11 @@ function resolveAttachmentSaveTarget(savePath, attachmentName) {
|
|
|
78374
78377
|
if (!isPathWithinAllowedRoots(savedPath)) {
|
|
78375
78378
|
throw new Error(`Output path "${savedPath}" is outside allowed directories`);
|
|
78376
78379
|
}
|
|
78377
|
-
if (existsSync3(savedPath)
|
|
78378
|
-
|
|
78380
|
+
if (existsSync3(savedPath)) {
|
|
78381
|
+
if (lstatSync(savedPath).isSymbolicLink()) {
|
|
78382
|
+
throw new Error(`Refusing to overwrite symbolic link "${savedPath}"`);
|
|
78383
|
+
}
|
|
78384
|
+
throw new Error(`Refusing to overwrite existing file "${savedPath}"`);
|
|
78379
78385
|
}
|
|
78380
78386
|
return { saveDirectory, savedPath };
|
|
78381
78387
|
}
|
|
@@ -78653,6 +78659,8 @@ var AppleMailManager = class {
|
|
|
78653
78659
|
* misses and falls back to the full scan, so it can never wedge a lookup.
|
|
78654
78660
|
*/
|
|
78655
78661
|
idLocationIndex = /* @__PURE__ */ new Map();
|
|
78662
|
+
/** Error from the most recent numeric message read, if it was refused. */
|
|
78663
|
+
lastMessageLookupError;
|
|
78656
78664
|
/** Cap on the id→location index so a long-lived process can't grow unbounded. */
|
|
78657
78665
|
ID_LOCATION_MAX = 5e3;
|
|
78658
78666
|
/** Record (or refresh) where a message id lives, evicting oldest when full. */
|
|
@@ -78681,6 +78689,12 @@ var AppleMailManager = class {
|
|
|
78681
78689
|
noteMessageLocation(id, account, mailbox) {
|
|
78682
78690
|
this.rememberLocation(id, account, mailbox);
|
|
78683
78691
|
}
|
|
78692
|
+
/** Consume the most recent read refusal so the tool layer can preserve it. */
|
|
78693
|
+
consumeLastMessageLookupError() {
|
|
78694
|
+
const error2 = this.lastMessageLookupError;
|
|
78695
|
+
this.lastMessageLookupError = void 0;
|
|
78696
|
+
return error2;
|
|
78697
|
+
}
|
|
78684
78698
|
/**
|
|
78685
78699
|
* AppleScript fragment resolving `account` + `mailbox` into `_tmb`, leaving
|
|
78686
78700
|
* `_tmb` as `missing value` when it can't be pinned down. Exact-name match
|
|
@@ -79699,6 +79713,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79699
79713
|
* returned the entire raw MIME blob mislabeled as HTML (#32).
|
|
79700
79714
|
*/
|
|
79701
79715
|
getMessageContent(id, includeHtml = false, hint) {
|
|
79716
|
+
this.lastMessageLookupError = void 0;
|
|
79702
79717
|
const sourceFetch = includeHtml ? `set htmlSource to ""
|
|
79703
79718
|
try
|
|
79704
79719
|
set htmlSource to source of msg
|
|
@@ -79724,17 +79739,25 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79724
79739
|
}
|
|
79725
79740
|
const script = buildAppLevelScript(`
|
|
79726
79741
|
try
|
|
79742
|
+
set _hits to {}
|
|
79743
|
+
set _names to ""
|
|
79727
79744
|
repeat with acct in accounts
|
|
79728
79745
|
repeat with mb in mailboxes of acct
|
|
79729
79746
|
try
|
|
79730
79747
|
set matchingMsgs to (messages of mb whose id is ${Number(id)})
|
|
79731
79748
|
if (count of matchingMsgs) > 0 then
|
|
79732
|
-
set
|
|
79733
|
-
|
|
79749
|
+
set end of _hits to item 1 of matchingMsgs
|
|
79750
|
+
set _names to _names & (name of acct) & "/" & (name of mb) & ", "
|
|
79734
79751
|
end if
|
|
79735
79752
|
end try
|
|
79736
79753
|
end repeat
|
|
79737
79754
|
end repeat
|
|
79755
|
+
if (count of _hits) is 0 then return "${LOOKUP_ERROR_MARKER}Message not found"
|
|
79756
|
+
if (count of _hits) > 1 then return "${LOOKUP_ERROR_MARKER}${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the read targets the right copy"
|
|
79757
|
+
if (count of _hits) is 1 then
|
|
79758
|
+
set msg to item 1 of _hits
|
|
79759
|
+
${innerFetch}
|
|
79760
|
+
end if
|
|
79738
79761
|
return ""
|
|
79739
79762
|
on error errMsg
|
|
79740
79763
|
return ""
|
|
@@ -79756,6 +79779,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79756
79779
|
if (!result.success) console.error(`Failed to get message content: ${result.error}`);
|
|
79757
79780
|
return null;
|
|
79758
79781
|
}
|
|
79782
|
+
if (result.output.startsWith(LOOKUP_ERROR_MARKER)) {
|
|
79783
|
+
this.lastMessageLookupError = result.output.slice(LOOKUP_ERROR_MARKER.length).trim();
|
|
79784
|
+
return null;
|
|
79785
|
+
}
|
|
79759
79786
|
const htmlSplit = result.output.split(HTML_MARKER);
|
|
79760
79787
|
const contentPart = htmlSplit[0];
|
|
79761
79788
|
const rawSource = htmlSplit.length > 1 ? htmlSplit[1] : "";
|
|
@@ -79783,6 +79810,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79783
79810
|
* a 20MB attachment can take several seconds over Exchange/IMAP.
|
|
79784
79811
|
*/
|
|
79785
79812
|
getRawSource(id, hint) {
|
|
79813
|
+
this.lastMessageLookupError = void 0;
|
|
79786
79814
|
const loc = hint?.account && hint?.mailbox ? { account: hint.account, mailbox: hint.mailbox } : this.idLocationIndex.get(id.toString());
|
|
79787
79815
|
if (loc) {
|
|
79788
79816
|
const scopedScript = this.scopedByIdScript(
|
|
@@ -79792,21 +79820,34 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79792
79820
|
"return source of msg"
|
|
79793
79821
|
);
|
|
79794
79822
|
const scoped = executeAppleScript(scopedScript, { timeoutMs: 12e4 });
|
|
79795
|
-
if (scoped.success && scoped.output.trim()
|
|
79823
|
+
if (scoped.success && scoped.output.trim() && !scoped.output.startsWith(LOOKUP_ERROR_MARKER)) {
|
|
79824
|
+
return scoped.output;
|
|
79825
|
+
}
|
|
79826
|
+
if (scoped.success && scoped.output.startsWith(LOOKUP_ERROR_MARKER)) {
|
|
79827
|
+
this.lastMessageLookupError = scoped.output.slice(LOOKUP_ERROR_MARKER.length).trim();
|
|
79828
|
+
}
|
|
79796
79829
|
}
|
|
79797
79830
|
const script = buildAppLevelScript(`
|
|
79798
79831
|
try
|
|
79832
|
+
set _hits to {}
|
|
79833
|
+
set _names to ""
|
|
79799
79834
|
repeat with acct in accounts
|
|
79800
79835
|
repeat with mb in mailboxes of acct
|
|
79801
79836
|
try
|
|
79802
79837
|
set matchingMsgs to (messages of mb whose id is ${Number(id)})
|
|
79803
79838
|
if (count of matchingMsgs) > 0 then
|
|
79804
|
-
set
|
|
79805
|
-
|
|
79839
|
+
set end of _hits to item 1 of matchingMsgs
|
|
79840
|
+
set _names to _names & (name of acct) & "/" & (name of mb) & ", "
|
|
79806
79841
|
end if
|
|
79807
79842
|
end try
|
|
79808
79843
|
end repeat
|
|
79809
79844
|
end repeat
|
|
79845
|
+
if (count of _hits) is 0 then return "${LOOKUP_ERROR_MARKER}Message not found"
|
|
79846
|
+
if (count of _hits) > 1 then return "${LOOKUP_ERROR_MARKER}${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the read targets the right copy"
|
|
79847
|
+
if (count of _hits) is 1 then
|
|
79848
|
+
set msg to item 1 of _hits
|
|
79849
|
+
return source of msg
|
|
79850
|
+
end if
|
|
79810
79851
|
return ""
|
|
79811
79852
|
on error errMsg
|
|
79812
79853
|
return ""
|
|
@@ -79816,6 +79857,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79816
79857
|
if (!result.success || !result.output.trim()) {
|
|
79817
79858
|
return null;
|
|
79818
79859
|
}
|
|
79860
|
+
if (result.output.startsWith(LOOKUP_ERROR_MARKER)) {
|
|
79861
|
+
this.lastMessageLookupError = result.output.slice(LOOKUP_ERROR_MARKER.length).trim();
|
|
79862
|
+
return null;
|
|
79863
|
+
}
|
|
79819
79864
|
return result.output;
|
|
79820
79865
|
}
|
|
79821
79866
|
/**
|
|
@@ -80256,27 +80301,13 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80256
80301
|
const safeBody = escapeForAppleScriptBody(body);
|
|
80257
80302
|
const replyAllClause = replyAll ? " with reply to all" : "";
|
|
80258
80303
|
const sendAction = send ? "send theReply" : "";
|
|
80259
|
-
const script =
|
|
80260
|
-
|
|
80261
|
-
|
|
80262
|
-
|
|
80263
|
-
|
|
80264
|
-
|
|
80265
|
-
|
|
80266
|
-
set msg to item 1 of matchingMsgs
|
|
80267
|
-
set theReply to reply msg without opening window${replyAllClause}
|
|
80268
|
-
set content of theReply to "${safeBody}"
|
|
80269
|
-
${sendAction}
|
|
80270
|
-
return "ok"
|
|
80271
|
-
end if
|
|
80272
|
-
end try
|
|
80273
|
-
end repeat
|
|
80274
|
-
end repeat
|
|
80275
|
-
return "error:Message not found"
|
|
80276
|
-
on error errMsg
|
|
80277
|
-
return "error:" & errMsg
|
|
80278
|
-
end try
|
|
80279
|
-
`);
|
|
80304
|
+
const script = this.findMessageScript(
|
|
80305
|
+
id,
|
|
80306
|
+
`
|
|
80307
|
+
set theReply to reply msg without opening window${replyAllClause}
|
|
80308
|
+
set content of theReply to "${safeBody}"
|
|
80309
|
+
${sendAction}`
|
|
80310
|
+
);
|
|
80280
80311
|
const result = executeAppleScript(script, { timeoutMs: 6e4 });
|
|
80281
80312
|
if (!result.success || result.output.startsWith("error:")) {
|
|
80282
80313
|
console.error(`Failed to reply to message: ${result.error || result.output}`);
|
|
@@ -80301,28 +80332,14 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80301
80332
|
recipientCommands += `make new to recipient at end of to recipients of theForward with properties {address:"${escapeForAppleScript(addr)}"}
|
|
80302
80333
|
`;
|
|
80303
80334
|
}
|
|
80304
|
-
const script =
|
|
80305
|
-
|
|
80306
|
-
|
|
80307
|
-
|
|
80308
|
-
|
|
80309
|
-
|
|
80310
|
-
|
|
80311
|
-
|
|
80312
|
-
set theForward to forward msg without opening window
|
|
80313
|
-
${recipientCommands}
|
|
80314
|
-
${safeBody ? `set content of theForward to "${safeBody}"` : ""}
|
|
80315
|
-
${sendAction}
|
|
80316
|
-
return "ok"
|
|
80317
|
-
end if
|
|
80318
|
-
end try
|
|
80319
|
-
end repeat
|
|
80320
|
-
end repeat
|
|
80321
|
-
return "error:Message not found"
|
|
80322
|
-
on error errMsg
|
|
80323
|
-
return "error:" & errMsg
|
|
80324
|
-
end try
|
|
80325
|
-
`);
|
|
80335
|
+
const script = this.findMessageScript(
|
|
80336
|
+
id,
|
|
80337
|
+
`
|
|
80338
|
+
set theForward to forward msg without opening window
|
|
80339
|
+
${recipientCommands}
|
|
80340
|
+
${safeBody ? `set content of theForward to "${safeBody}"` : ""}
|
|
80341
|
+
${sendAction}`
|
|
80342
|
+
);
|
|
80326
80343
|
const result = executeAppleScript(script, { timeoutMs: 6e4 });
|
|
80327
80344
|
if (!result.success || result.output.startsWith("error:")) {
|
|
80328
80345
|
console.error(`Failed to forward message: ${result.error || result.output}`);
|
|
@@ -81150,7 +81167,21 @@ ${this.errorEmit(" ")}
|
|
|
81150
81167
|
return false;
|
|
81151
81168
|
}
|
|
81152
81169
|
const safeName = escapeForAppleScript(attachmentName);
|
|
81153
|
-
|
|
81170
|
+
let temporaryDirectory;
|
|
81171
|
+
try {
|
|
81172
|
+
temporaryDirectory = mkdtempSync2(join4(target.saveDirectory, ".apple-mail-mcp-"));
|
|
81173
|
+
} catch (error2) {
|
|
81174
|
+
console.error(`Failed to create attachment staging directory: ${error2}`);
|
|
81175
|
+
return false;
|
|
81176
|
+
}
|
|
81177
|
+
const temporaryPath = join4(temporaryDirectory, "attachment");
|
|
81178
|
+
const safeTemporaryPath = escapeForAppleScript(temporaryPath);
|
|
81179
|
+
const cleanupTemporaryDirectory = () => {
|
|
81180
|
+
try {
|
|
81181
|
+
rmSync2(temporaryDirectory, { recursive: true, force: true });
|
|
81182
|
+
} catch {
|
|
81183
|
+
}
|
|
81184
|
+
};
|
|
81154
81185
|
const numericId = Number(id);
|
|
81155
81186
|
const script = buildAppLevelScript(`
|
|
81156
81187
|
try
|
|
@@ -81162,7 +81193,7 @@ ${this.errorEmit(" ")}
|
|
|
81162
81193
|
set msg to item 1 of matchingMsgs
|
|
81163
81194
|
repeat with att in mail attachments of msg
|
|
81164
81195
|
if name of att is "${safeName}" then
|
|
81165
|
-
set savePath to POSIX file "${
|
|
81196
|
+
set savePath to POSIX file "${safeTemporaryPath}"
|
|
81166
81197
|
save att in savePath
|
|
81167
81198
|
return "ok"
|
|
81168
81199
|
end if
|
|
@@ -81179,8 +81210,18 @@ ${this.errorEmit(" ")}
|
|
|
81179
81210
|
`);
|
|
81180
81211
|
const result = executeAppleScript(script, { timeoutMs: 6e4 });
|
|
81181
81212
|
if (result.success && result.output === "ok") {
|
|
81182
|
-
|
|
81213
|
+
try {
|
|
81214
|
+
copyFileSync(temporaryPath, target.savedPath, fsConstants.COPYFILE_EXCL);
|
|
81215
|
+
chmodSync(target.savedPath, 384);
|
|
81216
|
+
cleanupTemporaryDirectory();
|
|
81217
|
+
return true;
|
|
81218
|
+
} catch (err) {
|
|
81219
|
+
cleanupTemporaryDirectory();
|
|
81220
|
+
console.error(`Failed to commit attachment to disk: ${err}`);
|
|
81221
|
+
return false;
|
|
81222
|
+
}
|
|
81183
81223
|
}
|
|
81224
|
+
cleanupTemporaryDirectory();
|
|
81184
81225
|
const rawSource = this.getRawSource(id);
|
|
81185
81226
|
if (!rawSource) {
|
|
81186
81227
|
console.error(`Failed to save attachment: could not retrieve message source`);
|
|
@@ -81191,12 +81232,24 @@ ${this.errorEmit(" ")}
|
|
|
81191
81232
|
console.error(`Failed to save attachment: "${attachmentName}" not found in MIME source`);
|
|
81192
81233
|
return false;
|
|
81193
81234
|
}
|
|
81235
|
+
let mimeTemporaryDirectory;
|
|
81194
81236
|
try {
|
|
81195
|
-
|
|
81237
|
+
mimeTemporaryDirectory = mkdtempSync2(join4(target.saveDirectory, ".apple-mail-mcp-"));
|
|
81238
|
+
const mimeTemporaryPath = join4(mimeTemporaryDirectory, "attachment");
|
|
81239
|
+
writeFileSync3(mimeTemporaryPath, attachment.data, { flag: "wx", mode: 384 });
|
|
81240
|
+
copyFileSync(mimeTemporaryPath, target.savedPath, fsConstants.COPYFILE_EXCL);
|
|
81241
|
+
chmodSync(target.savedPath, 384);
|
|
81196
81242
|
return true;
|
|
81197
81243
|
} catch (err) {
|
|
81198
81244
|
console.error(`Failed to write attachment to disk: ${err}`);
|
|
81199
81245
|
return false;
|
|
81246
|
+
} finally {
|
|
81247
|
+
if (mimeTemporaryDirectory) {
|
|
81248
|
+
try {
|
|
81249
|
+
rmSync2(mimeTemporaryDirectory, { recursive: true, force: true });
|
|
81250
|
+
} catch {
|
|
81251
|
+
}
|
|
81252
|
+
}
|
|
81200
81253
|
}
|
|
81201
81254
|
}
|
|
81202
81255
|
/**
|
|
@@ -84735,7 +84788,10 @@ Do not use when: you don't yet have an id (use search-messages or list-messages
|
|
|
84735
84788
|
account,
|
|
84736
84789
|
mailbox
|
|
84737
84790
|
});
|
|
84738
|
-
if (!content)
|
|
84791
|
+
if (!content) {
|
|
84792
|
+
const lookupError = mailManager.consumeLastMessageLookupError();
|
|
84793
|
+
return errorResponse(lookupError ?? `Message with ID "${id}" not found`);
|
|
84794
|
+
}
|
|
84739
84795
|
const isHtml = preferHtml === true && !!content.htmlContent;
|
|
84740
84796
|
const body = isHtml ? content.htmlContent : content.plainText;
|
|
84741
84797
|
return successResponse(`Subject: ${content.subject}
|
|
@@ -85661,7 +85717,7 @@ registerTool(
|
|
|
85661
85717
|
if (!r.success || !r.base64) {
|
|
85662
85718
|
return errorResponse(r.error || `Failed to fetch attachment "${attachmentName}"`);
|
|
85663
85719
|
}
|
|
85664
|
-
writeFileSync4(target.savedPath, Buffer.from(r.base64, "base64"));
|
|
85720
|
+
writeFileSync4(target.savedPath, Buffer.from(r.base64, "base64"), { flag: "wx", mode: 384 });
|
|
85665
85721
|
return successResponse(`Attachment "${attachmentName}" saved to ${savePath}`, {
|
|
85666
85722
|
ok: true,
|
|
85667
85723
|
attachmentName,
|
package/package.json
CHANGED