apple-notes-mcp 2.15.6 → 2.15.8

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 (3) hide show
  1. package/README.md +65 -8
  2. package/build/index.js +126 -37
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -153,6 +153,30 @@ In the IDE, open **MCP Servers → Manage MCP Servers → View raw config**. Mer
153
153
 
154
154
  The repository's `.antigravity-plugin/mcp_config.json` supplies an unpinned stdio server entry; the JSON above pins the audited release. Use `command` and `args`; this package does not provide a remote `serverUrl`.
155
155
 
156
+ #### Gemini Spark (macOS Local MCP)
157
+
158
+ In Gemini macOS builds that offer **Local MCP**, open **Connected Apps**, click **+ Custom**, and select **Local MCP** in "Set up a custom connected app". Fill in the form with:
159
+
160
+ | Field | Value |
161
+ |-------|-------|
162
+ | Server Name | Apple Notes |
163
+ | Description | Read, search, create, edit, organize, and export Apple Notes on this Mac. |
164
+ | Command | `npx -y apple-notes-mcp@2.15.1` |
165
+
166
+ You can print these instructions from the installed server:
167
+
168
+ ```bash
169
+ npx -y apple-notes-mcp setup --gemini-spark
170
+ ```
171
+
172
+ The command only prints the guide. It exits before loading server configuration, checking Notes, or running the other setup commands, and it doesn't register the app or change permissions. Exit 0 means the guide was printed, not that Gemini connected. Run it by itself; combining it with other setup options exits 2.
173
+
174
+ Keep **Sandbox settings** at **Default** initially, then click **Next** and review the discovered tools and permission requests before connecting. If Gemini can't launch the server, inspect **Show Logs** and check whether `npx` is available to the app. For a stable local installation, `npm install -g apple-notes-mcp` provides the `apple-notes-mcp` command; you can also use absolute paths to your Node executable and the installed `build/index.js` if the app can't find them. Keep that installation available, and update the command if you move it or replace Node.
175
+
176
+ Gemini's sandbox and macOS permissions are separate. Review any additional filesystem access the launcher needs; **Full Access** isn't a required setup step. Automation of Notes.app enables the AppleScript tools, while database tools need the runtime's [Full Disk Access](#full-disk-access). Shortcut operations need the packaged bridge setup above. After connecting, ask Gemini to run `get-capabilities` for tool availability, then `doctor` for Notes access diagnostics before attempting a write. A successful terminal check doesn't verify the permissions of Gemini's process.
177
+
178
+ This integration uses the Mac app's Local MCP command interface and the server's existing stdio transport. Availability depends on your Gemini build. Google's [custom apps documentation](https://support.google.com/gemini/answer/17209137) currently describes URL-based setup in the web app; it doesn't document this local form. **Cloud MCP**, Gemini web/mobile, and a hosted HTTP endpoint aren't supported by this server. These instructions apply to Gemini Spark, separately from Gemini CLI and Antigravity.
179
+
156
180
  #### Other MCP clients and remote execution
157
181
 
158
182
  A local MCP client can use the command `npx` with arguments `-y` and `apple-notes-mcp@2.15.1`. Follow that client's stdio configuration format: JSON `mcpServers`, TOML, and YAML are not interchangeable.
@@ -1376,21 +1400,28 @@ actual destination folder ID matches the requested folder.
1376
1400
 
1377
1401
  #### Folder scope guards
1378
1402
 
1379
- `update-note`, `append-to-note`, `delete-note`, and `move-note` accept three
1380
- optional folder preconditions. Use exact folder ids from `list-folders`.
1403
+ `update-note`, `append-to-note`, `delete-note`, `move-note`, and
1404
+ `batch-move-notes` accept three optional folder preconditions. Use exact
1405
+ folder ids from `list-folders`.
1381
1406
 
1382
1407
  - `ifFolderId`: the note must currently be in exactly this folder.
1383
1408
  - `ifAncestorFolderId`: the note must be inside this folder or any of its
1384
1409
  subfolders.
1385
1410
  - `forbiddenAncestorFolderIds` (up to 50): the note must not be inside any of
1386
- these folders or their subfolders. For `move-note`, the destination must not
1387
- be either.
1411
+ these folders or their subfolders. For `move-note` and `batch-move-notes`,
1412
+ the destination must not be either.
1388
1413
 
1389
1414
  The checks read Notes.app's live folders inside the same AppleScript as the
1390
- write, immediately before it, so a note moved after you reviewed it is left
1391
- alone and the call fails with `Scope guard failed: …`. The one exception is a
1392
- native append to a protected note (it runs through Shortcuts): there the check
1393
- is a separate read just before the append, so it is not atomic.
1415
+ write, immediately before it. If the folder observed by the check fails a
1416
+ precondition, the write is refused with `Scope guard failed: …`. The check and
1417
+ write are separate Notes operations; another actor can move the note between
1418
+ them. A native append to a protected note runs through Shortcuts, so its
1419
+ precheck runs in a separate AppleScript before the append.
1420
+
1421
+ For `batch-move-notes`, the same preconditions apply to every supplied note ID.
1422
+ Each note is checked independently immediately before its move. A scope mismatch
1423
+ leaves that note unchanged and appears in its failure result; the remaining
1424
+ items continue. A batch can therefore move some notes and refuse others.
1394
1425
 
1395
1426
  **Example - retire a note only while it is still in the inbox:**
1396
1427
  ```json
@@ -1915,9 +1946,35 @@ Moves multiple notes to a folder.
1915
1946
  | `ids` | string[] | Yes | Array of note IDs to move (max 500 per request) |
1916
1947
  | `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`). Must already exist — create it with [`create-folder`](#create-folder). A smart folder is refused for the whole call before any note moves |
1917
1948
  | `account` | string | No | Account containing the folder |
1949
+ | `ifFolderId`, `ifAncestorFolderId`, `forbiddenAncestorFolderIds` | string, string, string[] | No | Shared folder preconditions applied independently to each note; see [Folder scope guards](#folder-scope-guards). Forbidden folders also apply to the destination |
1918
1950
 
1919
1951
  **Returns:** Summary of successes and failures. Each success is reported only
1920
1952
  after the note's actual container folder ID matches the destination folder ID.
1953
+ If every note fails, the call returns its existing error response; the exact
1954
+ note IDs and reasons remain in the failure text.
1955
+
1956
+ Failure rows carry `committed: false, indeterminate: false` only when that
1957
+ item was refused before its move. Move/readback failures and an unknown
1958
+ whole-script outcome carry `indeterminate: true`, with `committed` omitted.
1959
+ If every item fails, the batch reports `committed: false` only when every row
1960
+ is a proven pre-move refusal. Otherwise its error result is indeterminate;
1961
+ read each exact note ID before retrying, even if another row has a scope refusal.
1962
+
1963
+ **Example - move only notes that are still in the reviewed inbox:**
1964
+ ```json
1965
+ {
1966
+ "ids": ["x-coredata://ABC123/ICNote/p456", "x-coredata://ABC123/ICNote/p457"],
1967
+ "folder": "Archive",
1968
+ "ifFolderId": "x-coredata://ABC123/ICFolder/p12"
1969
+ }
1970
+ ```
1971
+
1972
+ The source-folder check runs immediately before each move in the same
1973
+ AppleScript. If the check finds that a note is no longer in that inbox, its
1974
+ result reports `Scope guard failed: …` while a note still there can move.
1975
+ The check does not lock the note against another actor moving it before the
1976
+ move. The batch is not a transaction, and successful moves are not rolled
1977
+ back after a refusal.
1921
1978
 
1922
1979
  ---
1923
1980
 
package/build/index.js CHANGED
@@ -24535,8 +24535,9 @@ Targets (choose at most one; default: packaged Shortcuts):
24535
24535
  --permissions-window Build and install the optional permission checklist window
24536
24536
  --permissions Check permissions; does not grant them
24537
24537
  --broker Build and install the opt-in permission broker
24538
+ --gemini-spark Print the macOS Local MCP guide; no setup effects
24538
24539
 
24539
- Options for every target:
24540
+ Options for installation and permission targets:
24540
24541
  --check Inspect only; do not install or import anything
24541
24542
  --help, -h Show this help without performing any checks
24542
24543
 
@@ -24547,6 +24548,9 @@ Options only with --permissions:
24547
24548
  --window Show the checklist in the installed optional window
24548
24549
  --probe-automation Send a read-only Apple event (may show a consent prompt)
24549
24550
 
24551
+ Options only with --gemini-spark:
24552
+ --help, -h Print the guide without connecting a client
24553
+
24550
24554
  Options only with --broker:
24551
24555
  --uninstall Remove the installed broker and LaunchAgent
24552
24556
  --sign-identity <id> Select an existing signing identity
@@ -24560,7 +24564,8 @@ var SETUP_TARGETS = /* @__PURE__ */ new Map([
24560
24564
  ["--native-helper", "native-helper"],
24561
24565
  ["--permissions-window", "permissions-window"],
24562
24566
  ["--permissions", "permissions"],
24563
- ["--broker", "broker"]
24567
+ ["--broker", "broker"],
24568
+ ["--gemini-spark", "gemini-spark"]
24564
24569
  ]);
24565
24570
  var PERMISSION_OPTIONS = /* @__PURE__ */ new Set([
24566
24571
  "--once",
@@ -24590,6 +24595,13 @@ function parseSetupArgs(args) {
24590
24595
  throw new Error(`Unknown setup option "${arg}".`);
24591
24596
  }
24592
24597
  }
24598
+ if (target === "gemini-spark") {
24599
+ if (args.filter((arg) => arg === "--gemini-spark").length !== 1 || args.some((arg) => !["--gemini-spark", "--help", "-h"].includes(arg)) || args.filter((arg) => arg === "--help" || arg === "-h").length > 1)
24600
+ throw new Error(
24601
+ "The Gemini Spark print-only guide cannot be combined with other setup options."
24602
+ );
24603
+ return { kind: "setup", target, args: [...args], checkOnly: false };
24604
+ }
24593
24605
  if (args.some((arg) => arg === "--uninstall" || arg === "--sign-identity") && target !== "broker")
24594
24606
  throw new Error("--uninstall and --sign-identity require setup --broker.");
24595
24607
  for (const arg of args) {
@@ -24611,6 +24623,33 @@ function parseCliArgs(args) {
24611
24623
  throw new Error(`Unknown command or option "${command2}".`);
24612
24624
  }
24613
24625
 
24626
+ // src/setupGeminiSpark.ts
24627
+ var GEMINI_SPARK_LOCAL_MCP = {
24628
+ serverName: "Apple Notes",
24629
+ description: "Read, search, create, edit, organize, and export Apple Notes on this Mac.",
24630
+ command: "npx -y apple-notes-mcp"
24631
+ };
24632
+ function formatGeminiSparkSetup() {
24633
+ return [
24634
+ "Gemini Spark on macOS: Local MCP",
24635
+ "Requires a Gemini macOS build with Local MCP in Connected Apps > + Custom.",
24636
+ "Select Local MCP and enter:",
24637
+ "",
24638
+ `Server Name: ${GEMINI_SPARK_LOCAL_MCP.serverName}`,
24639
+ `Description: ${GEMINI_SPARK_LOCAL_MCP.description}`,
24640
+ `Command: ${GEMINI_SPARK_LOCAL_MCP.command}`,
24641
+ "",
24642
+ "Keep Sandbox settings at Default initially. If startup fails, inspect Show Logs",
24643
+ "and review the specific launcher or filesystem access it needs.",
24644
+ "Sandbox access and macOS Automation / Full Disk Access are separate permissions.",
24645
+ "Click Next, review the tools and permissions, and complete the connection yourself.",
24646
+ "Verify tool discovery with get-capabilities in Gemini; use doctor for Notes diagnostics.",
24647
+ "",
24648
+ "This command only prints instructions; a successful exit does not verify a connection.",
24649
+ "Cloud MCP needs a hosted MCP URL. This server provides local stdio only."
24650
+ ].join("\n");
24651
+ }
24652
+
24614
24653
  // src/cliEntry.ts
24615
24654
  var parsed;
24616
24655
  try {
@@ -24623,6 +24662,11 @@ ${usage}`);
24623
24662
  process.exit(2);
24624
24663
  }
24625
24664
  var command = parsed;
24665
+ if (command.kind === "setup" && command.target === "gemini-spark") {
24666
+ process.stdout.write(`${formatGeminiSparkSetup()}
24667
+ `);
24668
+ process.exit(0);
24669
+ }
24626
24670
  if (command.kind === "help") {
24627
24671
  process.stdout.write(command.topic === "setup" ? SETUP_USAGE : CLI_USAGE);
24628
24672
  process.exit(0);
@@ -43149,8 +43193,9 @@ function parseScopeFailure(output) {
43149
43193
  const trimmed = output.trim();
43150
43194
  return trimmed.startsWith(`${SCOPE_MARKER}:`) ? trimmed.slice(SCOPE_MARKER.length + 1) : null;
43151
43195
  }
43152
- function scopeConflictMessage(reason) {
43153
- return `Scope guard failed: ${reason}. Nothing was changed; read the note's current folder and review before retrying.`;
43196
+ function scopeConflictMessage(reason, operation) {
43197
+ const refusal = operation === "batch-move" ? "This note's move was refused" : "Nothing was changed";
43198
+ return `Scope guard failed: ${reason}. ${refusal}; read the note's current folder and review before retrying.`;
43154
43199
  }
43155
43200
 
43156
43201
  // src/services/appleNotesManager.ts
@@ -43499,8 +43544,9 @@ function buildAccountScopedScript(scope2, command2) {
43499
43544
  end tell
43500
43545
  `;
43501
43546
  }
43502
- function buildAppLevelScript(command2) {
43547
+ function buildAppLevelScript(command2, handlers = "") {
43503
43548
  return `
43549
+ ${handlers}
43504
43550
  tell application "Notes"
43505
43551
  ${command2}
43506
43552
  end tell
@@ -45881,8 +45927,8 @@ var AppleNotesManager = class {
45881
45927
  /**
45882
45928
  * Result of a batch operation on a single item.
45883
45929
  */
45884
- createBatchResult(id2, success, error2) {
45885
- return error2 ? { id: id2, success, error: error2 } : { id: id2, success };
45930
+ createBatchResult(id2, success, error2, certainty) {
45931
+ return { ...error2 ? { id: id2, success, error: error2 } : { id: id2, success }, ...certainty };
45886
45932
  }
45887
45933
  /**
45888
45934
  * Maps a per-item status token emitted by a batch AppleScript loop to a
@@ -45890,23 +45936,39 @@ var AppleNotesManager = class {
45890
45936
  * per-note implementation. See {@link batchMoveNotes}.
45891
45937
  */
45892
45938
  mapBatchStatus(id2, status, op) {
45939
+ const beforeMove = op === "move" ? { committed: false, indeterminate: false } : void 0;
45940
+ const uncertainMove = op === "move" ? { indeterminate: true } : void 0;
45941
+ const scopeFailure = parseScopeFailure(status ?? "");
45942
+ if (scopeFailure !== null)
45943
+ return this.createBatchResult(
45944
+ id2,
45945
+ false,
45946
+ scopeConflictMessage(scopeFailure, op === "move" ? "batch-move" : void 0),
45947
+ beforeMove
45948
+ );
45893
45949
  switch (status) {
45894
45950
  case "ok":
45895
45951
  return this.createBatchResult(id2, true);
45896
45952
  case "pw":
45897
- return this.createBatchResult(id2, false, "Note is password-protected");
45953
+ return this.createBatchResult(id2, false, "Note is password-protected", beforeMove);
45898
45954
  case "missing":
45899
- return this.createBatchResult(id2, false, "Note not found");
45955
+ return this.createBatchResult(id2, false, "Note not found", beforeMove);
45900
45956
  case "fail":
45901
45957
  return this.createBatchResult(
45902
45958
  id2,
45903
45959
  false,
45904
- op === "delete" ? "Deletion failed" : "Move failed"
45960
+ op === "delete" ? "Deletion failed" : "Move failed",
45961
+ uncertainMove
45905
45962
  );
45906
45963
  case "wrongfolder":
45907
- return this.createBatchResult(id2, false, "Destination folder verification failed");
45964
+ return this.createBatchResult(
45965
+ id2,
45966
+ false,
45967
+ "Destination folder verification failed",
45968
+ uncertainMove
45969
+ );
45908
45970
  default:
45909
- return this.createBatchResult(id2, false, "Unknown error");
45971
+ return this.createBatchResult(id2, false, "Unknown error", uncertainMove);
45910
45972
  }
45911
45973
  }
45912
45974
  /**
@@ -45918,6 +45980,7 @@ var AppleNotesManager = class {
45918
45980
  * @param ids - Array of CoreData URL identifiers for notes to move
45919
45981
  * @param folder - Destination folder name
45920
45982
  * @param account - Account containing the folder (defaults to Notes.app's default account)
45983
+ * @param scope - Shared folder preconditions checked independently before each move
45921
45984
  * @returns Array of results with id, success status, and optional error message
45922
45985
  *
45923
45986
  * @example
@@ -45928,8 +45991,26 @@ var AppleNotesManager = class {
45928
45991
  * );
45929
45992
  * ```
45930
45993
  */
45931
- batchMoveNotes(ids, folder, account) {
45994
+ batchMoveNotes(ids, folder, account, scope2) {
45932
45995
  if (ids.length === 0) return [];
45996
+ const scopeChecks = buildScopeGuardScript("noteRef", scope2, "destFolder");
45997
+ const moveHandler = `
45998
+ script batchMoveWorker
45999
+ on moveBatchNote(noteRef, destFolder, theId)
46000
+ tell application "Notes"
46001
+ ${scopeChecks}
46002
+ move noteRef to destFolder
46003
+ set movedNoteRef to note id theId
46004
+ set actualFolder to container of movedNoteRef
46005
+ if (id of actualFolder) is (id of destFolder) then
46006
+ return "ok"
46007
+ else
46008
+ return "wrongfolder"
46009
+ end if
46010
+ end tell
46011
+ end moveBatchNote
46012
+ end script
46013
+ `;
45933
46014
  const targetAccount = this.resolveAccount(account);
45934
46015
  const destFolderSetup = buildLiveFolderResolution(folder, "destFolder", {
45935
46016
  excludeFolderIds: this.smartFolderIds()
@@ -45943,13 +46024,15 @@ var AppleNotesManager = class {
45943
46024
  results[i] = this.createBatchResult(
45944
46025
  id2,
45945
46026
  false,
45946
- e instanceof Error ? e.message : "Invalid note ID"
46027
+ e instanceof Error ? e.message : "Invalid note ID",
46028
+ { committed: false, indeterminate: false }
45947
46029
  );
45948
46030
  }
45949
46031
  });
45950
46032
  if (runnable.length > 0) {
45951
46033
  const idList2 = runnable.map((r) => `"${r.safe}"`).join(", ");
45952
- const script = buildAppLevelScript(`
46034
+ const script = buildAppLevelScript(
46035
+ `
45953
46036
  ${buildAccountResolution(targetAccount)}
45954
46037
  ${destFolderSetup}
45955
46038
  set out to ""
@@ -45970,14 +46053,8 @@ var AppleNotesManager = class {
45970
46053
  set out to out & "pw" & ${AS_RECORD_SEP}
45971
46054
  else
45972
46055
  try
45973
- move noteRef to destFolder
45974
- set movedNoteRef to note id theId
45975
- set actualFolder to container of movedNoteRef
45976
- if (id of actualFolder) is (id of destFolder) then
45977
- set out to out & "ok" & ${AS_RECORD_SEP}
45978
- else
45979
- set out to out & "wrongfolder" & ${AS_RECORD_SEP}
45980
- end if
46056
+ set itemStatus to batchMoveWorker's moveBatchNote(noteRef, destFolder, theId)
46057
+ set out to out & itemStatus & ${AS_RECORD_SEP}
45981
46058
  on error
45982
46059
  set out to out & "fail" & ${AS_RECORD_SEP}
45983
46060
  end try
@@ -45985,7 +46062,9 @@ var AppleNotesManager = class {
45985
46062
  end if
45986
46063
  end repeat
45987
46064
  return out
45988
- `);
46065
+ `,
46066
+ moveHandler
46067
+ );
45989
46068
  const res = executeMutationAppleScript(script);
45990
46069
  if (!res.success) {
45991
46070
  throwIfAccountResolutionFailed(res.error);
@@ -45994,11 +46073,12 @@ var AppleNotesManager = class {
45994
46073
  results[r.index] = this.createBatchResult(
45995
46074
  ids[r.index],
45996
46075
  false,
45997
- res.error ?? "Batch move failed"
46076
+ res.error ?? "Batch move failed",
46077
+ { indeterminate: true }
45998
46078
  );
45999
46079
  }
46000
46080
  } else {
46001
- const statuses = res.output.split(RECORD_SEP).map((s) => s.trim()).filter((s) => s.length > 0);
46081
+ const statuses = res.output.split(RECORD_SEP).map((s) => s.trim());
46002
46082
  runnable.forEach((r, k) => {
46003
46083
  results[r.index] = this.mapBatchStatus(ids[r.index], statuses[k], "move");
46004
46084
  });
@@ -64232,7 +64312,7 @@ var scopeGuardInputs = {
64232
64312
  maxItems: MAX_FORBIDDEN_FOLDERS
64233
64313
  }
64234
64314
  ).optional().describe(
64235
- "Precondition: the note must not be inside any of these folders or their subfolders (for move-note, neither may the destination). Re-checked immediately before the write."
64315
+ "Precondition: the note must not be inside any of these folders or their subfolders (for move-note and batch-move-notes, neither may the destination). Re-checked immediately before the write."
64236
64316
  )
64237
64317
  };
64238
64318
  function scopeFrom(args) {
@@ -66791,8 +66871,9 @@ registerTool(
66791
66871
  registerTool(
66792
66872
  "batch-move-notes",
66793
66873
  {
66794
- description: "Use when: moving multiple notes by id into one destination folder.\nReturns: per-id success/failure counts after destination-folder verification.\nDo not use when: moving a single note (move-note).\nSafety: each moved note's actual container ID is compared with the destination folder ID before success is reported. The destination folder must already exist (create-folder); a smart folder is refused for the whole call (code unsupported) before any note moves.",
66874
+ description: "Use when: moving multiple notes by id into one destination folder.\nReturns: per-id success/failure counts after destination-folder verification.\nDo not use when: moving a single note (move-note).\nSafety: optional folder preconditions apply independently to every note, checked live immediately before its move in the same AppleScript; a scope mismatch leaves that note unchanged and other items continue. Each moved note's actual container ID is compared with the destination folder ID before success is reported. The destination folder must already exist (create-folder); a smart folder is refused for the whole call (code unsupported) before any note moves.",
66795
66875
  inputSchema: {
66876
+ ...scopeGuardInputs,
66796
66877
  ids: noteIdArrayInput.describe(
66797
66878
  `Array of note IDs to move (max ${MAX.BATCH_IDS} per request)`
66798
66879
  ),
@@ -66811,11 +66892,12 @@ registerTool(
66811
66892
  results: external_exports.array(external_exports.object({}).passthrough()).optional()
66812
66893
  }
66813
66894
  },
66814
- withErrorHandling(({ ids, folder, account }) => {
66895
+ withErrorHandling((args) => {
66896
+ const { ids, folder, account } = args;
66815
66897
  if (ids.length === 0) {
66816
66898
  return errorResponse("No note IDs provided");
66817
66899
  }
66818
- const results = notesManager.batchMoveNotes(ids, folder, account);
66900
+ const results = notesManager.batchMoveNotes(ids, folder, account, scopeFrom(args));
66819
66901
  const succeeded = results.filter((r) => r.success).length;
66820
66902
  const failed = results.filter((r) => !r.success).length;
66821
66903
  const lines = [`Batch move to "${folder}": ${succeeded} succeeded, ${failed} failed`];
@@ -66825,13 +66907,20 @@ registerTool(
66825
66907
  lines.push(` - ${result.id}: ${result.error}`);
66826
66908
  }
66827
66909
  }
66828
- return succeeded > 0 ? successResponse(lines.join("\n"), {
66829
- ok: failed === 0,
66830
- folder,
66831
- succeeded,
66832
- failed,
66833
- results
66834
- }) : errorResponse(lines.join("\n"));
66910
+ if (succeeded > 0)
66911
+ return successResponse(lines.join("\n"), {
66912
+ ok: failed === 0,
66913
+ folder,
66914
+ succeeded,
66915
+ failed,
66916
+ results
66917
+ });
66918
+ const allRefusedBeforeMove = results.length === ids.length && results.every((result) => result.committed === false && result.indeterminate === false);
66919
+ if (!allRefusedBeforeMove)
66920
+ lines.push("\nBatch move outcome is uncertain; read each exact note ID before retrying.");
66921
+ const message = lines.join("\n");
66922
+ const envelope = allRefusedBeforeMove ? { ...classifyError(message), committed: false, indeterminate: false } : { code: "verification_failed", indeterminate: true };
66923
+ return errorResponse(message, new CodedError(message, envelope));
66835
66924
  }, "Error performing batch move")
66836
66925
  );
66837
66926
  registerTool(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.15.6",
3
+ "version": "2.15.8",
4
4
  "description": "MCP server for Apple Notes - read, search, create, edit, organize, and export notes on macOS via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",