apple-tools-mcp 2.0.3 → 2.0.5
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 +7 -1
- package/lib/appleScript.js +11 -0
- package/lib/mailWrite.js +146 -9
- package/lib/writeTools.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -461,7 +461,13 @@ Two arguments are available on **every** write tool:
|
|
|
461
461
|
| `mail_archive` | `message_id` or `file_path` | none |
|
|
462
462
|
| `mail_trash` | `message_id` or `file_path` | **`confirm` required** |
|
|
463
463
|
|
|
464
|
-
`body_format: "html"`
|
|
464
|
+
`body_format: "html"` uses the same compose path with a tag-stripped body pasted into Mail's native editor. It does **not** set Mail's `content` or `html content` — those setters cite-wrap the whole message (`>` prefixes and `<blockquote type="cite">`, same iOS purple bar). Mail may still generate its own HTML alternative from the native compose. The default is plain text.
|
|
465
|
+
|
|
466
|
+
**Compose is not quoted.** `mail_send` / `mail_draft` (plain and html) call `make new outgoing message` **without** AppleScript `content:` so `newMessage` is a real Mail object, then paste the body into the compose window (System Events). Mail's `mailto` command was shipped in 2.0.4 and **fails on Mini/MacBook Mail**: it does not return an outgoing message (`newMessage` undefined, AppleScript **-2753**). On current Mail (Ventura+, FB11734014) `content` / `html content` store the body as a citation: every plain-text line prefixed with `>`, plus a `multipart/alternative` HTML part wrapped in `<blockquote type="cite">`. Desktop Mail often hides the bar with inline styles; iOS Mail paints the whole body purple with a left quote bar — even when the subject is not `Re:`/`Fwd:` and there is no `In-Reply-To`. Reply and forward still quote the original, which is expected.
|
|
467
|
+
|
|
468
|
+
Paste uses System Events, so **node needs Accessibility** (Privacy & Security → Accessibility) in addition to Automation → Mail. That Accessibility deny is not a Mail Automation deny (`-1743` / `-10004`).
|
|
469
|
+
|
|
470
|
+
**Manual prove (2.0.5 on the Mac host):** `mail_send` a short plain message and a short `body_format: "html"` message whose body looks like ordinary paragraphs (for example `<p>Quick note</p>`), neither with a `Re:`/`Fwd:` subject. Inspect each Sent `.emlx`: the text/plain part must not prefix every body line with `>`, and any HTML alternative must not wrap the whole body in `<blockquote type="cite">`. Compose must succeed (no `-2753` / undefined `newMessage`).
|
|
465
471
|
|
|
466
472
|
Emails are addressed by their RFC822 **Message-ID**. Pass `message_id`, or pass the `file_path` from `mail_search` / `mail_recent` and the server reads the Message-ID out of the `.emlx` headers for you. `mail_archive` moves the message to its account's Archive (or All Mail) mailbox; `mail_trash` moves it to that account's Trash.
|
|
467
473
|
|
package/lib/appleScript.js
CHANGED
|
@@ -179,6 +179,7 @@ export function needsHostTccAdvice(message) {
|
|
|
179
179
|
text.includes(CALENDAR_TCC_GUIDANCE) ||
|
|
180
180
|
text.includes(MESSAGES_TCC_GUIDANCE) ||
|
|
181
181
|
text.includes(MAIL_TCC_GUIDANCE) ||
|
|
182
|
+
text.includes(MAIL_ACCESSIBILITY_GUIDANCE) ||
|
|
182
183
|
text.includes(ATTRIBUTION_GUIDANCE) ||
|
|
183
184
|
text.includes(TCC_GUIDANCE)
|
|
184
185
|
) {
|
|
@@ -332,6 +333,16 @@ export const MAIL_TCC_GUIDANCE =
|
|
|
332
333
|
"Allow node in System Settings → Privacy & Security → Automation for Mail (same Allow-via-prompt as Contacts and Calendar — do not add node via + in a privacy list). " +
|
|
333
334
|
"A Contacts or Calendar grant does not include Mail.";
|
|
334
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Body paste for mail_send / mail_draft uses System Events. That is
|
|
338
|
+
* Accessibility, not Mail Automation (-1743).
|
|
339
|
+
*/
|
|
340
|
+
export const MAIL_ACCESSIBILITY_GUIDANCE =
|
|
341
|
+
"macOS denied Accessibility (System Events could not drive Mail's compose window). " +
|
|
342
|
+
"mail_send / mail_draft paste the body so Mail does not cite-wrap it (FB11734014); Mail's mailto command does not return an outgoing message on current Mini/MacBook Mail (-2753). " +
|
|
343
|
+
"Allow node in System Settings → Privacy & Security → Accessibility, and keep Automation → Mail allowed. " +
|
|
344
|
+
"This is not a Mail Automation deny (-1743 / -10004).";
|
|
345
|
+
|
|
335
346
|
/**
|
|
336
347
|
* AppleScript hung (ETIMEDOUT / -1712) while finding, opening, replying,
|
|
337
348
|
* or sending. That is not -1743 / -10004, including before send.
|
package/lib/mailWrite.js
CHANGED
|
@@ -19,8 +19,10 @@ import { validateEmailPath, unfoldRfc822Headers, safeMatch, stripHtmlTags } from
|
|
|
19
19
|
import {
|
|
20
20
|
runAppleScript,
|
|
21
21
|
asString,
|
|
22
|
+
asInteger,
|
|
22
23
|
MAIL_TCC_GUIDANCE,
|
|
23
24
|
MAIL_SEND_TIMEOUT_GUIDANCE,
|
|
25
|
+
MAIL_ACCESSIBILITY_GUIDANCE,
|
|
24
26
|
MAIL_APP_NOT_RUNNING_GUIDANCE,
|
|
25
27
|
ATTRIBUTION_GUIDANCE,
|
|
26
28
|
isHardTccDenial
|
|
@@ -148,11 +150,118 @@ export function probeMailAutomation() {
|
|
|
148
150
|
};
|
|
149
151
|
}
|
|
150
152
|
|
|
153
|
+
export const ACCESSIBILITY_DENIED_SENTINEL = "ACCESSIBILITY_DENIED";
|
|
154
|
+
export const BODY_PASTE_MISDIRECTED_SENTINEL = "BODY_PASTE_MISDIRECTED";
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Body text for native Mail compose (clipboard paste, not AppleScript content).
|
|
158
|
+
*
|
|
159
|
+
* HTML is tag-stripped so a plain-looking HTML body (`<p>…</p>`) becomes
|
|
160
|
+
* typed/pasted text. Never prefixes lines with `>` and never emits
|
|
161
|
+
* `<blockquote>` — AppleScript `content` / `html content` cite-wrap
|
|
162
|
+
* (FB11734014). Mail's `mailto` command is not used: on current Mini/MacBook
|
|
163
|
+
* Mail it does not return an outgoing message (`newMessage` stays undefined,
|
|
164
|
+
* AppleScript -2753).
|
|
165
|
+
*/
|
|
166
|
+
export function composeNativeBody(body, { html = false } = {}) {
|
|
167
|
+
if (html) return stripHtmlTags(body);
|
|
168
|
+
return body === undefined || body === null ? "" : String(body);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* AppleScript handlers: focus the compose body (not To) and paste.
|
|
173
|
+
* System Events needs Accessibility; Mail make/send still needs Automation.
|
|
174
|
+
*/
|
|
175
|
+
export function buildMailBodyPasteHandler() {
|
|
176
|
+
return `on atmFocusMailBody()
|
|
177
|
+
tell application "System Events"
|
|
178
|
+
tell process "Mail"
|
|
179
|
+
set frontmost to true
|
|
180
|
+
if (count of windows) < 1 then error "MAIL_COMPOSE_WINDOW_MISSING"
|
|
181
|
+
set w to first window
|
|
182
|
+
try
|
|
183
|
+
set focused of text area 1 of scroll area 1 of w to true
|
|
184
|
+
return
|
|
185
|
+
end try
|
|
186
|
+
try
|
|
187
|
+
click (first web area of w)
|
|
188
|
+
return
|
|
189
|
+
end try
|
|
190
|
+
try
|
|
191
|
+
click (first web area of scroll area 1 of w)
|
|
192
|
+
return
|
|
193
|
+
end try
|
|
194
|
+
try
|
|
195
|
+
click (first web area of scroll area 1 of splitter group 1 of w)
|
|
196
|
+
return
|
|
197
|
+
end try
|
|
198
|
+
try
|
|
199
|
+
click (first web area of scroll area 1 of group 1 of splitter group 1 of w)
|
|
200
|
+
return
|
|
201
|
+
end try
|
|
202
|
+
set {wx, wy} to position of w
|
|
203
|
+
set {ww, wh} to size of w
|
|
204
|
+
click at {(wx + (ww div 2)) as integer, (wy + ((wh * 2) div 3)) as integer}
|
|
205
|
+
end tell
|
|
206
|
+
end tell
|
|
207
|
+
end atmFocusMailBody
|
|
208
|
+
|
|
209
|
+
on atmPasteMailBody(bodyText)
|
|
210
|
+
set savedClip to ""
|
|
211
|
+
set hadClip to false
|
|
212
|
+
try
|
|
213
|
+
set savedClip to the clipboard as text
|
|
214
|
+
set hadClip to true
|
|
215
|
+
end try
|
|
216
|
+
set the clipboard to bodyText
|
|
217
|
+
tell application "Mail" to activate
|
|
218
|
+
delay 0.25
|
|
219
|
+
try
|
|
220
|
+
atmFocusMailBody()
|
|
221
|
+
tell application "System Events"
|
|
222
|
+
tell process "Mail"
|
|
223
|
+
keystroke "v" using command down
|
|
224
|
+
end tell
|
|
225
|
+
end tell
|
|
226
|
+
delay 0.15
|
|
227
|
+
on error errMsg number errNum
|
|
228
|
+
if hadClip then set the clipboard to savedClip
|
|
229
|
+
if errMsg contains "assistive access" or errNum is -25211 then error "${ACCESSIBILITY_DENIED_SENTINEL}"
|
|
230
|
+
error errMsg number errNum
|
|
231
|
+
end try
|
|
232
|
+
if hadClip then set the clipboard to savedClip
|
|
233
|
+
end atmPasteMailBody`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function recipientStillPresentChecks(to) {
|
|
237
|
+
const toCount = asInteger(to.length, { min: 1, max: 99, field: "toCount" });
|
|
238
|
+
const stillThere = to
|
|
239
|
+
.map((address, i) => {
|
|
240
|
+
const n = asInteger(i, { min: 0, max: 99, field: "recipientIndex" });
|
|
241
|
+
return ` set atmWanted${n} to ${asString(address)}
|
|
242
|
+
set atmFound${n} to false
|
|
243
|
+
repeat with r in to recipients of newMessage
|
|
244
|
+
if (address of r as string) is atmWanted${n} then set atmFound${n} to true
|
|
245
|
+
end repeat
|
|
246
|
+
if atmFound${n} is false then error "${BODY_PASTE_MISDIRECTED_SENTINEL}"`;
|
|
247
|
+
})
|
|
248
|
+
.join("\n");
|
|
249
|
+
return ` if (count of to recipients of newMessage) is not ${toCount} then error "${BODY_PASTE_MISDIRECTED_SENTINEL}"
|
|
250
|
+
${stillThere}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
151
253
|
/**
|
|
152
254
|
* Build the outgoing-message script shared by send and draft.
|
|
153
255
|
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
256
|
+
* Always `make new outgoing message` (no `content:`) so `newMessage` is a
|
|
257
|
+
* real Mail object — Mail's `mailto` command is a no-op / non-returning
|
|
258
|
+
* on Mini+MacBook and left `newMessage` undefined (-2753).
|
|
259
|
+
*
|
|
260
|
+
* Do not set AppleScript `content` or `html content`: Ventura+ FB11734014
|
|
261
|
+
* cite-wraps those setters (`>` prefixes + `<blockquote type="cite">`).
|
|
262
|
+
* Recipients are AppleScript `make new … recipient`. The body is pasted
|
|
263
|
+
* into the compose window (native editor) via System Events. If paste
|
|
264
|
+
* lands in To, recipient count changes and we abort + delete (nothing sent).
|
|
156
265
|
*/
|
|
157
266
|
export function buildComposeScript({ to, cc, bcc, subject, body, send, html = false }) {
|
|
158
267
|
const recipients = [
|
|
@@ -161,17 +270,32 @@ export function buildComposeScript({ to, cc, bcc, subject, body, send, html = fa
|
|
|
161
270
|
recipientLines(bcc, "bcc recipient")
|
|
162
271
|
].filter((block) => block.length > 0).join("\n");
|
|
163
272
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
273
|
+
const plainBody = composeNativeBody(body, { html });
|
|
274
|
+
const pasteAndVerify = plainBody
|
|
275
|
+
? ` atmPasteMailBody(${asString(plainBody)})
|
|
276
|
+
tell application "Mail"
|
|
277
|
+
${recipientStillPresentChecks(to)}
|
|
278
|
+
end tell`
|
|
168
279
|
: "";
|
|
169
280
|
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
|
|
281
|
+
return `${buildMailBodyPasteHandler()}
|
|
282
|
+
|
|
283
|
+
tell application "Mail"
|
|
284
|
+
set newMessage to make new outgoing message with properties {subject:${asString(subject)}, visible:true}
|
|
285
|
+
tell newMessage
|
|
173
286
|
${recipients}
|
|
174
287
|
end tell
|
|
288
|
+
activate
|
|
289
|
+
end tell
|
|
290
|
+
try
|
|
291
|
+
${pasteAndVerify}
|
|
292
|
+
on error errMsg number errNum
|
|
293
|
+
try
|
|
294
|
+
tell application "Mail" to delete newMessage
|
|
295
|
+
end try
|
|
296
|
+
error errMsg number errNum
|
|
297
|
+
end try
|
|
298
|
+
tell application "Mail"
|
|
175
299
|
${send ? "send newMessage" : "save newMessage"}
|
|
176
300
|
end tell
|
|
177
301
|
return "OK"`;
|
|
@@ -384,7 +508,20 @@ export function recoverIfInSent({ inReplyTo = null, subject = null, forwardTo =
|
|
|
384
508
|
return null;
|
|
385
509
|
}
|
|
386
510
|
|
|
511
|
+
export function isMailAccessibilityDenial(result) {
|
|
512
|
+
if (!result) return false;
|
|
513
|
+
const text = String(result.error || "").toLowerCase();
|
|
514
|
+
return text.includes("accessibility_denied") || text.includes("assistive access");
|
|
515
|
+
}
|
|
516
|
+
|
|
387
517
|
function failure(action, summary, result, secrets) {
|
|
518
|
+
const err = String((result && result.error) || "").toLowerCase();
|
|
519
|
+
if (err.includes("body_paste_misdirected")) {
|
|
520
|
+
return `${action} failed — attempted to ${summary}. The body was pasted into a header field instead of the message body; nothing was sent.`;
|
|
521
|
+
}
|
|
522
|
+
if (isMailAccessibilityDenial(result)) {
|
|
523
|
+
return `${action} failed — attempted to ${summary}. ${MAIL_ACCESSIBILITY_GUIDANCE}`;
|
|
524
|
+
}
|
|
388
525
|
if (result.kind === "tcc" || isHardTccDenial(result && result.error)) {
|
|
389
526
|
return `${action} failed — attempted to ${summary}. ${MAIL_TCC_GUIDANCE}`;
|
|
390
527
|
}
|
package/lib/writeTools.js
CHANGED
|
@@ -57,7 +57,7 @@ export const WRITE_TOOL_DEFINITIONS = [
|
|
|
57
57
|
bcc: { type: "array", items: { type: "string" }, description: "BCC email addresses" },
|
|
58
58
|
subject: { type: "string", description: "Subject line (required)" },
|
|
59
59
|
body: { type: "string", description: "Message body (required)" },
|
|
60
|
-
body_format: { type: "string", enum: ["plain", "html"], description: "
|
|
60
|
+
body_format: { type: "string", enum: ["plain", "html"], description: "Plain text (default) or HTML. HTML is tag-stripped and pasted into Mail's compose window; AppleScript content/html content is not set because it quote-wraps the Sent body" },
|
|
61
61
|
...CONFIRM_PROPS
|
|
62
62
|
},
|
|
63
63
|
required: ["to", "subject", "body"]
|
|
@@ -74,7 +74,7 @@ export const WRITE_TOOL_DEFINITIONS = [
|
|
|
74
74
|
bcc: { type: "array", items: { type: "string" }, description: "BCC email addresses" },
|
|
75
75
|
subject: { type: "string", description: "Subject line" },
|
|
76
76
|
body: { type: "string", description: "Message body" },
|
|
77
|
-
body_format: { type: "string", enum: ["plain", "html"], description: "
|
|
77
|
+
body_format: { type: "string", enum: ["plain", "html"], description: "Plain text (default) or HTML. HTML is tag-stripped and pasted into Mail's compose window; AppleScript content/html content is not set because it quote-wraps the Sent body" },
|
|
78
78
|
...CONFIRM_PROPS
|
|
79
79
|
},
|
|
80
80
|
required: ["to"]
|