apple-tools-mcp 3.0.2 → 3.0.3

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.
Files changed (2) hide show
  1. package/lib/mailWrite.js +27 -0
  2. package/package.json +1 -1
package/lib/mailWrite.js CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  isHardTccDenial
32
32
  } from "./appleScript.js";
33
33
  import { isIndexerMode } from "./processMode.js";
34
+ import { safeSpawn } from "./shell.js";
34
35
  import {
35
36
  planWrite,
36
37
  plannedWriteResult,
@@ -160,6 +161,21 @@ export const BODY_PASTE_MISDIRECTED_SENTINEL = "BODY_PASTE_MISDIRECTED";
160
161
  export const BODY_FOCUS_FAILED_SENTINEL = "BODY_FOCUS_FAILED";
161
162
  /** Focus request ran, but the re-read AXFocusedUIElement was not the chosen body. */
162
163
  export const BODY_FOCUS_UNPROVEN_SENTINEL = "BODY_FOCUS_UNPROVEN";
164
+ /** System Events sees 0 Mail windows (display asleep) while Mail AppleScript still lists the compose. */
165
+ export const DISPLAY_ASLEEP_GUI_UNAVAILABLE_SENTINEL = "DISPLAY_ASLEEP_GUI_UNAVAILABLE";
166
+ export const DISPLAY_WAKE_SECONDS = 15;
167
+ export const DISPLAY_WAKE_POLL_ATTEMPTS = 20;
168
+
169
+ /** Declare user activity so a sleeping display wakes before GUI scripting. Best-effort; never throws. */
170
+ export function wakeDisplay() {
171
+ if (process.platform !== "darwin" || process.env.VITEST) return false;
172
+ try {
173
+ return safeSpawn("/usr/bin/caffeinate", ["-u", "-t", String(DISPLAY_WAKE_SECONDS)], { timeout: 5000 }).status === 0;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+
163
179
  /** Mail To: recipient chips surface as U+FFFC (object replacement), not body text. */
164
180
  export const MAIL_RECIPIENT_TOKEN_CHAR_ID = 65532;
165
181
  export const GUI_SCRIPTING_UNAVAILABLE_SENTINEL = "GUI_SCRIPTING_UNAVAILABLE";
@@ -630,6 +646,11 @@ on atmFillMailBody(bodyText, needle, titleMarker, msg)
630
646
  tell application "System Events"
631
647
  tell process "Mail"
632
648
  set frontmost to true
649
+ repeat ${DISPLAY_WAKE_POLL_ATTEMPTS} times
650
+ if (count of (every window)) > 0 then exit repeat
651
+ delay 0.5
652
+ end repeat
653
+ set atmSeWindowCount to count of (every window)
633
654
  delay 0.3
634
655
  set atmWin to missing value
635
656
  repeat with atmCand in windows
@@ -658,6 +679,7 @@ on atmFillMailBody(bodyText, needle, titleMarker, msg)
658
679
  end if
659
680
  end repeat
660
681
  end if
682
+ if atmWin is missing value and atmSeWindowCount is 0 then error "${DISPLAY_ASLEEP_GUI_UNAVAILABLE_SENTINEL}"
661
683
  if atmWin is missing value then error "${BODY_FOCUS_FAILED_SENTINEL}"
662
684
  try
663
685
  perform action "AXRaise" of atmWin
@@ -744,6 +766,7 @@ on atmFillMailBody(bodyText, needle, titleMarker, msg)
744
766
  on error errMsg number errNum
745
767
  if errMsg contains "assistive access" or errNum is -25211 then error "${ACCESSIBILITY_DENIED_SENTINEL}"
746
768
  if errMsg contains "${GUI_SCRIPTING_UNAVAILABLE_SENTINEL}" then error "${GUI_SCRIPTING_UNAVAILABLE_SENTINEL}"
769
+ if errMsg contains "${DISPLAY_ASLEEP_GUI_UNAVAILABLE_SENTINEL}" then error "${DISPLAY_ASLEEP_GUI_UNAVAILABLE_SENTINEL}"
747
770
  error errMsg number errNum
748
771
  end try
749
772
  end atmFillMailBody`;
@@ -1292,6 +1315,9 @@ function failure(action, summary, result, secrets) {
1292
1315
  if (err.includes("gui_scripting_unavailable")) {
1293
1316
  return `${action} failed — attempted to ${summary}. ${MAIL_GUI_SCRIPTING_GUIDANCE}`;
1294
1317
  }
1318
+ if (err.includes("display_asleep_gui_unavailable")) {
1319
+ return `${action} failed — attempted to ${summary}. The display is asleep, so System Events cannot see Mail's compose window (GUI unavailable even after a wake attempt); nothing was sent. Wake the display and retry.`;
1320
+ }
1295
1321
  if (err.includes("body_focus_unproven")) {
1296
1322
  return `${action} failed — attempted to ${summary}. Keyboard focus was not on the message body (focus proof failed); the draft was discarded and nothing was sent.`;
1297
1323
  }
@@ -1480,6 +1506,7 @@ export function mailCompose(args = {}, { draft = false, indexerMode = isIndexerM
1480
1506
  });
1481
1507
 
1482
1508
  const deadlineMs = sentVerifyClock.now() + MCP_CALL_DEADLINE_MS;
1509
+ wakeDisplay();
1483
1510
  const result = runAppleScript(script, { timeout: MAIL_COMPOSE_TIMEOUT_MS, appName: "Mail" });
1484
1511
  if (
1485
1512
  !result.ok &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-tools-mcp",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "MCP server for semantic search and write actions across Apple Mail, Messages, Calendar, and Contacts. Speaks stdio (local) or bearer-token-authenticated Streamable HTTP (LAN / Tailscale).",
5
5
  "type": "module",
6
6
  "main": "index.js",