apple-tools-mcp 2.1.1 → 2.1.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 CHANGED
@@ -104,7 +104,7 @@ If the invoked CLI path and `process.execPath` differ, the command prints a **WA
104
104
 
105
105
  On the host UI (Mini Screen Sharing or MacBook local), open **System Settings → Privacy & Security → Automation**, then run the command. Click **Allow** for **`node`** → Contacts, Calendar, Mail, Messages, and System Events. npm `postinstall` only **prints a reminder** — it does not run the probes unattended.
106
106
 
107
- This is a real Apple Events pass: Mail uses `make new outgoing message` (compose then discard; nothing is sent). Messages enumerates accounts (nothing is sent). `dry_run` of `mail_send` / `messages_send` never talks to those apps and **does not count**. Contacts creates and deletes a throwaway person in-script. Calendar lists calendars only (no leftover events). System Events is asked for its process name (nothing is typed) so the keystroke compose path can pop **Allow**.
107
+ This is a real Apple Events pass: Mail uses `make new outgoing message` (compose then discard; nothing is sent). Messages enumerates accounts (nothing is sent). `dry_run` of `mail_send` / `messages_send` never talks to those apps and **does not count**. Contacts creates and deletes a throwaway person in-script. Calendar lists calendars only (no leftover events). System Events is probed with **process / GUI scripting** (`tell process` + `unix id` of System Events; nothing is typed) so the keystroke compose path can pop **Allow** and verify a real Automation grant. A soft `tell application "System Events" to get name` is **not** enough — that can succeed while TCC still has node → System Events **denied**. If a prior **Don't Allow** left a sticky Deny, `permissions` reports System Events **missing** and exits non-zero: turn **System Events** on for that node in System Settings → Privacy & Security → **Automation**, then **re-run `apple-tools-mcp permissions` after Allow**.
108
108
 
109
109
  **One-pass first-run:** Allow **`node`** to control **Mail**, **Messages**, **Contacts**, **Calendar**, and **System Events** in the same Automation pass. Contacts or Calendar being allowed does **not** grant Mail, Messages, or System Events — they are separate Apple Events targets. A denied Mail grant hangs `mail_send` / `mail_draft` / `mail_reply` / `mail_forward` until the client disconnects; `dry_run` never talks to Mail, so that deny is invisible until a real compose.
110
110
 
@@ -603,7 +603,7 @@ The message names which process macOS was actually asking about. Work through it
603
603
 
604
604
  1. **MacBook / Terminal (`permissions` CLI):** there is **no** always-on indexer. Re-run `apple-tools-mcp permissions` from **Terminal.app**, Allow the **printed `process.execPath`**, and check System Settings → Privacy & Security → **Automation** for that node → Contacts, Calendar, Mail, Messages, and System Events. Do **not** start `apple-tools-indexer`.
605
605
  2. **Mini only:** `pgrep -fl apple-tools-indexer` / `~/.apple-tools-mcp/writer.sock`. If the LaunchAgent is the product host, start it so writes run under launchd-owned node (see [Mini ship-gate](#mini-ship-gate-host-setup)).
606
- 3. **Did you approve the Automation prompts for `node`?** Re-run `apple-tools-mcp permissions` with the product `node` (it prints `process.execPath`). Open System Settings → Privacy & Security → **Automation** and confirm **that printed node** is allowed to control **Mail**, **Messages**, Contacts, Calendar, and **System Events**. On Mini the LaunchAgent binary is whatever the plist / `which node` reports (`/Users/petercoates/.local/node/bin/node` is a Mini *example* only). MacBook Claude nvm is `/Users/petercoates/.nvm/versions/node/v22.21.1/bin/node`, not Homebrew. Watch the host — Allow **`node`**, not any other app. Contacts or Calendar being allowed does **not** grant Mail. A `mail_send` hang is **Automation denied**, not “Mail.app could not be reached”; `dry_run` never talks to Mail so it cannot detect this. Do **not** approve the MCP client / host app that launched a short-lived stdio server, and do **not** try to add `node` via **+** in the Contacts or Calendars privacy lists — those panes often have no Add button. `tccutil reset AppleEvents` re-arms the Automation prompt so you can Allow **`node`** again. Full Disk Access is a separate read grant; npm Trusted Publisher / tokens are unrelated.
606
+ 3. **Did you approve the Automation prompts for `node`?** Re-run `apple-tools-mcp permissions` with the product `node` (it prints `process.execPath`). Open System Settings → Privacy & Security → **Automation** and confirm **that printed node** is allowed to control **Mail**, **Messages**, Contacts, Calendar, and **System Events**. A prior **Don't Allow** for System Events is a sticky Deny — the Allow dialog will not appear again; turn **System Events** on for that node, then re-run `permissions` (it must not report granted from a soft name check). On Mini the LaunchAgent binary is whatever the plist / `which node` reports (`/Users/petercoates/.local/node/bin/node` is a Mini *example* only). MacBook Claude nvm is `/Users/petercoates/.nvm/versions/node/v22.21.1/bin/node`, not Homebrew. Watch the host — Allow **`node`**, not any other app. Contacts or Calendar being allowed does **not** grant Mail. A `mail_send` hang is **Automation denied**, not “Mail.app could not be reached”; `dry_run` never talks to Mail so it cannot detect this. Do **not** approve the MCP client / host app that launched a short-lived stdio server, and do **not** try to add `node` via **+** in the Contacts or Calendars privacy lists — those panes often have no Add button. `tccutil reset AppleEvents` re-arms the Automation prompt so you can Allow **`node`** again. Full Disk Access is a separate read grant; npm Trusted Publisher / tokens are unrelated.
607
607
  4. **Mini: is the daemon's node binary the one with Full Disk Access?** LaunchAgents do not inherit your shell `PATH`; confirm the plist points at the same path `which node` reports.
608
608
  5. **Is it actually the write path?** Contacts or Calendar *reads* failing with `EPERM` is a Full Disk Access / attribution problem, not the entitlement gap — fix FDA for the responsible process. Contacts or Calendar *CRUD* failing with no prompt under Claude Desktop is the [documented host limitation](#reads-and-writes-are-different-mechanisms): Claude.app carries neither the addressbook nor the calendars entitlement. On Mini, start the daemon so the write goes through the bridge. On MacBook, use Terminal.app + the printed node.
609
609
  6. **"Contacts.app / Calendar.app could not be reached"** with the app clearly installed is an **Automation / responsible-process** failure, not a missing app — macOS reports a refused Apple event as `-1728` / "can't get application". On MacBook / Terminal, Allow the printed `process.execPath`. On Mini, the write bridge / LaunchAgent may apply. A Contacts `-600` / “application isn't running” is a **cold launch**, not Automation — keep Contacts, Mail, and Messages running; Calendar does not need to stay open.
@@ -345,15 +345,19 @@ export const MAIL_ACCESSIBILITY_GUIDANCE =
345
345
  "This is not a Mail Automation deny (-1743 / -10004).";
346
346
 
347
347
  /**
348
- * System Events Apple Events / GUI scripting is missing, so the keystroke
349
- * compose path cannot run. Distinct from a Mail send hang: nothing was typed
350
- * or sent. Mini write-bridge should return unsupported so stdio falls back
351
- * in-process rather than wedging ~60s.
348
+ * Sticky Don't Allow for node → System Events: the Allow dialog will not
349
+ * reappear. Operators must flip the Automation row and re-run permissions.
352
350
  */
351
+ export const SYSTEM_EVENTS_STICKY_DENY_GUIDANCE =
352
+ "A prior Don't Allow for node → System Events is a sticky Deny: the Allow dialog will not appear again. " +
353
+ "Turn System Events on for this node in System Settings → Privacy & Security → Automation, then re-run apple-tools-mcp permissions after Allow.";
354
+
355
+ /** System Events GUI scripting missing — not a Mail send hang. */
353
356
  export const MAIL_GUI_SCRIPTING_GUIDANCE =
354
357
  "System Events GUI scripting is unavailable for this process, so the body cannot be typed into Mail. " +
355
358
  "mail_send / mail_draft use System Events keystrokes (Tab into the body, typed Returns) so Mail does not cite-wrap the Sent body (FB11734014). " +
356
359
  "Allow node → System Events in System Settings → Privacy & Security → Automation (run apple-tools-mcp permissions after install/upgrade) in addition to node → Mail, and keep Accessibility allowed. " +
360
+ SYSTEM_EVENTS_STICKY_DENY_GUIDANCE + " " +
357
361
  "This is not a Mail send timeout and not a Mail Automation deny (-1743 / -10004).";
358
362
 
359
363
  /**
package/lib/mailWrite.js CHANGED
@@ -160,11 +160,13 @@ export const BODY_PASTE_MISDIRECTED_SENTINEL = "BODY_PASTE_MISDIRECTED";
160
160
  export const BODY_FOCUS_FAILED_SENTINEL = "BODY_FOCUS_FAILED";
161
161
  export const GUI_SCRIPTING_UNAVAILABLE_SENTINEL = "GUI_SCRIPTING_UNAVAILABLE";
162
162
  export const SYSTEM_EVENTS_PROBE_MARKER = "ATM_SYSTEM_EVENTS_PROBE";
163
+ /** Permissions-only marker: process / GUI scripting, not application `name`. */
164
+ export const SYSTEM_EVENTS_AUTOMATION_PROBE_MARKER = "ATM_SYSTEM_EVENTS_AUTOMATION_PROBE";
163
165
  /** Compose body AX height vs To/Subject/Cc fields (those are ~22px). Used to reject short header AXTextAreas after Tab; does not walk windows. */
164
166
  export const MAIL_BODY_MIN_AX_HEIGHT = 50;
165
167
  /** Mini-proven Tab count from default compose focus into the body. */
166
168
  export const MAIL_BODY_TAB_MAX = 6;
167
- /** Cheap System Events Automation probe used before compose; SIGKILL if it still hangs. */
169
+ /** Cheap pre-compose System Events check; SIGKILL if it still hangs. Not the permissions grant. */
168
170
  export const MAIL_SE_PROBE_TIMEOUT_MS = 4000;
169
171
  /** First-run / upgrade permissions probe: long enough for the Allow click. */
170
172
  export const SYSTEM_EVENTS_AUTOMATION_PROBE_TIMEOUT_MS = 30000;
@@ -250,6 +252,11 @@ export function isSystemEventsProbeScript(script) {
250
252
  return String(script || "").includes(SYSTEM_EVENTS_PROBE_MARKER);
251
253
  }
252
254
 
255
+ export function isSystemEventsAutomationProbeScript(script) {
256
+ return String(script || "").includes(SYSTEM_EVENTS_AUTOMATION_PROBE_MARKER);
257
+ }
258
+
259
+ /** Cheap pre-compose check. Do not use for `permissions` — `get name` can soft-pass. */
253
260
  export function buildSystemEventsProbeScript() {
254
261
  const secs = asInteger(MAIL_SE_PROBE_APPLEEVENT_TIMEOUT_SEC, {
255
262
  min: 1,
@@ -275,15 +282,41 @@ export function probeSystemEventsGui({ timeout = MAIL_SE_PROBE_TIMEOUT_MS } = {}
275
282
  }
276
283
 
277
284
  /**
278
- * First-run / upgrade probe: a live System Events Apple Event that pops
279
- * Automation → System Events. Does not type into the focused UI.
280
- * Timeout is long enough for the operator to click Allow.
285
+ * First-run / upgrade probe. Soft `tell application "System Events" to get
286
+ * name` is not enough — Mini @2.1.1 reported granted while TCC auth_value=0
287
+ * for node → com.apple.systemevents. Process / GUI scripting (`tell process`)
288
+ * is the grant mail_send actually needs. Does not type into the focused UI.
289
+ * Timeout is long enough for the operator to click Allow. No short inner
290
+ * AppleEvent timeout (same as the Mail compose-and-discard probe).
291
+ */
292
+ export function buildSystemEventsAutomationProbeScript() {
293
+ return `-- ${SYSTEM_EVENTS_AUTOMATION_PROBE_MARKER}
294
+ tell application "System Events"
295
+ tell process "System Events"
296
+ set atmPid to unix id
297
+ set atmShown to name
298
+ if atmShown is "" then error "${GUI_SCRIPTING_UNAVAILABLE_SENTINEL}"
299
+ if atmPid is missing value then error "${GUI_SCRIPTING_UNAVAILABLE_SENTINEL}"
300
+ if (atmPid as integer) is less than or equal to 0 then error "${GUI_SCRIPTING_UNAVAILABLE_SENTINEL}"
301
+ count of UI elements
302
+ end tell
303
+ set atmUi to UI elements enabled
304
+ end tell
305
+ if atmUi is false then error "${ACCESSIBILITY_DENIED_SENTINEL}"
306
+ return "OK"`;
307
+ }
308
+
309
+ /**
310
+ * First-run / upgrade probe: a live System Events GUI-scripting Apple Event
311
+ * that creates or verifies Automation → System Events. Does not type.
281
312
  * @returns {{ ok: boolean, message: string, kind: string|null }}
282
313
  */
283
314
  export function probeSystemEventsAutomation() {
284
315
  const action = "system_events_automation_probe";
285
- const summary = "ask System Events for its name (Automation / Apple Events check; nothing is typed)";
286
- const result = probeSystemEventsGui({ timeout: SYSTEM_EVENTS_AUTOMATION_PROBE_TIMEOUT_MS });
316
+ const summary = "verify System Events process / GUI scripting (Automation / Apple Events grant; nothing is typed)";
317
+ const result = runAppleScript(buildSystemEventsAutomationProbeScript(), {
318
+ timeout: SYSTEM_EVENTS_AUTOMATION_PROBE_TIMEOUT_MS
319
+ });
287
320
  if (!result.ok) {
288
321
  const kind = systemEventsGrantKind(result);
289
322
  const guidance = isMailAccessibilityDenial(result)
@@ -300,7 +333,7 @@ export function probeSystemEventsAutomation() {
300
333
  kind: null,
301
334
  message: writeSuccessMessage(
302
335
  action,
303
- "System Events Automation allowed (process name read; nothing was typed)"
336
+ "System Events Automation allowed (process GUI scripting verified; nothing was typed)"
304
337
  )
305
338
  };
306
339
  }
@@ -10,11 +10,13 @@
10
10
  * points at a different node, print a WARN — Allows attach to execPath.
11
11
  * Mail and Messages use the existing live Apple Events helpers — dry_run of
12
12
  * mail_send / messages_send never talks to those apps and does not count.
13
+ * System Events uses process / GUI scripting (`tell process`), not a soft
14
+ * application `get name` — that can succeed without AppleEvents auth.
13
15
  */
14
16
 
15
17
  import fs from "fs";
16
18
  import path from "path";
17
- import { classifyAppleScriptError } from "./appleScript.js";
19
+ import { classifyAppleScriptError, SYSTEM_EVENTS_STICKY_DENY_GUIDANCE } from "./appleScript.js";
18
20
  import { probeMailAutomation, probeSystemEventsAutomation } from "./mailWrite.js";
19
21
  import { probeMessagesAutomation } from "./messagesWrite.js";
20
22
  import { probeContactsAutomation } from "./contactsWrite.js";
@@ -272,7 +274,9 @@ function bannerLines(binary, version) {
272
274
  "Nothing is sent to third parties. Mail composes and discards a temporary outgoing message.",
273
275
  "Messages only enumerates accounts. Contacts creates and deletes a throwaway person in-script.",
274
276
  "Calendar lists calendars only — no events are created.",
275
- "System Events is asked for its process name (mail_send keystrokes; nothing is typed)."
277
+ "System Events is probed with process / GUI scripting (tell process + unix id; nothing is typed).",
278
+ "A soft tell System Events to get name is not enough — that can succeed without AppleEvents auth.",
279
+ "A prior Don't Allow is a sticky Deny: turn System Events on for this node in Automation, then re-run this command."
276
280
  ];
277
281
  }
278
282
 
@@ -359,6 +363,9 @@ export async function runPermissionsCommand({
359
363
  } else {
360
364
  log("Result: INCOMPLETE — one or more required grants are missing or errored.");
361
365
  log(hostAdvice);
366
+ if (grants["System Events"] !== GRANT_GRANTED) {
367
+ log(SYSTEM_EVENTS_STICKY_DENY_GUIDANCE);
368
+ }
362
369
  log("Click Allow for the missing surfaces and re-run this command. Exit is non-zero (fail closed).");
363
370
  }
364
371
  return code;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-tools-mcp",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
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",