apple-notes-mcp 2.6.4 → 2.6.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 CHANGED
@@ -1019,8 +1019,8 @@ All configuration is optional — the server works out of the box. Override beha
1019
1019
  | `APPLE_NOTES_MCP_MAX_ATTACHMENT_BYTES` | `26214400` (25 MB) | Max size of an attachment that [`fetch-attachment`](#fetch-attachment) will base64-encode inline. Larger attachments are rejected with an error pointing at [`save-attachment`](#save-attachment) (which streams to disk and has no such limit). Raise it to fetch bigger attachments inline; lower it to cap memory. |
1020
1020
  | `APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES` | `262144` (256 KB) | Per-image cap on the base64 payload kept inline in a [`get-note-content`](#get-note-content) response. Inline images over the cap are replaced with placeholders (with a warning appended) so an image-heavy note cannot exceed the MCP client's message limit and drop the connection; export the real files with [`save-attachment`](#save-attachment) or [`fetch-attachment`](#fetch-attachment). Raise it to keep bigger images inline. |
1021
1021
  | `APPLE_NOTES_MCP_CONFIG_FILE` | `~/Library/Application Support/apple-notes-mcp/config.json` | Path to the JSON config file (see below). |
1022
- | `APPLE_NOTES_MCP_TIMEOUT_MS` | `30000` (30 s) | Per-call AppleScript timeout. Raise it if full-library operations (large searches, exports) time out on a big Notes library. Per-call `timeoutMs` options still win. |
1023
- | `APPLE_NOTES_MCP_MAX_RETRIES` | `2` | Total attempts for an AppleScript call that fails with a **transient** error (Notes.app busy / not responding / lost connection / timeout). `2` means one retry; set `1` to fail fast with no retries. Non-transient errors (e.g. "note not found") never retry. |
1022
+ | `APPLE_NOTES_MCP_TIMEOUT_MS` | `30000` (30 s) | Total AppleScript operation timeout, including retry attempts and delays. Raise it if full-library operations (large searches, exports) time out on a big Notes library. Per-call `timeoutMs` options still win. |
1023
+ | `APPLE_NOTES_MCP_MAX_RETRIES` | `2` | Maximum attempts for a read-only AppleScript call that fails with a **transient** error (Notes.app busy / not responding / lost connection). `2` means one retry; set `1` to fail fast with no retries. Retries share the single `APPLE_NOTES_MCP_TIMEOUT_MS` budget rather than each getting a fresh one, and a retry is skipped when under a second of that budget remains — so this is a ceiling, not a guarantee. In particular a call that exhausts the budget with a **timeout** has no time left to retry by construction. Mutating operations run once because a timeout can occur after Notes.app applied the change. Non-transient errors (e.g. "note not found") never retry. |
1024
1024
  | `APPLE_NOTES_MCP_RETRY_DELAY_MS` | `1000` (1 s) | Base delay before the first retry; subsequent retries back off exponentially (1s, 2s, 4s, ...). |
1025
1025
  | `DEBUG` / `VERBOSE` | unset | Set either to enable verbose diagnostic logging to stderr. |
1026
1026
 
package/build/index.js CHANGED
@@ -38677,6 +38677,7 @@ function getMaxBuffer() {
38677
38677
  return envPositiveNumber("APPLE_NOTES_MCP_MAX_BUFFER") ?? DEFAULT_MAX_BUFFER_BYTES;
38678
38678
  }
38679
38679
  var SCRIPT_TIMEOUT_HEADROOM_MS = 5e3;
38680
+ var MIN_ATTEMPT_BUDGET_MS = 1e3;
38680
38681
  function wrapWithTimeout(script, processTimeoutMs) {
38681
38682
  const seconds = Math.max(1, Math.ceil((processTimeoutMs - SCRIPT_TIMEOUT_HEADROOM_MS) / 1e3));
38682
38683
  return `with timeout of ${seconds} seconds
@@ -38818,7 +38819,6 @@ function executeAppleScript(script, options = {}) {
38818
38819
  error: "Cannot execute empty AppleScript"
38819
38820
  };
38820
38821
  }
38821
- const preparedScript = wrapWithTimeout(script.trim(), timeoutMs);
38822
38822
  debugLog("Executing AppleScript", {
38823
38823
  scriptPreview: script.trim().substring(0, 200) + (script.length > 200 ? "..." : ""),
38824
38824
  timeout: timeoutMs,
@@ -38826,13 +38826,16 @@ function executeAppleScript(script, options = {}) {
38826
38826
  });
38827
38827
  let lastError = null;
38828
38828
  const startTime = Date.now();
38829
+ const deadline = startTime + timeoutMs;
38829
38830
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
38831
+ const attemptTimeoutMs = Math.max(1, deadline - Date.now());
38832
+ const preparedScript = wrapWithTimeout(script.trim(), attemptTimeoutMs);
38830
38833
  const attemptStart = Date.now();
38831
38834
  try {
38832
38835
  const output = execFileSync("osascript", ["-"], {
38833
38836
  input: preparedScript,
38834
38837
  encoding: "utf8",
38835
- timeout: timeoutMs,
38838
+ timeout: attemptTimeoutMs,
38836
38839
  // SIGKILL (not the default SIGTERM): a wedged osascript blocked on an
38837
38840
  // unresponsive Notes.app can ignore SIGTERM and leak, piling up and
38838
38841
  // worsening contention. SIGKILL guarantees reaping on timeout. (#17)
@@ -38885,8 +38888,9 @@ function executeAppleScript(script, options = {}) {
38885
38888
  };
38886
38889
  const canRetry = isTimeout || isRetryableError(errorMessage);
38887
38890
  const hasAttemptsLeft = attempt < maxRetries;
38888
- if (canRetry && hasAttemptsLeft) {
38889
- const delayMs = retryDelayMs * Math.pow(2, attempt - 1);
38891
+ const delayMs = retryDelayMs * Math.pow(2, attempt - 1);
38892
+ const hasTimeForRetry = Date.now() + delayMs + MIN_ATTEMPT_BUDGET_MS < deadline;
38893
+ if (canRetry && hasAttemptsLeft && hasTimeForRetry) {
38890
38894
  console.error(
38891
38895
  `AppleScript retry: Attempt ${attempt}/${maxRetries} failed with "${errorMessage}". Retrying in ${delayMs}ms...`
38892
38896
  );
@@ -39221,6 +39225,9 @@ var FIELD_SEP = "";
39221
39225
  var RECORD_SEP = "";
39222
39226
  var AS_FIELD_SEP = "(ASCII character 31)";
39223
39227
  var AS_RECORD_SEP = "(ASCII character 30)";
39228
+ function executeMutationAppleScript(script) {
39229
+ return executeAppleScript(script, { maxRetries: 1 });
39230
+ }
39224
39231
  function escapeForAppleScript(text) {
39225
39232
  if (!text) {
39226
39233
  return "";
@@ -39481,7 +39488,7 @@ var AppleNotesManager = class {
39481
39488
  `;
39482
39489
  }
39483
39490
  const script = buildAccountScopedScript({ account: targetAccount }, createCommand);
39484
- const result = executeAppleScript(script);
39491
+ const result = executeMutationAppleScript(script);
39485
39492
  if (!result.success) {
39486
39493
  console.error(`Failed to create note "${title}":`, result.error);
39487
39494
  return null;
@@ -39824,7 +39831,7 @@ var AppleNotesManager = class {
39824
39831
  const safeTitle = escapePlainStringForAppleScript(title);
39825
39832
  const deleteCommand = `delete note "${safeTitle}"`;
39826
39833
  const script = buildAccountScopedScript({ account: targetAccount }, deleteCommand);
39827
- const result = executeAppleScript(script);
39834
+ const result = executeMutationAppleScript(script);
39828
39835
  if (!result.success) {
39829
39836
  console.error(`Failed to delete note "${title}":`, result.error);
39830
39837
  return false;
@@ -39844,7 +39851,7 @@ var AppleNotesManager = class {
39844
39851
  const safeId = sanitizeId(id);
39845
39852
  const deleteCommand = `delete note id "${safeId}"`;
39846
39853
  const script = buildAppLevelScript(deleteCommand);
39847
- const result = executeAppleScript(script);
39854
+ const result = executeMutationAppleScript(script);
39848
39855
  if (!result.success) {
39849
39856
  console.error(`Failed to delete note with ID "${id}":`, result.error);
39850
39857
  return false;
@@ -39888,7 +39895,7 @@ var AppleNotesManager = class {
39888
39895
  }
39889
39896
  const updateCommand = `set body of note "${safeCurrentTitle}" to "${fullBody}"`;
39890
39897
  const script = buildAccountScopedScript({ account: targetAccount }, updateCommand);
39891
- const result = executeAppleScript(script);
39898
+ const result = executeMutationAppleScript(script);
39892
39899
  if (!result.success) {
39893
39900
  console.error(`Failed to update note "${title}":`, result.error);
39894
39901
  return false;
@@ -39937,7 +39944,7 @@ var AppleNotesManager = class {
39937
39944
  const safeId = sanitizeId(id);
39938
39945
  const updateCommand = `set body of note id "${safeId}" to "${fullBody}"`;
39939
39946
  const script = buildAppLevelScript(updateCommand);
39940
- const result = executeAppleScript(script);
39947
+ const result = executeMutationAppleScript(script);
39941
39948
  if (!result.success) {
39942
39949
  console.error(`Failed to update note with ID "${id}":`, result.error);
39943
39950
  return false;
@@ -40256,7 +40263,7 @@ var AppleNotesManager = class {
40256
40263
  createCommand = `make new folder at ${parentRef} with properties {name:"${segmentName}"}`;
40257
40264
  }
40258
40265
  const script = buildAccountScopedScript({ account: targetAccount }, createCommand);
40259
- const result = executeAppleScript(script);
40266
+ const result = executeMutationAppleScript(script);
40260
40267
  if (!result.success) {
40261
40268
  console.error(`Failed to create folder "${name}":`, result.error);
40262
40269
  return null;
@@ -40288,7 +40295,7 @@ var AppleNotesManager = class {
40288
40295
  const targetAccount = this.resolveAccount(account);
40289
40296
  const deleteCommand = `delete ${buildFolderReference(name)}`;
40290
40297
  const script = buildAccountScopedScript({ account: targetAccount }, deleteCommand);
40291
- const result = executeAppleScript(script);
40298
+ const result = executeMutationAppleScript(script);
40292
40299
  if (!result.success) {
40293
40300
  console.error(`Failed to delete folder "${name}":`, result.error);
40294
40301
  return false;
@@ -40345,7 +40352,7 @@ var AppleNotesManager = class {
40345
40352
  move noteRef to destFolder
40346
40353
  `;
40347
40354
  const script = buildAppLevelScript(moveCommand);
40348
- const result = executeAppleScript(script);
40355
+ const result = executeMutationAppleScript(script);
40349
40356
  if (!result.success) {
40350
40357
  console.error(
40351
40358
  `Cannot move note to "${destinationFolder}" (folder may not exist):`,
@@ -40500,7 +40507,7 @@ var AppleNotesManager = class {
40500
40507
  showNoteById(id, separately = false) {
40501
40508
  const safeId = sanitizeId(id);
40502
40509
  const separatelyClause = separately ? " separately true" : "";
40503
- const result = executeAppleScript(
40510
+ const result = executeMutationAppleScript(
40504
40511
  buildAppLevelScript(`show note id "${safeId}"${separatelyClause}`)
40505
40512
  );
40506
40513
  if (!result.success) {
@@ -40563,7 +40570,7 @@ var AppleNotesManager = class {
40563
40570
  showFolderById(id, separately = false) {
40564
40571
  const safeId = sanitizeId(id);
40565
40572
  const separatelyClause = separately ? " separately true" : "";
40566
- const result = executeAppleScript(
40573
+ const result = executeMutationAppleScript(
40567
40574
  buildAppLevelScript(`show folder id "${safeId}"${separatelyClause}`)
40568
40575
  );
40569
40576
  if (!result.success) {
@@ -40585,7 +40592,7 @@ var AppleNotesManager = class {
40585
40592
  showAccountById(id, separately = false) {
40586
40593
  const safeId = sanitizeId(id);
40587
40594
  const separatelyClause = separately ? " separately true" : "";
40588
- const result = executeAppleScript(
40595
+ const result = executeMutationAppleScript(
40589
40596
  buildAppLevelScript(`show account id "${safeId}"${separatelyClause}`)
40590
40597
  );
40591
40598
  if (!result.success) {
@@ -40628,7 +40635,7 @@ var AppleNotesManager = class {
40628
40635
  return "OK"
40629
40636
  end tell
40630
40637
  `;
40631
- const result = executeAppleScript(script);
40638
+ const result = executeMutationAppleScript(script);
40632
40639
  if (!result.success) {
40633
40640
  console.error(
40634
40641
  `Failed to show attachment "${attachmentId}" on note "${noteId}":`,
@@ -41030,7 +41037,7 @@ var AppleNotesManager = class {
41030
41037
  return "OK" & ${AS_FIELD_SEP} & (name of theAttachment) & ${AS_FIELD_SEP} & (content identifier of theAttachment)
41031
41038
  end tell
41032
41039
  `;
41033
- const result = executeAppleScript(script);
41040
+ const result = executeMutationAppleScript(script);
41034
41041
  if (!result.success) {
41035
41042
  return { success: false, error: result.error ?? "unknown error" };
41036
41043
  }
@@ -41164,7 +41171,7 @@ var AppleNotesManager = class {
41164
41171
  end repeat
41165
41172
  return out
41166
41173
  `);
41167
- const res = executeAppleScript(script);
41174
+ const res = executeMutationAppleScript(script);
41168
41175
  if (!res.success) {
41169
41176
  for (const r of runnable) {
41170
41177
  results[r.index] = this.createBatchResult(
@@ -41274,7 +41281,7 @@ var AppleNotesManager = class {
41274
41281
  end repeat
41275
41282
  return out
41276
41283
  `);
41277
- const res = executeAppleScript(script);
41284
+ const res = executeMutationAppleScript(script);
41278
41285
  if (!res.success) {
41279
41286
  for (const r of runnable) {
41280
41287
  results[r.index] = this.createBatchResult(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.6.4",
3
+ "version": "2.6.5",
4
4
  "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",