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.
- package/README.md +65 -8
- package/build/index.js +126 -37
- 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`,
|
|
1380
|
-
optional folder preconditions. Use exact
|
|
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
|
|
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
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
45974
|
-
set
|
|
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())
|
|
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:
|
|
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((
|
|
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
|
-
|
|
66829
|
-
|
|
66830
|
-
|
|
66831
|
-
|
|
66832
|
-
|
|
66833
|
-
|
|
66834
|
-
|
|
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.
|
|
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",
|