apple-tools-mcp 2.0.8 → 2.0.10

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
@@ -466,13 +466,17 @@ Two arguments are available on **every** write tool:
466
466
 
467
467
  **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.
468
468
 
469
- 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`). Body focus uses `text area` / `scroll area` / `text field` and `UI element whose role is "AXWebArea"` — never the System Events class `web area`, which does not compile on macOS 26.x (**-2741**, “Expected class name but found identifier”).
469
+ 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`). Body focus uses `text area` / `scroll area` / `text field` and `UI element whose role is "AXWebArea"` — never the System Events class `web area`, which does not compile on macOS 26.x (**-2741**, “Expected class name but found identifier”). The pre-paste focus read uses `first UI element whose focused is true` and `value of attribute "AXFocusedUIElement"` — never the compound `focused UI element`, which does not compile on System Events 26.x (**-2741** on MacBook 26.1, **-2740** on Mini 26.6.2).
470
470
 
471
471
  **Compose body focus (2.0.8).** After `make new outgoing message`, To and Subject are AppleScript properties (never paste). Cmd-V runs only after the caret leaves the header: a tall `AXWebArea` (HTML compose body), a tall `text area` (plain compose; short To/Subject fields are skipped), Subject then Tab, or a click in the lower two-thirds of the compose window. If focus is still a header field, the tool raises `BODY_FOCUS_FAILED` and does **not** paste. After paste it checks To/Cc/Bcc counts, the exact subject, and that the native body contains the intended text. A mismatch is `BODY_PASTE_MISDIRECTED`: the outgoing message is deleted and nothing is sent. Quote-prefix Sent prove and Sent/Outbox verify stay gated on this paste-focus contract — a MacBook 2.0.7 live `mail_send` with Accessibility allowed still hit `BODY_PASTE_MISDIRECTED` when Cmd-V landed in To/Subject (`ATP-207-SENT-PROVE-20260921-084048`).
472
472
 
473
+ **macOS 26 focus compile (2.0.9).** Dual-host Sent prove on `@2.0.8` never reached paste: `atmSafeToPaste` used bare `focused UI element`, which System Events 26.x rejects at compile. The 2.0.8 fail-closed gates stay; only the focus *query* changed to the forms hosts compiled.
474
+
475
+ **AppleScript reserved identifiers (2.0.10).** Dual-host Sent prove on `@2.0.9` compiled past the focused-UI fix, then failed in `atmFocusMailBody`: `set {tw, th} to size of atmElem` → **-2741** Expected expression but found `th` (AppleScript reserved ordinal). Size/position lists now use `atmW`/`atmH` (hosts confirmed) and other `atm*` names — never `th`/`tw`/`st`/`nd`/`rd`/`to`/`by`/`id` in destructuring. 2.0.8/2.0.9 fail-closed paste-focus and 2.0.9 AXFocusedUIElement / `whose focused is true` are unchanged.
476
+
473
477
  **mail_send success is Sent/Outbox verify (2.0.7).** AppleScript `send` returning without throw is not enough. After a real send the tool looks in **Sent** and **Outbox** and reports success only if the message is there. The success text names the delivery state (`mailbox: sent` + `delivery: sent`, or `mailbox: outbox` + `delivery: outbox` while still sending). If verify misses, the tool returns a failure (`isError`) — not success. Hang/timeout recover matches **To + subject** (and Message-ID when compose captured one). It never matches subject alone (short subjects like `test` are unsafe). `from` / account selection is out of scope; Mail's default From is used.
474
478
 
475
- **Manual prove (2.0.8 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. The caret must land in the **body** (not To/Subject): no `BODY_FOCUS_FAILED` / `BODY_PASTE_MISDIRECTED`, and the intended text must be in the native body before send. 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`, no **-2741** on body focus). That quote-prefix dual-host Sent prove is a separate bar from the 2.0.7 verify contract.
479
+ **Manual prove (2.0.10 on the Mac host):** AppleScript for paste-focus **compiles** (no `-2741` on `th`; no `-2741`/`-2740` on `focused UI element`). Then `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. The caret must land in the **body** (not To/Subject): no `BODY_FOCUS_FAILED` / `BODY_PASTE_MISDIRECTED`, and the intended text must be in the native body before send. 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`, no **-2741** / **-2740** on body focus). That quote-prefix dual-host Sent prove is a separate bar from the 2.0.7 verify contract.
476
480
 
477
481
  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.
478
482
 
package/lib/mailWrite.js CHANGED
@@ -159,6 +159,52 @@ export const BODY_FOCUS_FAILED_SENTINEL = "BODY_FOCUS_FAILED";
159
159
  /** Compose body AX height vs To/Subject/Cc fields (those are ~22px). */
160
160
  export const MAIL_BODY_MIN_AX_HEIGHT = 50;
161
161
 
162
+ /**
163
+ * AppleScript ordinal/keyword tokens that fail compile in `set {a, b}`
164
+ * lists on macOS 26.x (MacBook 26.1 + Mini 26.6.2: `th` → -2741).
165
+ * Paste-focus handlers must not destructure into these names.
166
+ */
167
+ export const ATM_APPLESCRIPT_RESERVED_SHORTS = Object.freeze([
168
+ "th",
169
+ "st",
170
+ "nd",
171
+ "rd",
172
+ "to",
173
+ "by",
174
+ "id"
175
+ ]);
176
+
177
+ /**
178
+ * macOS 26.x System Events rejects the compound `focused UI element`
179
+ * (MacBook 26.1 compile -2741; Mini 26.6.2 compile -2740). Hosts compile
180
+ * these instead — same class of dictionary break as bare `web area`.
181
+ */
182
+ export const ATM_FOCUSED_WHOSE_QUERY = "first UI element whose focused is true";
183
+ export const ATM_FOCUSED_AX_QUERY = 'value of attribute "AXFocusedUIElement"';
184
+
185
+ /**
186
+ * Resolve the focused System Events element without `focused UI element`.
187
+ * AXFocusedUIElement is tried first (nested caret); `whose focused is true`
188
+ * is the compile-proven fallback. A process-level whose match can be the
189
+ * key window — callers must still reject header roles / AXWindow.
190
+ */
191
+ export function buildAtmFocusedElementHandler() {
192
+ return `on atmFocusedElement()
193
+ tell application "System Events"
194
+ tell process "Mail"
195
+ try
196
+ set fe to ${ATM_FOCUSED_AX_QUERY}
197
+ if fe is not missing value then return fe
198
+ end try
199
+ try
200
+ return ${ATM_FOCUSED_WHOSE_QUERY}
201
+ end try
202
+ end tell
203
+ end tell
204
+ return missing value
205
+ end atmFocusedElement`;
206
+ }
207
+
162
208
  /**
163
209
  * Body text for native Mail compose (clipboard paste, not AppleScript content).
164
210
  *
@@ -192,10 +238,21 @@ const BODY_MIN_H = asInteger(MAIL_BODY_MIN_AX_HEIGHT, { min: 1, max: 500, field:
192
238
  * System Events needs Accessibility; Mail make/send still needs Automation.
193
239
  *
194
240
  * macOS 26.x System Events has no `web area` class (compile -2741,
195
- * "Expected class name but found identifier"). Hosts compile
196
- * `text area` / `scroll area` / `text field` and `UI element whose role is
197
- * "AXWebArea"`. Never focus the first AXTextArea blindly — on MacBook Mail
198
- * that is a header field, and Cmd-V becomes BODY_PASTE_MISDIRECTED.
241
+ * "Expected class name but found identifier") and no compound
242
+ * `focused UI element` (compile -2741 / -2740). Hosts compile
243
+ * `text area` / `scroll area` / `text field`, `UI element whose role is
244
+ * "AXWebArea"`, `first UI element whose focused is true`, and
245
+ * `value of attribute "AXFocusedUIElement"`. Never focus the first
246
+ * AXTextArea blindly — on MacBook Mail that is a header field, and
247
+ * Cmd-V becomes BODY_PASTE_MISDIRECTED.
248
+ *
249
+ * AppleScript treats ordinal suffixes as reserved tokens (`th` as in 4th,
250
+ * also `st`/`nd`/`rd`). Dual-host Sent prove on 2.0.9 compiled past the
251
+ * focused-UI fix, then failed in `atmFocusMailBody`:
252
+ * `set {tw, th} to size of atmElem` → -2741 Expected expression but found
253
+ * `th`. Size/position lists use `atmW`/`atmH` (hosts confirmed) and other
254
+ * `atm*` names — never `th`/`tw`/`st`/`nd`/`rd`/`to`/`by`/`id` in
255
+ * destructuring.
199
256
  *
200
257
  * Strategies, each fail-closed unless the caret left the header:
201
258
  * 1. Click a tall AXWebArea (HTML compose body)
@@ -204,22 +261,26 @@ const BODY_MIN_H = asInteger(MAIL_BODY_MIN_AX_HEIGHT, { min: 1, max: 500, field:
204
261
  * 4. Click the lower two-thirds of the compose window
205
262
  */
206
263
  export function buildMailBodyPasteHandler() {
207
- return `on atmSafeToPaste()
264
+ return `${buildAtmFocusedElementHandler()}
265
+
266
+ on atmSafeToPaste()
208
267
  tell application "System Events"
209
268
  tell process "Mail"
210
269
  try
211
- set fe to focused UI element
270
+ set fe to my atmFocusedElement()
271
+ if fe is missing value then return false
212
272
  set r to (role of fe) as string
273
+ if r is "AXWindow" then return false
213
274
  if r is "AXTextField" then return false
214
275
  if r is "AXComboBox" then return false
215
276
  if r is "AXButton" then return false
216
277
  if r is "AXMenuButton" then return false
217
278
  if r is "AXPopUpButton" then return false
218
- set eh to 0
279
+ set atmH to 0
219
280
  try
220
- set {ew, eh} to size of fe
281
+ set {atmW, atmH} to size of fe
221
282
  end try
222
- if r is "AXTextArea" and eh < ${BODY_MIN_H} then return false
283
+ if r is "AXTextArea" and atmH < ${BODY_MIN_H} then return false
223
284
  ignoring case
224
285
  set bits to ""
225
286
  try
@@ -235,7 +296,7 @@ export function buildMailBodyPasteHandler() {
235
296
  end ignoring
236
297
  if r is "AXWebArea" then return true
237
298
  if r is "AXTextArea" then return true
238
- if eh >= ${BODY_MIN_H} then return true
299
+ if atmH >= ${BODY_MIN_H} then return true
239
300
  end try
240
301
  return false
241
302
  end tell
@@ -295,8 +356,8 @@ on atmFocusMailBody(expectedSubject)
295
356
  end if
296
357
  try
297
358
  if (role of atmElem as string) is "AXWebArea" then
298
- set {ew, eh} to size of atmElem
299
- if eh >= ${BODY_MIN_H} then
359
+ set {atmW, atmH} to size of atmElem
360
+ if atmH >= ${BODY_MIN_H} then
300
361
  click atmElem
301
362
  delay 0.08
302
363
  if my atmSafeToPaste() then return
@@ -305,8 +366,8 @@ on atmFocusMailBody(expectedSubject)
305
366
  end try
306
367
  try
307
368
  if (role of atmElem as string) is "AXTextArea" then
308
- set {tw, th} to size of atmElem
309
- if th >= ${BODY_MIN_H} and atmTallText is missing value then set atmTallText to atmElem
369
+ set {atmW, atmH} to size of atmElem
370
+ if atmH >= ${BODY_MIN_H} and atmTallText is missing value then set atmTallText to atmElem
310
371
  end if
311
372
  end try
312
373
  try
@@ -333,8 +394,8 @@ on atmFocusMailBody(expectedSubject)
333
394
  end repeat
334
395
  try
335
396
  set ta to text area 1 of scroll area 1 of w
336
- set {tw, th} to size of ta
337
- if th >= ${BODY_MIN_H} then
397
+ set {atmW, atmH} to size of ta
398
+ if atmH >= ${BODY_MIN_H} then
338
399
  set focused of ta to true
339
400
  delay 0.08
340
401
  if my atmSafeToPaste() then return
@@ -366,9 +427,9 @@ on atmFocusMailBody(expectedSubject)
366
427
  if my atmSafeToPaste() then return
367
428
  end try
368
429
  end if
369
- set {wx, wy} to position of w
370
- set {ww, wh} to size of w
371
- click at {(wx + (ww div 2)) as integer, (wy + ((wh * 2) div 3)) as integer}
430
+ set {atmX, atmY} to position of w
431
+ set {atmWinW, atmWinH} to size of w
432
+ click at {(atmX + (atmWinW div 2)) as integer, (atmY + ((atmWinH * 2) div 3)) as integer}
372
433
  delay 0.08
373
434
  if my atmSafeToPaste() then return
374
435
  error "${BODY_FOCUS_FAILED_SENTINEL}"
@@ -436,12 +497,13 @@ function composeMisdirectChecks({ to, cc = [], bcc = [], subject, body }) {
436
497
  end if
437
498
  if atmGotBody does not contain ${asString(needle)} then
438
499
  try
439
- tell application "System Events"
440
- tell process "Mail"
441
- set atmAxVal to value of focused UI element as string
500
+ set atmFe to my atmFocusedElement()
501
+ if atmFe is not missing value then
502
+ tell application "System Events"
503
+ set atmAxVal to value of atmFe as string
442
504
  if atmAxVal contains ${asString(needle)} then set atmGotBody to atmAxVal
443
505
  end tell
444
- end tell
506
+ end if
445
507
  end try
446
508
  end if
447
509
  if atmGotBody does not contain ${asString(needle)} then error "${BODY_PASTE_MISDIRECTED_SENTINEL}"`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-tools-mcp",
3
- "version": "2.0.8",
3
+ "version": "2.0.10",
4
4
  "description": "MCP server for semantic search and write actions across Apple Mail, Messages, Calendar, and Contacts",
5
5
  "type": "module",
6
6
  "main": "index.js",