apple-notes-mcp 2.7.5 → 2.8.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.
package/README.md CHANGED
@@ -284,8 +284,10 @@ Retrieves the full content of a specific note.
284
284
  }
285
285
  ```
286
286
 
287
- **Returns:** The HTML content of the note, or error if not found. The
288
- `structuredContent` also includes `hashtags` any inline `#hashtag` tags parsed
287
+ **Returns:** The HTML content of the note, its exact `id`, and a
288
+ `contentHash`. Pass that hash back as `expectedContentHash` for a later update,
289
+ append, or delete; the write is rejected if the note changed after this read.
290
+ The `structuredContent` also includes `hashtags` — any inline `#hashtag` tags parsed
289
291
  from the body. Apple Notes tags are inline hashtags, not a scriptable property;
290
292
  see [docs/APPLESCRIPT-LIMITATIONS.md](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/APPLESCRIPT-LIMITATIONS.md#tags--hashtags-29). Smart Folders are not scriptable.
291
293
 
@@ -294,10 +296,9 @@ base64 images larger than `APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES` (default
294
296
  256 KB) are replaced with `[inline image omitted: …]` text placeholders so an
295
297
  image-heavy note cannot blow the MCP message limit. `structuredContent` reports
296
298
  this as `strippedImages` (count) and `truncated` (boolean). When either is set,
297
- passing this body to [`update-note`](#update-note) would replace the real images
298
- with the placeholder text use [`append-to-note`](#append-to-note) for
299
- additions, or export the images with `save-attachment` / `fetch-attachment`
300
- first.
299
+ passing this body to an unguarded full-body writer would replace the real images
300
+ with placeholder text. This server refuses update and append operations on
301
+ attachment-bearing notes; edit them in Notes.app.
301
302
 
302
303
  ---
303
304
 
@@ -379,56 +380,42 @@ Updates an existing note's content and/or title.
379
380
 
380
381
  | Parameter | Type | Required | Description |
381
382
  |-----------|------|----------|-------------|
382
- | `id` | string | No | Note ID (preferred - more reliable than title) |
383
- | `title` | string | No | Current title of the note to update (use `id` instead when available) |
383
+ | `id` | string | Yes | Exact CoreData note ID returned by a read or search |
384
+ | `expectedContentHash` | string | Yes | `contentHash` from the exact note version being replaced |
384
385
  | `newTitle` | string | No | New title (if changing the title; ignored when `format` is `"html"`) |
385
386
  | `newContent` | string | Yes | New content for the note body |
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
- **Note:** Either `id` or `title` must be provided. Using `id` is recommended.
390
-
391
- **Returns:** Confirmation with the note's visible title and, for ID-based updates, its ID. For HTML updates, the title comes from the first rendered line of `newContent`, matching Notes.app. The response also warns if the note is shared.
389
+ Title-only updates are rejected because Apple Notes titles are not unique.
392
390
 
393
391
  **Example - Using ID (recommended):**
394
392
  ```json
395
393
  {
396
394
  "id": "x-coredata://ABC123/ICNote/p456",
395
+ "expectedContentHash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
397
396
  "newContent": "Updated content here"
398
397
  }
399
398
  ```
400
399
 
401
- **Example - Update content only:**
402
- ```json
403
- {
404
- "title": "Shopping List",
405
- "newContent": "- Milk\n- Eggs\n- Bread\n- Butter"
406
- }
407
- ```
408
-
409
- **Example - Update title and content:**
410
- ```json
411
- {
412
- "title": "Draft",
413
- "newTitle": "Final Version",
414
- "newContent": "This is the completed document."
415
- }
416
- ```
417
-
418
400
  **Example - Update with HTML formatting:**
419
401
  ```json
420
402
  {
421
403
  "id": "x-coredata://ABC123/ICNote/p456",
404
+ "expectedContentHash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
422
405
  "newContent": "<p>New findings with <b>bold</b> emphasis.</p><pre><code>console.log('hello');</code></pre>",
423
406
  "format": "html"
424
407
  }
425
408
  ```
426
409
 
427
- **Returns:** Confirmation message, or error if note not found.
410
+ **Returns:** Confirmation with the exact ID, post-save `contentHash`, and
411
+ `verifiedVisibleText: true`, or an error if the note changed before saving.
412
+ Apple Notes normalizes HTML, so this proves the visible text after saving, not
413
+ byte-identical rich formatting.
428
414
 
429
415
  **Note:** `newContent` **replaces the entire note body** — it is not appended. To add to a note, prefer [`append-to-note`](#append-to-note), which does the read-and-concatenate for you and always round-trips the body as HTML. If you do read-modify-write by hand, note that `get-note-content` replaces oversized inline images with text placeholders (see [`get-note-content`](#get-note-content)) — writing that body back bakes the placeholders in.
430
416
 
431
- **Attachments:** A full-body replace can drop embedded files, images, scans, PDFs, or audio. When a note may hold attachments, run [`list-attachments`](#list-attachments) first, and either save them with `save-attachment` or build a new note rather than overwriting. See the skill's [Attachment-Safe Updates](https://github.com/sweetrb/apple-notes-mcp/blob/main/skills/apple-notes/SKILL.md#attachment-safe-updates) guidance.
417
+ **Attachments:** `update-note` refuses to replace any note that contains an
418
+ attachment. Edit those notes in Notes.app or create a separate note instead.
432
419
 
433
420
  ---
434
421
 
@@ -438,23 +425,17 @@ Deletes a note (moves to Recently Deleted in Notes.app).
438
425
 
439
426
  | Parameter | Type | Required | Description |
440
427
  |-----------|------|----------|-------------|
441
- | `id` | string | No | Note ID (preferred - more reliable than title) |
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 Notes.app's default account; exact or unique-prefix match, ignored if `id` is provided) |
428
+ | `id` | string | Yes | Exact CoreData note ID returned by a read or search |
429
+ | `expectedContentHash` | string | Yes | `contentHash` from the exact note version being deleted |
444
430
 
445
- **Note:** Either `id` or `title` must be provided. Using `id` is recommended.
431
+ Title-only deletion is rejected. If the note changed after the supplied hash
432
+ was read, deletion is also rejected.
446
433
 
447
434
  **Example - Using ID (recommended):**
448
435
  ```json
449
436
  {
450
- "id": "x-coredata://ABC123/ICNote/p456"
451
- }
452
- ```
453
-
454
- **Example - Using title:**
455
- ```json
456
- {
457
- "title": "Old Draft"
437
+ "id": "x-coredata://ABC123/ICNote/p456",
438
+ "expectedContentHash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
458
439
  }
459
440
  ```
460
441
 
@@ -470,12 +451,10 @@ Moves a note to a different folder. The note is relocated in place via Notes.app
470
451
 
471
452
  | Parameter | Type | Required | Description |
472
453
  |-----------|------|----------|-------------|
473
- | `id` | string | No | Note ID (preferred - more reliable than title) |
474
- | `title` | string | No | Title of the note to move (use `id` instead when available) |
454
+ | `id` | string | Yes | Exact CoreData note ID returned by a read or search |
475
455
  | `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`) |
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
456
 
478
- **Note:** Either `id` or `title` must be provided. Using `id` is recommended.
457
+ Title-only moves are rejected.
479
458
 
480
459
  **Example - Using ID (recommended):**
481
460
  ```json
@@ -485,15 +464,8 @@ Moves a note to a different folder. The note is relocated in place via Notes.app
485
464
  }
486
465
  ```
487
466
 
488
- **Example - Using title:**
489
- ```json
490
- {
491
- "title": "Completed Task",
492
- "folder": "Archive"
493
- }
494
- ```
495
-
496
- **Returns:** Confirmation message, or error if note or folder not found.
467
+ **Returns:** Confirmation only after the same note ID is read back and its
468
+ actual destination folder ID matches the requested folder.
497
469
 
498
470
  ---
499
471
 
@@ -503,20 +475,20 @@ Appends or prepends content to an existing note without replacing it. Always rea
503
475
 
504
476
  | Parameter | Type | Required | Description |
505
477
  |-----------|------|----------|-------------|
506
- | `id` | string | No | Note ID (preferred - more reliable than title) |
507
- | `title` | string | No | Note title (use `id` instead when available) |
478
+ | `id` | string | Yes | Exact CoreData note ID returned by a read or search |
479
+ | `expectedContentHash` | string | Yes | `contentHash` from the exact note version being extended |
508
480
  | `content` | string | Yes | Text to append to the note body |
509
481
  | `position` | string | No | `"after"` (default) appends to the end; `"before"` prepends to the start |
510
482
  | `separator` | string | No | String placed between existing content and new content (default: two newlines → `<div><br></div>` in HTML) |
511
483
  | `format` | string | No | Format of the content being appended: `"plaintext"` (default) or `"html"` |
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
484
 
514
- **Note:** Either `id` or `title` must be provided. Using `id` is recommended.
485
+ Title-only appends are rejected.
515
486
 
516
487
  **Example - Append plaintext:**
517
488
  ```json
518
489
  {
519
490
  "id": "x-coredata://ABC123/ICNote/p456",
491
+ "expectedContentHash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
520
492
  "content": "New item added today"
521
493
  }
522
494
  ```
@@ -525,15 +497,20 @@ Appends or prepends content to an existing note without replacing it. Always rea
525
497
  ```json
526
498
  {
527
499
  "id": "x-coredata://ABC123/ICNote/p456",
500
+ "expectedContentHash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
528
501
  "content": "<div><b>Status:</b> done</div>",
529
502
  "format": "html",
530
503
  "position": "before"
531
504
  }
532
505
  ```
533
506
 
534
- **Returns:** Confirmation with note id and title. Warns when the note is shared with collaborators.
507
+ **Returns:** Confirmation with the exact ID, post-save `contentHash`, and
508
+ `verifiedVisibleText: true`. Apple Notes normalizes HTML, so this proves the
509
+ visible text after saving, not byte-identical rich formatting. Warns when the
510
+ note is shared with collaborators.
535
511
 
536
- **⚠️ Safety:** Reads the existing body first, concatenates, then writes back. Run `list-attachments` first if the note may hold embedded files a full-body rewrite can drop attachments.
512
+ **Safety:** The append is rejected if the note changed since it was read or if
513
+ the note contains an attachment.
537
514
 
538
515
  ---
539
516
 
@@ -740,7 +717,7 @@ Deletes multiple notes at once by ID.
740
717
 
741
718
  | Parameter | Type | Required | Description |
742
719
  |-----------|------|----------|-------------|
743
- | `ids` | string[] | Yes | Array of note IDs to delete (max 500 per request) |
720
+ | `notes` | object[] | Yes | Array of `{id, expectedContentHash}` snapshots to delete (max 500 per request) |
744
721
 
745
722
  **Returns:** Summary of successes and failures.
746
723
 
@@ -758,7 +735,8 @@ Moves multiple notes to a folder.
758
735
  | `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`). Must already exist — create it with [`create-folder`](#create-folder) |
759
736
  | `account` | string | No | Account containing the folder |
760
737
 
761
- **Returns:** Summary of successes and failures.
738
+ **Returns:** Summary of successes and failures. Each success is reported only
739
+ after the note's actual container folder ID matches the destination folder ID.
762
740
 
763
741
  ---
764
742
 
@@ -1003,7 +981,7 @@ AI: [calls create-folder with name="Archive"]
1003
981
  "Created folder 'Archive'"
1004
982
 
1005
983
  User: "Move my old meeting notes to Archive"
1006
- AI: [calls move-note with title="Old Meeting Notes", folder="Archive"]
984
+ AI: [searches for the note, then calls move-note with its exact id and folder="Archive"]
1007
985
  "Moved 'Old Meeting Notes' to 'Archive'"
1008
986
 
1009
987
  User: "What folders do I have?"