apple-notes-mcp 2.7.0 → 2.7.1

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 +35 -15
  2. package/build/index.js +160 -58
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -181,7 +181,7 @@ Creates a new note in Apple Notes.
181
181
  | `content` | string | Yes | The body content of the note (do not repeat the title here) |
182
182
  | `tags` | string[] | No | Returned-only metadata — **NOT written to Notes.app**. Apple Notes tags can't be set via AppleScript, so values passed here are echoed back in the response but do not appear on the created note. Use inline `#hashtags` in `content` instead (Notes.app turns those into real tags) |
183
183
  | `folder` | string | No | Folder to create the note in. Supports nested paths like `"Work/Clients"`. **The folder must already exist** — create it first with [`create-folder`](#create-folder). Defaults to account root |
184
- | `account` | string | No | Account name (defaults to iCloud). Must be an account Notes.app already has configured — see [`list-accounts`](#list-accounts) |
184
+ | `account` | string | No | Account name (defaults to Notes.app's default account; matched exactly or by a *unique* prefix — an ambiguous prefix is refused). Must be an account Notes.app already has configured — see [`list-accounts`](#list-accounts) |
185
185
  | `format` | string | No | Content format: `"plaintext"` (default) or `"html"`. In both formats, the title is automatically prepended as `<h1>`. In plaintext mode, newlines become `<br>`, tabs become `<br>`, and backslashes are preserved as HTML entities |
186
186
 
187
187
  **Example (tagged with inline hashtags):**
@@ -224,7 +224,7 @@ Searches for notes by title or content.
224
224
  |-----------|------|----------|-------------|
225
225
  | `query` | string | Yes | Text to search for |
226
226
  | `searchContent` | boolean | No | If `true`, searches note body; if `false` (default), searches titles only |
227
- | `account` | string | No | Account to search in (defaults to iCloud) |
227
+ | `account` | string | No | Account to search in (defaults to Notes.app's default account; exact or unique-prefix match) |
228
228
  | `folder` | string | No | Limit search to a specific folder (supports nested paths like `"Work/Clients"`) |
229
229
  | `modifiedSince` | string | No | ISO 8601 date string to filter notes modified on or after this date (e.g., `"2025-01-01"`) |
230
230
  | `limit` | number | No | Maximum number of results to return. **Defaults to 50** — a broad query reads several properties per match via AppleScript (~200ms/note), so an unbounded search over hundreds of matches can exceed Notes' 30s timeout and return an error instead of results. Pass a higher value to see more; the applied limit (and whether it truncated the results) is disclosed in the response. |
@@ -266,7 +266,7 @@ Retrieves the full content of a specific note.
266
266
  |-----------|------|----------|-------------|
267
267
  | `id` | string | No | Note ID (preferred - more reliable than title) |
268
268
  | `title` | string | No | Note title (use `id` instead when available) |
269
- | `account` | string | No | Account containing the note (defaults to iCloud, ignored if `id` is provided) |
269
+ | `account` | string | No | Account containing the note (defaults to Notes.app's default account; exact or unique-prefix match, ignored if `id` is provided) |
270
270
 
271
271
  **Note:** Either `id` or `title` must be provided. Using `id` is recommended as it's unique and avoids issues with duplicate titles.
272
272
 
@@ -309,7 +309,7 @@ Retrieves a note's body as plain text, with no HTML markup.
309
309
  |-----------|------|----------|-------------|
310
310
  | `id` | string | No | Note ID (preferred - more reliable than title) |
311
311
  | `title` | string | No | Note title (use `id` instead when available) |
312
- | `account` | string | No | Account containing the note (defaults to iCloud, ignored if `id` is provided) |
312
+ | `account` | string | No | Account containing the note (defaults to Notes.app's default account; exact or unique-prefix match, ignored if `id` is provided) |
313
313
 
314
314
  **Note:** Either `id` or `title` must be provided. This reads the note's native `plaintext` property, so it skips the HTML-to-text conversion that `get-note-content` plus a Markdown pass would do. Use `get-note-content` when you need the HTML, or `get-note-markdown` when you want Markdown with checklist state.
315
315
 
@@ -324,7 +324,7 @@ Retrieves metadata about a note (without full content).
324
324
  | Parameter | Type | Required | Description |
325
325
  |-----------|------|----------|-------------|
326
326
  | `title` | string | Yes | Exact title of the note |
327
- | `account` | string | No | Account containing the note (defaults to iCloud) |
327
+ | `account` | string | No | Account containing the note (defaults to Notes.app's default account; exact or unique-prefix match) |
328
328
 
329
329
  **Example:**
330
330
  ```json
@@ -383,7 +383,7 @@ Updates an existing note's content and/or title.
383
383
  | `title` | string | No | Current title of the note to update (use `id` instead when available) |
384
384
  | `newTitle` | string | No | New title (if changing the title; ignored when `format` is `"html"`) |
385
385
  | `newContent` | string | Yes | New content for the note body |
386
- | `account` | string | No | Account containing the note (defaults to iCloud, ignored if `id` is provided) |
386
+ | `account` | string | No | Account containing the note (defaults to Notes.app's default account; exact or unique-prefix match, ignored if `id` is provided) |
387
387
  | `format` | string | No | Content format: `"plaintext"` (default) or `"html"`. When `"html"`, content replaces the entire note body as raw HTML and `newTitle` is ignored (the first HTML element serves as the title) |
388
388
 
389
389
  **Note:** Either `id` or `title` must be provided. Using `id` is recommended.
@@ -440,7 +440,7 @@ Deletes a note (moves to Recently Deleted in Notes.app).
440
440
  |-----------|------|----------|-------------|
441
441
  | `id` | string | No | Note ID (preferred - more reliable than title) |
442
442
  | `title` | string | No | Exact title of the note to delete (use `id` instead when available) |
443
- | `account` | string | No | Account containing the note (defaults to iCloud, ignored if `id` is provided) |
443
+ | `account` | string | No | Account containing the note (defaults to Notes.app's default account; exact or unique-prefix match, ignored if `id` is provided) |
444
444
 
445
445
  **Note:** Either `id` or `title` must be provided. Using `id` is recommended.
446
446
 
@@ -473,7 +473,7 @@ Moves a note to a different folder. The note is relocated in place via Notes.app
473
473
  | `id` | string | No | Note ID (preferred - more reliable than title) |
474
474
  | `title` | string | No | Title of the note to move (use `id` instead when available) |
475
475
  | `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`) |
476
- | `account` | string | No | Account containing the note (defaults to iCloud, ignored if `id` is provided) |
476
+ | `account` | string | No | Account containing the note (defaults to Notes.app's default account; exact or unique-prefix match, ignored if `id` is provided) |
477
477
 
478
478
  **Note:** Either `id` or `title` must be provided. Using `id` is recommended.
479
479
 
@@ -509,7 +509,7 @@ Appends or prepends content to an existing note without replacing it. Always rea
509
509
  | `position` | string | No | `"after"` (default) appends to the end; `"before"` prepends to the start |
510
510
  | `separator` | string | No | String placed between existing content and new content (default: two newlines → `<div><br></div>` in HTML) |
511
511
  | `format` | string | No | Format of the content being appended: `"plaintext"` (default) or `"html"` |
512
- | `account` | string | No | Account containing the note (defaults to iCloud, ignored if `id` is provided) |
512
+ | `account` | string | No | Account containing the note (defaults to Notes.app's default account; exact or unique-prefix match, ignored if `id` is provided) |
513
513
 
514
514
  **Note:** Either `id` or `title` must be provided. Using `id` is recommended.
515
515
 
@@ -545,7 +545,7 @@ Returns the `notes://showNote?identifier=<uuid>` deep-link URL for a note. The U
545
545
  |-----------|------|----------|-------------|
546
546
  | `id` | string | No | Note ID (preferred - more reliable than title) |
547
547
  | `title` | string | No | Note title (use `id` instead when available) |
548
- | `account` | string | No | Account containing the note (defaults to iCloud, ignored if `id` is provided) |
548
+ | `account` | string | No | Account containing the note (defaults to Notes.app's default account; exact or unique-prefix match, ignored if `id` is provided) |
549
549
 
550
550
  **Note:** Either `id` or `title` must be provided. Using `id` is recommended. Password-protected notes cannot be linked.
551
551
 
@@ -568,7 +568,7 @@ Lists all notes, optionally filtered by folder, date, and limit.
568
568
 
569
569
  | Parameter | Type | Required | Description |
570
570
  |-----------|------|----------|-------------|
571
- | `account` | string | No | Account to list notes from (defaults to iCloud) |
571
+ | `account` | string | No | Account to list notes from (defaults to Notes.app's default account; exact or unique-prefix match) |
572
572
  | `folder` | string | No | Filter to notes in this folder only (supports nested paths like `"Work/Clients"`) |
573
573
  | `modifiedSince` | string | No | ISO 8601 date string to filter notes modified on or after this date (e.g., `"2025-01-01"`) |
574
574
  | `limit` | number | No | Maximum number of notes to return |
@@ -619,7 +619,7 @@ Lists all folders in an account with full hierarchical paths.
619
619
 
620
620
  | Parameter | Type | Required | Description |
621
621
  |-----------|------|----------|-------------|
622
- | `account` | string | No | Account to list folders from (defaults to iCloud) |
622
+ | `account` | string | No | Account to list folders from (defaults to Notes.app's default account; exact or unique-prefix match) |
623
623
 
624
624
  **Example:**
625
625
  ```json
@@ -637,7 +637,7 @@ Creates a new folder, including a whole nested hierarchy in one call.
637
637
  | Parameter | Type | Required | Description |
638
638
  |-----------|------|----------|-------------|
639
639
  | `name` | string | Yes | Folder name, or a nested path separated by `/` (e.g. `"Retro Tech/PC/CPUs"`). Every intermediate folder is created; segments that already exist are skipped |
640
- | `account` | string | No | Account to create folder in (defaults to iCloud) |
640
+ | `account` | string | No | Account to create folder in (defaults to Notes.app's default account; exact or unique-prefix match) |
641
641
 
642
642
  **Example:**
643
643
  ```json
@@ -664,7 +664,7 @@ Deletes a folder.
664
664
  | Parameter | Type | Required | Description |
665
665
  |-----------|------|----------|-------------|
666
666
  | `name` | string | Yes | Name or path of the folder to delete (supports nested paths like `"Work/Old"`) |
667
- | `account` | string | No | Account containing the folder (defaults to iCloud) |
667
+ | `account` | string | No | Account containing the folder (defaults to Notes.app's default account; exact or unique-prefix match) |
668
668
 
669
669
  **Example:**
670
670
  ```json
@@ -963,7 +963,10 @@ AI: [calls get-note-content with title="Shopping List"]
963
963
 
964
964
  ### Working with Accounts
965
965
 
966
- By default, all operations use iCloud. To work with other accounts:
966
+ Omit `account` and an operation targets whichever account **Notes.app itself
967
+ reports as the default** — not a hardcoded "iCloud". That matters if your default
968
+ is a non-iCloud account, if the account name is localized, or if it carries a
969
+ trailing U+F8FF () character.
967
970
 
968
971
  ```
969
972
  User: "What accounts do I have?"
@@ -975,6 +978,23 @@ AI: [calls list-notes with account="Gmail"]
975
978
  "Your Gmail account has 5 notes..."
976
979
  ```
977
980
 
981
+ When you do pass `account`, it is resolved in this order:
982
+
983
+ 1. **Exact name match** wins outright.
984
+ 2. A **unique prefix match** resolves — `account="robert"` finds
985
+ `robert.b.sweet@gmail.com`.
986
+ 3. An **ambiguous prefix is refused**, with every candidate named:
987
+
988
+ ```
989
+ Account "rob" is ambiguous - it matches 2 accounts:
990
+ rob@superiortech.io, robert.b.sweet@gmail.com. Use the full account name.
991
+ ```
992
+
993
+ That third rule is deliberate. Silently taking the *first* prefix match would
994
+ make `delete-note` or `move-note` land in the wrong account and report success.
995
+ An unresolvable account is reported as such rather than as "note not found", so
996
+ you are not sent looking for the wrong problem.
997
+
978
998
  ### Organizing with Folders
979
999
 
980
1000
  ```
package/build/index.js CHANGED
@@ -39407,11 +39407,47 @@ function buildFolderReference(folderPath) {
39407
39407
  }
39408
39408
  return parts.reverse().map((part) => `folder "${escapePlainStringForAppleScript(part)}"`).join(" of ");
39409
39409
  }
39410
+ var AS_ACCOUNT_REF = "__acctRef";
39411
+ var ACCOUNT_RESOLUTION_ERROR = "AccountResolutionError:";
39412
+ function throwIfAccountResolutionFailed(error2) {
39413
+ if (!error2) return;
39414
+ const at = error2.indexOf(ACCOUNT_RESOLUTION_ERROR);
39415
+ if (at === -1) return;
39416
+ throw new Error(error2.slice(at + ACCOUNT_RESOLUTION_ERROR.length).trim());
39417
+ }
39418
+ function buildAccountResolution(account) {
39419
+ if (account === void 0) {
39420
+ return `set ${AS_ACCOUNT_REF} to default account`;
39421
+ }
39422
+ const safeAccount = sanitizeAccountName(account);
39423
+ return `
39424
+ set ${AS_ACCOUNT_REF} to missing value
39425
+ set _exactMatches to (every account whose name is "${safeAccount}")
39426
+ if (count of _exactMatches) is 1 then
39427
+ set ${AS_ACCOUNT_REF} to item 1 of _exactMatches
39428
+ else
39429
+ set _prefixMatches to (every account whose name starts with "${safeAccount}")
39430
+ if (count of _prefixMatches) is 1 then
39431
+ set ${AS_ACCOUNT_REF} to item 1 of _prefixMatches
39432
+ else if (count of _prefixMatches) > 1 then
39433
+ set _candidateNames to {}
39434
+ repeat with _candidate in _prefixMatches
39435
+ set end of _candidateNames to name of (contents of _candidate)
39436
+ end repeat
39437
+ set AppleScript's text item delimiters to ", "
39438
+ set _joinedNames to _candidateNames as text
39439
+ set AppleScript's text item delimiters to ""
39440
+ error "${ACCOUNT_RESOLUTION_ERROR} Account \\"${safeAccount}\\" is ambiguous - it matches " & (count of _prefixMatches) & " accounts: " & _joinedNames & ". Use the full account name."
39441
+ end if
39442
+ end if
39443
+ if ${AS_ACCOUNT_REF} is missing value then error "${ACCOUNT_RESOLUTION_ERROR} Account \\"${safeAccount}\\" not found"
39444
+ `;
39445
+ }
39410
39446
  function buildAccountScopedScript(scope, command) {
39411
- const safeAccount = sanitizeAccountName(scope.account);
39412
39447
  return `
39413
39448
  tell application "Notes"
39414
- tell account "${safeAccount}"
39449
+ ${buildAccountResolution(scope.account)}
39450
+ tell ${AS_ACCOUNT_REF}
39415
39451
  ${command}
39416
39452
  end tell
39417
39453
  end tell
@@ -39452,16 +39488,48 @@ function extractCoreDataId(output, prefix) {
39452
39488
  }
39453
39489
  var AppleNotesManager = class {
39454
39490
  /**
39455
- * Default account used when no account is specified.
39456
- * iCloud is the primary account for most Apple Notes users.
39491
+ * Normalizes a caller-supplied account name for the script builders.
39492
+ *
39493
+ * Returns `undefined` when no account was requested, which
39494
+ * {@link buildAccountResolution} turns into Notes.app's own
39495
+ * `default account`. It deliberately no longer substitutes a hardcoded
39496
+ * `"iCloud"` — that name is wrong for anyone whose default account differs,
39497
+ * is localized, or carries a U+F8FF suffix (#128).
39457
39498
  */
39458
- defaultAccount = "iCloud";
39499
+ resolveAccount(account) {
39500
+ const trimmed = account?.trim();
39501
+ return trimmed ? trimmed : void 0;
39502
+ }
39459
39503
  /**
39460
- * Resolves the account to use for an operation.
39461
- * Falls back to default if not specified.
39504
+ * Cached name of Notes.app's default account, for payload reporting only.
39505
+ * `null` = not yet queried; `""` = queried and unavailable (don't retry).
39462
39506
  */
39463
- resolveAccount(account) {
39464
- return account || this.defaultAccount;
39507
+ defaultAccountName = null;
39508
+ /**
39509
+ * The account name to report back in a returned payload.
39510
+ *
39511
+ * Returns the caller's account verbatim when they named one. When they did
39512
+ * not, it reports the *real* default account rather than the hardcoded
39513
+ * "iCloud" the payloads used to claim (#128).
39514
+ *
39515
+ * Most scripts resolve the account inline via {@link buildAccountResolution}
39516
+ * and cost nothing extra; this exists for the handful of methods whose script
39517
+ * output can't carry the name back — notably `createNote`, whose nested-folder
39518
+ * branch depends on the implicit result of `make new note` and must not have
39519
+ * its return value extended (the -1728 workaround above).
39520
+ *
39521
+ * The lookup runs at most once per manager instance and is cached, including
39522
+ * the failure case, so a Notes.app that can't answer is not re-asked on every
39523
+ * call. Returns `undefined` when unknown — `account` is an optional field, and
39524
+ * omitting it is honest where guessing was not.
39525
+ */
39526
+ reportedAccount(targetAccount) {
39527
+ if (targetAccount !== void 0) return targetAccount;
39528
+ if (this.defaultAccountName === null) {
39529
+ const result = executeAppleScript(buildAppLevelScript("return name of default account"));
39530
+ this.defaultAccountName = result.success ? result.output.trim() : "";
39531
+ }
39532
+ return this.defaultAccountName || void 0;
39465
39533
  }
39466
39534
  /**
39467
39535
  * Checks if a note is password-protected by its ID.
@@ -39481,7 +39549,7 @@ var AppleNotesManager = class {
39481
39549
  * Checks if a note is password-protected by its title.
39482
39550
  *
39483
39551
  * @param title - Exact title of the note
39484
- * @param account - Account to search in (defaults to iCloud)
39552
+ * @param account - Account to search in (defaults to Notes.app's default account)
39485
39553
  * @returns true if the note is password-protected, false otherwise
39486
39554
  */
39487
39555
  isNotePasswordProtected(title, account) {
@@ -39502,7 +39570,7 @@ var AppleNotesManager = class {
39502
39570
  * @param content - Body content (plain text that will be HTML-escaped, or raw HTML when format is "html")
39503
39571
  * @param tags - Optional tags (stored in returned object, not used by Notes.app)
39504
39572
  * @param folder - Optional folder name to create the note in
39505
- * @param account - Account to use (defaults to iCloud)
39573
+ * @param account - Account to use (defaults to Notes.app's default account)
39506
39574
  * @param format - Content format: "plaintext" escapes and wraps in div tags (default), "html" uses content as-is
39507
39575
  * @returns Created Note object with metadata, or null on failure
39508
39576
  *
@@ -39542,6 +39610,7 @@ var AppleNotesManager = class {
39542
39610
  const script = buildAccountScopedScript({ account: targetAccount }, createCommand);
39543
39611
  const result = executeMutationAppleScript(script);
39544
39612
  if (!result.success) {
39613
+ throwIfAccountResolutionFailed(result.error);
39545
39614
  console.error(`Failed to create note "${title}":`, result.error);
39546
39615
  return null;
39547
39616
  }
@@ -39557,7 +39626,7 @@ var AppleNotesManager = class {
39557
39626
  created: now,
39558
39627
  modified: now,
39559
39628
  folder,
39560
- account: targetAccount
39629
+ account: this.reportedAccount(targetAccount)
39561
39630
  };
39562
39631
  }
39563
39632
  /**
@@ -39568,7 +39637,7 @@ var AppleNotesManager = class {
39568
39637
  *
39569
39638
  * @param query - Text to search for
39570
39639
  * @param searchContent - If true, search note bodies; if false, search titles
39571
- * @param account - Account to search in (defaults to iCloud)
39640
+ * @param account - Account to search in (defaults to Notes.app's default account)
39572
39641
  * @param folder - Optional folder to limit search to
39573
39642
  * @param modifiedSince - Optional ISO 8601 date string to filter notes modified on or after this date
39574
39643
  * @param limit - Optional maximum number of results to return (default: no limit)
@@ -39660,6 +39729,7 @@ var AppleNotesManager = class {
39660
39729
  const items = result.output.split(RECORD_SEP);
39661
39730
  const notes = [];
39662
39731
  const seenIds = /* @__PURE__ */ new Set();
39732
+ const reportedAccount = this.reportedAccount(targetAccount);
39663
39733
  for (const item of items) {
39664
39734
  const [title, id, folder2, created, modified] = item.split(FIELD_SEP);
39665
39735
  if (!title?.trim()) continue;
@@ -39675,7 +39745,7 @@ var AppleNotesManager = class {
39675
39745
  created: parseAppleScriptDate(created ?? ""),
39676
39746
  modified: parseAppleScriptDate(modified ?? ""),
39677
39747
  folder: folder2?.trim(),
39678
- account: targetAccount
39748
+ account: reportedAccount
39679
39749
  });
39680
39750
  }
39681
39751
  return notes;
@@ -39688,7 +39758,7 @@ var AppleNotesManager = class {
39688
39758
  * getNoteDetails() or isNotePasswordProtected().
39689
39759
  *
39690
39760
  * @param title - Exact title of the note
39691
- * @param account - Account to search in (defaults to iCloud)
39761
+ * @param account - Account to search in (defaults to Notes.app's default account)
39692
39762
  * @returns HTML content of the note, or empty string if not found
39693
39763
  *
39694
39764
  * @example
@@ -39706,6 +39776,7 @@ var AppleNotesManager = class {
39706
39776
  const script = buildAccountScopedScript({ account: targetAccount }, getCommand);
39707
39777
  const result = executeAppleScript(script);
39708
39778
  if (!result.success) {
39779
+ throwIfAccountResolutionFailed(result.error);
39709
39780
  console.error(`Failed to get content of note "${title}":`, result.error);
39710
39781
  return "";
39711
39782
  }
@@ -39744,7 +39815,7 @@ var AppleNotesManager = class {
39744
39815
  * round-trip entirely.
39745
39816
  *
39746
39817
  * @param title - Exact title of the note
39747
- * @param account - Account to search in (defaults to iCloud)
39818
+ * @param account - Account to search in (defaults to Notes.app's default account)
39748
39819
  * @returns Plain-text content of the note, or empty string if not found
39749
39820
  */
39750
39821
  getNotePlaintext(title, account) {
@@ -39754,6 +39825,7 @@ var AppleNotesManager = class {
39754
39825
  const script = buildAccountScopedScript({ account: targetAccount }, getCommand);
39755
39826
  const result = executeAppleScript(script);
39756
39827
  if (!result.success) {
39828
+ throwIfAccountResolutionFailed(result.error);
39757
39829
  console.error(`Failed to get plaintext of note "${title}":`, result.error);
39758
39830
  return "";
39759
39831
  }
@@ -39832,7 +39904,7 @@ var AppleNotesManager = class {
39832
39904
  * including creation date, modification date, and sharing status.
39833
39905
  *
39834
39906
  * @param title - Exact title of the note
39835
- * @param account - Account to search in (defaults to iCloud)
39907
+ * @param account - Account to search in (defaults to Notes.app's default account)
39836
39908
  * @returns Note object with full metadata, or null if not found
39837
39909
  */
39838
39910
  getNoteDetails(title, account) {
@@ -39849,6 +39921,7 @@ var AppleNotesManager = class {
39849
39921
  const script = buildAccountScopedScript({ account: targetAccount }, getCommand);
39850
39922
  const result = executeAppleScript(script);
39851
39923
  if (!result.success) {
39924
+ throwIfAccountResolutionFailed(result.error);
39852
39925
  console.error(`Failed to get details for note "${title}":`, result.error);
39853
39926
  return null;
39854
39927
  }
@@ -39866,7 +39939,7 @@ var AppleNotesManager = class {
39866
39939
  modified: parsed.modified,
39867
39940
  shared: parsed.shared,
39868
39941
  passwordProtected: parsed.passwordProtected,
39869
- account: targetAccount
39942
+ account: this.reportedAccount(targetAccount)
39870
39943
  };
39871
39944
  }
39872
39945
  /**
@@ -39876,7 +39949,7 @@ var AppleNotesManager = class {
39876
39949
  * from the "Recently Deleted" folder in Notes.app.
39877
39950
  *
39878
39951
  * @param title - Exact title of the note to delete
39879
- * @param account - Account containing the note (defaults to iCloud)
39952
+ * @param account - Account containing the note (defaults to Notes.app's default account)
39880
39953
  * @returns true if deletion succeeded, false otherwise
39881
39954
  */
39882
39955
  deleteNote(title, account) {
@@ -39886,6 +39959,7 @@ var AppleNotesManager = class {
39886
39959
  const script = buildAccountScopedScript({ account: targetAccount }, deleteCommand);
39887
39960
  const result = executeMutationAppleScript(script);
39888
39961
  if (!result.success) {
39962
+ throwIfAccountResolutionFailed(result.error);
39889
39963
  console.error(`Failed to delete note "${title}":`, result.error);
39890
39964
  return false;
39891
39965
  }
@@ -39928,7 +40002,7 @@ var AppleNotesManager = class {
39928
40002
  * @param title - Current title of the note to update
39929
40003
  * @param newTitle - New title (optional, keeps existing if not provided; ignored in html format)
39930
40004
  * @param newContent - New content for the note body
39931
- * @param account - Account containing the note (defaults to iCloud)
40005
+ * @param account - Account containing the note (defaults to Notes.app's default account)
39932
40006
  * @param format - Content format: "plaintext" wraps in div tags (default), "html" uses content as-is
39933
40007
  * @returns true if update succeeded, false otherwise
39934
40008
  */
@@ -39950,6 +40024,7 @@ var AppleNotesManager = class {
39950
40024
  const script = buildAccountScopedScript({ account: targetAccount }, updateCommand);
39951
40025
  const result = executeMutationAppleScript(script);
39952
40026
  if (!result.success) {
40027
+ throwIfAccountResolutionFailed(result.error);
39953
40028
  console.error(`Failed to update note "${title}":`, result.error);
39954
40029
  return false;
39955
40030
  }
@@ -40147,7 +40222,7 @@ var AppleNotesManager = class {
40147
40222
  /**
40148
40223
  * Lists all notes in an account, optionally filtered by folder, date, and limit.
40149
40224
  *
40150
- * @param account - Account to list notes from (defaults to iCloud)
40225
+ * @param account - Account to list notes from (defaults to Notes.app's default account)
40151
40226
  * @param folder - Optional folder to filter by
40152
40227
  * @param modifiedSince - Optional ISO 8601 date string to filter notes modified on or after this date
40153
40228
  * @param limit - Optional maximum number of results to return (default: no limit)
@@ -40248,12 +40323,13 @@ var AppleNotesManager = class {
40248
40323
  * so that duplicate folder names (e.g., multiple "Archive" folders) are
40249
40324
  * distinguishable and can be used directly in other operations.
40250
40325
  *
40251
- * @param account - Account to list folders from (defaults to iCloud)
40326
+ * @param account - Account to list folders from (defaults to Notes.app's default account)
40252
40327
  * @returns Array of Folder objects with path-based names
40253
40328
  */
40254
40329
  listFolders(account) {
40255
40330
  const targetAccount = this.resolveAccount(account);
40256
40331
  const listCommand = `
40332
+ set acctName to name of it
40257
40333
  set folderList to {}
40258
40334
  set allFolders to every folder
40259
40335
  repeat with f in allFolders
@@ -40264,7 +40340,7 @@ var AppleNotesManager = class {
40264
40340
  set parentId to id of cRef
40265
40341
  end if
40266
40342
  set sharedFlag to shared of fRef as text
40267
- set end of folderList to (id of fRef) & ${AS_FIELD_SEP} & (name of fRef) & ${AS_FIELD_SEP} & parentId & ${AS_FIELD_SEP} & sharedFlag
40343
+ set end of folderList to (id of fRef) & ${AS_FIELD_SEP} & (name of fRef) & ${AS_FIELD_SEP} & parentId & ${AS_FIELD_SEP} & sharedFlag & ${AS_FIELD_SEP} & acctName
40268
40344
  end repeat
40269
40345
  set AppleScript's text item delimiters to ${AS_RECORD_SEP}
40270
40346
  return folderList as text
@@ -40284,7 +40360,11 @@ var AppleNotesManager = class {
40284
40360
  id: (parts[0] || "").trim(),
40285
40361
  name: (parts[1] || "").trim(),
40286
40362
  parentId: (parts[2] || "").trim(),
40287
- shared: (parts[3] || "").trim().toLowerCase() === "true"
40363
+ shared: (parts[3] || "").trim().toLowerCase() === "true",
40364
+ // The account name as Notes.app actually reports it. When the caller
40365
+ // omitted `account` this is the resolved default, which is the whole
40366
+ // point — reporting a hardcoded "iCloud" here was the #128 bug.
40367
+ account: (parts[4] || "").trim()
40288
40368
  };
40289
40369
  });
40290
40370
  const byId = new Map(entries.map((e) => [e.id, e]));
@@ -40300,7 +40380,7 @@ var AppleNotesManager = class {
40300
40380
  return entries.map((entry) => ({
40301
40381
  id: entry.id,
40302
40382
  name: buildPath(entry),
40303
- account: targetAccount,
40383
+ account: entry.account || targetAccount || "",
40304
40384
  shared: entry.shared
40305
40385
  }));
40306
40386
  }
@@ -40308,7 +40388,7 @@ var AppleNotesManager = class {
40308
40388
  * Creates a new folder in an account.
40309
40389
  *
40310
40390
  * @param name - Name for the new folder
40311
- * @param account - Account to create folder in (defaults to iCloud)
40391
+ * @param account - Account to create folder in (defaults to Notes.app's default account)
40312
40392
  * @returns Created Folder object, or null on failure
40313
40393
  */
40314
40394
  createFolder(name, account) {
@@ -40341,6 +40421,7 @@ var AppleNotesManager = class {
40341
40421
  const script = buildAccountScopedScript({ account: targetAccount }, createCommand);
40342
40422
  const result = executeMutationAppleScript(script);
40343
40423
  if (!result.success) {
40424
+ throwIfAccountResolutionFailed(result.error);
40344
40425
  console.error(`Failed to create folder "${name}":`, result.error);
40345
40426
  return null;
40346
40427
  }
@@ -40348,14 +40429,15 @@ var AppleNotesManager = class {
40348
40429
  const fullRef = buildFolderReference(name);
40349
40430
  const idScript = buildAccountScopedScript(
40350
40431
  { account: targetAccount },
40351
- `return id of ${fullRef}`
40432
+ `return (id of ${fullRef}) & ${AS_FIELD_SEP} & (name of it)`
40352
40433
  );
40353
40434
  const idResult = executeAppleScript(idScript);
40354
- const folderId = idResult.success ? extractCoreDataId(idResult.output, "folder") : "";
40435
+ const [rawId = "", rawAccount = ""] = idResult.success ? idResult.output.split(FIELD_SEP) : [];
40436
+ const folderId = idResult.success ? extractCoreDataId(rawId, "folder") : "";
40355
40437
  return {
40356
40438
  id: folderId,
40357
40439
  name,
40358
- account: targetAccount
40440
+ account: rawAccount.trim() || targetAccount || ""
40359
40441
  };
40360
40442
  }
40361
40443
  /**
@@ -40364,7 +40446,7 @@ var AppleNotesManager = class {
40364
40446
  * Note: This may fail if the folder contains notes.
40365
40447
  *
40366
40448
  * @param name - Name of the folder to delete
40367
- * @param account - Account containing the folder (defaults to iCloud)
40449
+ * @param account - Account containing the folder (defaults to Notes.app's default account)
40368
40450
  * @returns true if deletion succeeded, false otherwise
40369
40451
  */
40370
40452
  deleteFolder(name, account) {
@@ -40373,6 +40455,7 @@ var AppleNotesManager = class {
40373
40455
  const script = buildAccountScopedScript({ account: targetAccount }, deleteCommand);
40374
40456
  const result = executeMutationAppleScript(script);
40375
40457
  if (!result.success) {
40458
+ throwIfAccountResolutionFailed(result.error);
40376
40459
  console.error(`Failed to delete folder "${name}":`, result.error);
40377
40460
  return false;
40378
40461
  }
@@ -40392,7 +40475,7 @@ var AppleNotesManager = class {
40392
40475
  *
40393
40476
  * @param title - Title of the note to move
40394
40477
  * @param destinationFolder - Name of the folder to move to (must already exist)
40395
- * @param account - Account containing the note (defaults to iCloud)
40478
+ * @param account - Account containing the note (defaults to Notes.app's default account)
40396
40479
  * @returns true if the move succeeded, false otherwise
40397
40480
  */
40398
40481
  moveNote(title, destinationFolder, account) {
@@ -40414,15 +40497,15 @@ var AppleNotesManager = class {
40414
40497
  *
40415
40498
  * @param id - CoreData URL identifier for the note
40416
40499
  * @param destinationFolder - Name of the folder to move to (must already exist)
40417
- * @param account - Account containing the destination folder (defaults to iCloud)
40500
+ * @param account - Account containing the destination folder (defaults to Notes.app's default account)
40418
40501
  * @returns true if the move succeeded, false otherwise
40419
40502
  */
40420
40503
  moveNoteById(id, destinationFolder, account) {
40421
40504
  const targetAccount = this.resolveAccount(account);
40422
40505
  const safeId = sanitizeId(id);
40423
- const safeAccount = sanitizeAccountName(targetAccount);
40424
- const destFolderRef = `${buildFolderReference(destinationFolder)} of account "${safeAccount}"`;
40506
+ const destFolderRef = `${buildFolderReference(destinationFolder)} of ${AS_ACCOUNT_REF}`;
40425
40507
  const moveCommand = `
40508
+ ${buildAccountResolution(targetAccount)}
40426
40509
  set destFolder to ${destFolderRef}
40427
40510
  set noteRef to note id "${safeId}"
40428
40511
  move noteRef to destFolder
@@ -40430,6 +40513,7 @@ var AppleNotesManager = class {
40430
40513
  const script = buildAppLevelScript(moveCommand);
40431
40514
  const result = executeMutationAppleScript(script);
40432
40515
  if (!result.success) {
40516
+ throwIfAccountResolutionFailed(result.error);
40433
40517
  console.error(
40434
40518
  `Cannot move note to "${destinationFolder}" (folder may not exist):`,
40435
40519
  result.error
@@ -40625,7 +40709,7 @@ var AppleNotesManager = class {
40625
40709
  * Returns the notes:// deep-link URL for a note by title.
40626
40710
  *
40627
40711
  * @param title - Exact note title
40628
- * @param account - Account to search in (defaults to iCloud)
40712
+ * @param account - Account to search in (defaults to Notes.app's default account)
40629
40713
  * @returns notes://showNote?identifier=<uuid> string, or null on failure
40630
40714
  */
40631
40715
  getNoteLink(title, account) {
@@ -40799,12 +40883,11 @@ var AppleNotesManager = class {
40799
40883
  });
40800
40884
  return { healthy: false, checks };
40801
40885
  }
40802
- const defaultAccount = accounts[0]?.name || "iCloud";
40803
- const notes = this.listNotes(defaultAccount);
40886
+ const notes = this.listNotes();
40804
40887
  checks.push({
40805
40888
  name: "operations",
40806
40889
  passed: true,
40807
- message: `Basic operations working (${notes.length} note(s) in ${defaultAccount})`
40890
+ message: `Basic operations working (${notes.length} note(s) in the default account)`
40808
40891
  });
40809
40892
  const allPassed = checks.every((c) => c.passed);
40810
40893
  return { healthy: allPassed, checks };
@@ -41026,18 +41109,18 @@ var AppleNotesManager = class {
41026
41109
  * Lists attachments for a note by its title.
41027
41110
  *
41028
41111
  * @param title - Title of the note
41029
- * @param account - Account containing the note (defaults to iCloud)
41112
+ * @param account - Account containing the note (defaults to Notes.app's default account)
41030
41113
  * @returns Array of Attachment objects, or empty array if the note genuinely has none
41031
41114
  * @throws If the AppleScript call fails, so a lookup failure is never mistaken for
41032
41115
  * an attachment-free note (callers gate destructive full-body updates on this)
41033
41116
  */
41034
41117
  listAttachments(title, account) {
41035
41118
  const targetAccount = this.resolveAccount(account);
41036
- const safeAccount = escapePlainStringForAppleScript(targetAccount);
41037
41119
  const safeTitle = escapePlainStringForAppleScript(title);
41038
41120
  const script = `
41039
41121
  tell application "Notes"
41040
- tell account "${safeAccount}"
41122
+ ${buildAccountResolution(targetAccount)}
41123
+ tell ${AS_ACCOUNT_REF}
41041
41124
  set theNote to note "${safeTitle}"
41042
41125
  set attachmentList to {}
41043
41126
  set attachmentIds to id of every attachment of theNote
@@ -41337,7 +41420,7 @@ var AppleNotesManager = class {
41337
41420
  *
41338
41421
  * @param ids - Array of CoreData URL identifiers for notes to move
41339
41422
  * @param folder - Destination folder name
41340
- * @param account - Account containing the folder (defaults to iCloud)
41423
+ * @param account - Account containing the folder (defaults to Notes.app's default account)
41341
41424
  * @returns Array of results with id, success status, and optional error message
41342
41425
  *
41343
41426
  * @example
@@ -41351,8 +41434,7 @@ var AppleNotesManager = class {
41351
41434
  batchMoveNotes(ids, folder, account) {
41352
41435
  if (ids.length === 0) return [];
41353
41436
  const targetAccount = this.resolveAccount(account);
41354
- const safeAccount = sanitizeAccountName(targetAccount);
41355
- const destFolderRef = `${buildFolderReference(folder)} of account "${safeAccount}"`;
41437
+ const destFolderRef = `${buildFolderReference(folder)} of ${AS_ACCOUNT_REF}`;
41356
41438
  const results = new Array(ids.length);
41357
41439
  const runnable = [];
41358
41440
  ids.forEach((id, i) => {
@@ -41369,6 +41451,7 @@ var AppleNotesManager = class {
41369
41451
  if (runnable.length > 0) {
41370
41452
  const idList = runnable.map((r) => `"${r.safe}"`).join(", ");
41371
41453
  const script = buildAppLevelScript(`
41454
+ ${buildAccountResolution(targetAccount)}
41372
41455
  set destFolder to ${destFolderRef}
41373
41456
  set out to ""
41374
41457
  repeat with rawId in {${idList}}
@@ -41400,6 +41483,7 @@ var AppleNotesManager = class {
41400
41483
  `);
41401
41484
  const res = executeMutationAppleScript(script);
41402
41485
  if (!res.success) {
41486
+ throwIfAccountResolutionFailed(res.error);
41403
41487
  for (const r of runnable) {
41404
41488
  results[r.index] = this.createBatchResult(
41405
41489
  ids[r.index],
@@ -41580,7 +41664,7 @@ var AppleNotesManager = class {
41580
41664
  * [x] (done) or [ ] (undone) prefixes.
41581
41665
  *
41582
41666
  * @param title - Exact title of the note
41583
- * @param account - Account containing the note (defaults to iCloud)
41667
+ * @param account - Account containing the note (defaults to Notes.app's default account)
41584
41668
  * @returns Markdown content, or empty string if not found
41585
41669
  *
41586
41670
  * @example
@@ -42171,11 +42255,15 @@ var MAX = {
42171
42255
  };
42172
42256
  var noteTitleSchema = {
42173
42257
  title: external_exports.string().min(1, "Note title is required").max(MAX.TITLE),
42174
- account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account name (defaults to iCloud)")
42258
+ account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
42259
+ "Account name (defaults to Notes.app's default account; exact or unique-prefix match)"
42260
+ )
42175
42261
  };
42176
42262
  var folderNameSchema = {
42177
42263
  name: external_exports.string().min(1, "Folder name is required").max(MAX.FOLDER),
42178
- account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account name (defaults to iCloud)")
42264
+ account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
42265
+ "Account name (defaults to Notes.app's default account; exact or unique-prefix match)"
42266
+ )
42179
42267
  };
42180
42268
  function registerTool(name, config2, cb) {
42181
42269
  const { outputSchema, ...rest } = config2;
@@ -42202,7 +42290,7 @@ registerTool(
42202
42290
  "Folder to create the note in (supports nested paths like 'Work/Clients'). The folder must already exist \u2014 this tool does not create it; call create-folder first, which is idempotent and creates intermediate segments."
42203
42291
  ),
42204
42292
  account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
42205
- "Account name (defaults to iCloud). Must be an account Notes.app already has configured \u2014 see list-accounts."
42293
+ "Account name (defaults to the account Notes.app itself reports as default). Matched exactly, or by a unique prefix; an ambiguous prefix is refused. Must be an account Notes.app already has configured \u2014 see list-accounts."
42206
42294
  )
42207
42295
  },
42208
42296
  outputSchema: {
@@ -42311,7 +42399,9 @@ registerTool(
42311
42399
  inputSchema: {
42312
42400
  id: external_exports.string().max(MAX.ID).optional().describe("Note ID (preferred - more reliable than title)"),
42313
42401
  title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
42314
- account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account name (defaults to iCloud, ignored if id is provided)")
42402
+ account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
42403
+ "Account name (defaults to Notes.app's default account; exact or unique-prefix match, ignored if id is provided)"
42404
+ )
42315
42405
  },
42316
42406
  outputSchema: {
42317
42407
  title: external_exports.string().optional(),
@@ -42386,7 +42476,9 @@ registerTool(
42386
42476
  inputSchema: {
42387
42477
  id: external_exports.string().max(MAX.ID).optional().describe("Note ID (preferred - more reliable than title)"),
42388
42478
  title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
42389
- account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account name (defaults to iCloud, ignored if id is provided)")
42479
+ account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
42480
+ "Account name (defaults to Notes.app's default account; exact or unique-prefix match, ignored if id is provided)"
42481
+ )
42390
42482
  },
42391
42483
  outputSchema: {
42392
42484
  title: external_exports.string().optional(),
@@ -42809,7 +42901,9 @@ registerTool(
42809
42901
  inputSchema: {
42810
42902
  id: external_exports.string().max(MAX.ID).optional().describe("Note ID (preferred - more reliable than title)"),
42811
42903
  title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
42812
- account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account name (defaults to iCloud, ignored if id is provided)")
42904
+ account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
42905
+ "Account name (defaults to Notes.app's default account; exact or unique-prefix match, ignored if id is provided)"
42906
+ )
42813
42907
  },
42814
42908
  outputSchema: {
42815
42909
  ok: external_exports.boolean().optional(),
@@ -43024,12 +43118,16 @@ ${syncWarnings.join(" ")}` : "";
43024
43118
  if (folders.length === 0) {
43025
43119
  return successResponse(`No folders found${acct}${syncNote}`, { folders: [], count: 0 });
43026
43120
  }
43121
+ const resolvedAcct = folders[0]?.account ? ` (${folders[0].account})` : acct;
43027
43122
  const folderList = folders.map((f) => ` - ${f.name}`).join("\n");
43028
- return successResponse(`Found ${folders.length} folders${acct}:
43029
- ${folderList}${syncNote}`, {
43030
- folders,
43031
- count: folders.length
43032
- });
43123
+ return successResponse(
43124
+ `Found ${folders.length} folders${resolvedAcct}:
43125
+ ${folderList}${syncNote}`,
43126
+ {
43127
+ folders,
43128
+ count: folders.length
43129
+ }
43130
+ );
43033
43131
  }, "Error listing folders")
43034
43132
  );
43035
43133
  registerTool(
@@ -43040,7 +43138,9 @@ registerTool(
43040
43138
  name: external_exports.string().min(1, "Folder name is required").max(MAX.FOLDER).describe(
43041
43139
  'Folder name or nested path separated by "/". E.g., "Retro Tech/PC/CPUs" creates all intermediate folders. Existing segments are skipped.'
43042
43140
  ),
43043
- account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account name (defaults to iCloud)")
43141
+ account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
43142
+ "Account name (defaults to Notes.app's default account; exact or unique-prefix match)"
43143
+ )
43044
43144
  },
43045
43145
  outputSchema: {
43046
43146
  ok: external_exports.boolean().optional(),
@@ -43383,7 +43483,9 @@ registerTool(
43383
43483
  folder: external_exports.string().max(MAX.FOLDER).describe(
43384
43484
  'Destination folder name or nested path (e.g. "Work/Clients"). Must already exist \u2014 create-folder first.'
43385
43485
  ),
43386
- account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account containing the destination folder (defaults to iCloud)")
43486
+ account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
43487
+ "Account containing the destination folder (defaults to Notes.app's default account; exact or unique-prefix match)"
43488
+ )
43387
43489
  },
43388
43490
  outputSchema: {
43389
43491
  ok: external_exports.boolean().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
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",