apple-notes-mcp 2.1.0 → 2.1.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 +27 -3
  2. package/build/index.js +26 -26
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -6,6 +6,10 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that e
6
6
  [![CI](https://github.com/sweetrb/apple-notes-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/sweetrb/apple-notes-mcp/actions/workflows/ci.yml)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
+ <p align="center">
10
+ <img src="codex/assets/screenshot.png" alt="Apple Notes MCP — create, search, and organize Apple Notes from Codex, Claude, and other AI assistants" width="680">
11
+ </p>
12
+
9
13
  ## What is This?
10
14
 
11
15
  This server acts as a bridge between AI assistants and Apple Notes. Once configured, you can ask Claude (or any MCP-compatible AI) to:
@@ -52,6 +56,10 @@ codex plugin add apple-notes@apple-notes-mcp
52
56
 
53
57
  The Codex plugin runs the published `apple-notes-mcp` server through `npx` and ships the same Apple Notes skill, so behavior matches the Claude Code plugin.
54
58
 
59
+ ### Other Hosts (Hermes, Antigravity)
60
+
61
+ Plugin packaging for the Hermes and Antigravity hosts is also included (`.hermes-plugin/` and `.antigravity-plugin/`). Each registers the same `apple-notes` MCP server (launched via `npx -y apple-notes-mcp`) and bundles the Apple Notes skill, so behavior matches the Claude Code and Codex plugins. Install them through each host's plugin/marketplace mechanism pointed at this repository.
62
+
55
63
  ### Manual Installation
56
64
 
57
65
  **1. Install the server:**
@@ -345,6 +353,8 @@ Updates an existing note's content and/or title.
345
353
 
346
354
  **Returns:** Confirmation message, or error if note not found.
347
355
 
356
+ **Note:** `newContent` **replaces the entire note body** — it is not appended. To preserve existing content, read it first (e.g. with `get-note-content`) and include it in `newContent`.
357
+
348
358
  ---
349
359
 
350
360
  #### `delete-note`
@@ -375,6 +385,8 @@ Deletes a note (moves to Recently Deleted in Notes.app).
375
385
 
376
386
  **Returns:** Confirmation message, or error if note not found.
377
387
 
388
+ **⚠️ Safety:** Irreversible from the agent's side — requires explicit user confirmation before calling. Prefer `search-notes` / `list-notes` first to confirm the exact id(s) being deleted.
389
+
378
390
  ---
379
391
 
380
392
  #### `move-note`
@@ -504,6 +516,8 @@ Deletes a folder.
504
516
 
505
517
  **Returns:** Confirmation message, or error if folder not found or not empty.
506
518
 
519
+ **⚠️ Safety:** Irreversible — requires explicit user confirmation before calling. Prefer `list-folders` first to confirm the exact folder path being deleted.
520
+
507
521
  ---
508
522
 
509
523
  ### Account Operations
@@ -535,6 +549,8 @@ Deletes multiple notes at once by ID.
535
549
 
536
550
  **Returns:** Summary of successes and failures.
537
551
 
552
+ **⚠️ Safety:** Irreversible — requires explicit user confirmation before calling. Prefer `search-notes` / `list-notes` first to confirm the exact ids being deleted.
553
+
538
554
  ---
539
555
 
540
556
  #### `batch-move-notes`
@@ -676,6 +692,8 @@ Gets comprehensive statistics about your notes.
676
692
 
677
693
  **Returns:** Total counts, per-account breakdown, folder statistics, and recently modified counts.
678
694
 
695
+ The `structuredContent` also includes a `coverage` object — `{ complete, scanned, covered, warnings[] }`. If `complete` is `false`, one or more accounts (or the recent-activity scan) could not be read and the counts reflect only the scopes that succeeded; the text output adds a "⚠️ Partial results" line. This lets you tell a genuinely empty library apart from a partial failure.
696
+
679
697
  ---
680
698
 
681
699
  #### `get-sync-status`
@@ -1005,6 +1023,12 @@ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for gui
1005
1023
 
1006
1024
  ## Related Projects
1007
1025
 
1008
- - [apple-mail-mcp](https://github.com/sweetrb/apple-mail-mcp) MCP server for Apple Mail
1009
- - [apple-numbers-mcp](https://github.com/sweetrb/apple-numbers-mcp) — MCP server for Apple Numbers spreadsheets
1010
- - [apple-photos-mcp](https://github.com/sweetrb/apple-photos-mcp) — MCP server for Apple Photos
1026
+ Part of a family of macOS MCP servers:
1027
+
1028
+ - [apple-mail-mcp](https://github.com/sweetrb/apple-mail-mcp) — MCP server for Apple Mail (read, search, send, and organize email)
1029
+ - [apple-numbers-mcp](https://github.com/sweetrb/apple-numbers-mcp) — MCP server for Apple Numbers (read and write .numbers spreadsheets)
1030
+ - [apple-photos-mcp](https://github.com/sweetrb/apple-photos-mcp) — MCP server for Apple Photos (query metadata and export originals)
1031
+
1032
+ ## Recurring macOS permission prompts
1033
+
1034
+ If macOS keeps re-prompting for Full Disk Access or Automation for `node` (often after a `brew upgrade`), see [docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md](docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md) — the fix is to run this server under the official, Developer-ID-signed Node so the grant survives Node updates.
package/build/index.js CHANGED
@@ -108,7 +108,7 @@ const folderNameSchema = {
108
108
  // Note Tools
109
109
  // =============================================================================
110
110
  // --- create-note ---
111
- server.tool("create-note", {
111
+ server.tool("create-note", "Use when: the user wants to create a brand-new Apple Note.\nReturns: the new note's title and id — reuse the id for follow-up reads/edits.\nDo not use when: editing an existing note (use update-note).\nNote: the title is prepended as an <h1>; true Apple Notes checklists cannot be created via AppleScript (see the content field).", {
112
112
  title: z.string().min(1, "Title is required"),
113
113
  content: z
114
114
  .string()
@@ -134,7 +134,7 @@ server.tool("create-note", {
134
134
  return successResponse(`Note created: "${note.title}" [id: ${note.id}]${checklistWarning}`);
135
135
  }, "Error creating note"));
136
136
  // --- search-notes ---
137
- server.tool("search-notes", {
137
+ server.tool("search-notes", "Use when: finding notes by a keyword in the title (or body with searchContent=true) and you need their ids.\nReturns: matching notes with title, folder, and id.\nDo not use when: you already have a note id (use get-note-content) or want every note (use list-notes).\nPrefer this first to obtain ids for subsequent read/update/delete/move calls.", {
138
138
  query: z.string().min(1, "Search query is required"),
139
139
  searchContent: z.boolean().optional().describe("Search note content instead of titles"),
140
140
  account: z.string().optional().describe("Account to search in"),
@@ -179,7 +179,7 @@ server.tool("search-notes", {
179
179
  return successResponse(`Found ${notes.length} notes (searched ${searchType}${folderInfo}${dateInfo}${limitInfo}):\n${noteList}${syncNote}`, { notes, count: notes.length });
180
180
  }, "Error searching notes"));
181
181
  // --- get-note-content ---
182
- server.tool("get-note-content", {
182
+ server.tool("get-note-content", "Use when: reading the full body text of one known note, by id (preferred) or title.\nReturns: the note's content plus parsed hashtags.\nDo not use when: you only need metadata (get-note-details) or Markdown with checklist state (get-note-markdown).\nNote: password-protected notes must be unlocked in Notes.app first.", {
183
183
  id: z.string().optional().describe("Note ID (preferred - more reliable than title)"),
184
184
  title: z.string().optional().describe("Note title (use id instead when available)"),
185
185
  account: z
@@ -224,7 +224,7 @@ server.tool("get-note-content", {
224
224
  return successResponse(content, { title, content, hashtags });
225
225
  }, "Error retrieving note content"));
226
226
  // --- get-note-by-id ---
227
- server.tool("get-note-by-id", {
227
+ server.tool("get-note-by-id", "Use when: you have a note id and need its metadata only.\nReturns: id, title, created, modified, shared, passwordProtected.\nDo not use when: you need the body text (get-note-content) or only have a title (get-note-details).", {
228
228
  id: z.string().min(1, "Note ID is required"),
229
229
  }, withErrorHandling(({ id }) => {
230
230
  const note = notesManager.getNoteById(id);
@@ -243,7 +243,7 @@ server.tool("get-note-by-id", {
243
243
  return successResponse(JSON.stringify(metadata, null, 2), metadata);
244
244
  }, "Error retrieving note"));
245
245
  // --- get-note-details ---
246
- server.tool("get-note-details", noteTitleSchema, withErrorHandling(({ title, account }) => {
246
+ server.tool("get-note-details", "Use when: you have a note title (not an id) and need its metadata.\nReturns: id, title, created, modified, shared, passwordProtected, account.\nDo not use when: you have an id (get-note-by-id) or need the body text (get-note-content).\nUse the returned id for reliable follow-up operations.", noteTitleSchema, withErrorHandling(({ title, account }) => {
247
247
  const note = notesManager.getNoteDetails(title, account);
248
248
  if (!note) {
249
249
  return errorResponse(`Note "${title}" not found`);
@@ -261,7 +261,7 @@ server.tool("get-note-details", noteTitleSchema, withErrorHandling(({ title, acc
261
261
  return successResponse(JSON.stringify(metadata, null, 2), metadata);
262
262
  }, "Error retrieving note details"));
263
263
  // --- update-note ---
264
- server.tool("update-note", {
264
+ server.tool("update-note", "Use when: changing the title and/or replacing the body of an existing note, by id (preferred) or title.\nReturns: confirmation; warns when the note is shared.\nDo not use when: creating a new note (create-note).\nSafety: newContent REPLACES the entire body — it does not append. Read the note first if you need to preserve existing text. Edits to shared notes are immediately visible to all collaborators.", {
265
265
  id: z.string().optional().describe("Note ID (preferred - more reliable than title)"),
266
266
  title: z.string().optional().describe("Current note title (use id instead when available)"),
267
267
  newTitle: z.string().optional().describe("New title for the note"),
@@ -326,7 +326,7 @@ server.tool("update-note", {
326
326
  return successResponse(`Note updated: "${finalTitle}"${sharedWarning}${checklistWarning}`);
327
327
  }, "Error updating note"));
328
328
  // --- delete-note ---
329
- server.tool("delete-note", {
329
+ server.tool("delete-note", "Use when: permanently deleting a single note, by id (preferred) or title.\nReturns: confirmation; warns when the note was shared.\nDo not use when: deleting many notes (batch-delete-notes) or just relocating one (move-note).\nSafety: requires explicit user confirmation before deleting. Prefer search-notes/list-notes first to show the affected note id and title. Deleting a shared note removes collaborator access.", {
330
330
  id: z.string().optional().describe("Note ID (preferred - more reliable than title)"),
331
331
  title: z.string().optional().describe("Note title (use id instead when available)"),
332
332
  account: z
@@ -371,7 +371,7 @@ server.tool("delete-note", {
371
371
  return successResponse(`Note deleted: "${title}"${sharedWarning}`);
372
372
  }, "Error deleting note"));
373
373
  // --- move-note ---
374
- server.tool("move-note", {
374
+ server.tool("move-note", "Use when: moving one note to a different folder, by id (preferred) or title.\nReturns: confirmation of the note and destination folder.\nDo not use when: moving many notes (batch-move-notes).\nNote: implemented as copy-then-delete; the destination folder must already exist (create-folder).", {
375
375
  id: z.string().optional().describe("Note ID (preferred - more reliable than title)"),
376
376
  title: z.string().optional().describe("Note title (use id instead when available)"),
377
377
  folder: z.string().min(1, "Destination folder is required"),
@@ -406,7 +406,7 @@ server.tool("move-note", {
406
406
  return successResponse(`Note moved: "${title}" -> "${folder}"`);
407
407
  }, "Error moving note"));
408
408
  // --- list-notes ---
409
- server.tool("list-notes", {
409
+ server.tool("list-notes", "Use when: enumerating notes in an account or folder; supports modifiedSince and limit for large collections.\nReturns: note titles only (no content or ids).\nDo not use when: you need content (get-note-content) or ids for follow-up edits (use search-notes).\nNote: warns if iCloud sync is active and results may be partial.", {
410
410
  account: z.string().optional().describe("Account to list notes from"),
411
411
  folder: z.string().optional().describe("Filter to specific folder"),
412
412
  modifiedSince: z
@@ -444,7 +444,7 @@ server.tool("list-notes", {
444
444
  // Folder Tools
445
445
  // =============================================================================
446
446
  // --- list-folders ---
447
- server.tool("list-folders", {
447
+ server.tool("list-folders", "Use when: listing all folders, with full nested paths, for an account.\nReturns: folder names/paths.\nDo not use when: listing notes (list-notes).\nNote: warns if iCloud sync is active.", {
448
448
  account: z.string().optional().describe("Account to list folders from"),
449
449
  }, withErrorHandling(({ account }) => {
450
450
  // Use sync-aware wrapper for this read operation
@@ -469,7 +469,7 @@ server.tool("list-folders", {
469
469
  });
470
470
  }, "Error listing folders"));
471
471
  // --- create-folder ---
472
- server.tool("create-folder", {
472
+ server.tool("create-folder", "Use when: creating a folder, including nested paths like 'Work/Clients' (intermediate folders are created, existing ones skipped).\nReturns: confirmation.\nDo not use when: creating a note (create-note).", {
473
473
  name: z
474
474
  .string()
475
475
  .min(1, "Folder name is required")
@@ -483,7 +483,7 @@ server.tool("create-folder", {
483
483
  return successResponse(`Folder created: "${folder.name}"`);
484
484
  }, "Error creating folder"));
485
485
  // --- delete-folder ---
486
- server.tool("delete-folder", folderNameSchema, withErrorHandling(({ name, account }) => {
486
+ server.tool("delete-folder", "Use when: deleting an existing folder by name or nested path.\nReturns: confirmation.\nDo not use when: deleting a note (delete-note).\nSafety: requires explicit user confirmation. Deletion fails if the folder still contains notes — list or move those notes first.", folderNameSchema, withErrorHandling(({ name, account }) => {
487
487
  const success = notesManager.deleteFolder(name, account);
488
488
  if (!success) {
489
489
  return errorResponse(`Failed to delete folder "${name}". Folder may not exist or may contain notes.`);
@@ -494,7 +494,7 @@ server.tool("delete-folder", folderNameSchema, withErrorHandling(({ name, accoun
494
494
  // Account Tools
495
495
  // =============================================================================
496
496
  // --- list-accounts ---
497
- server.tool("list-accounts", {}, withErrorHandling(() => {
497
+ server.tool("list-accounts", "Use when: discovering which Notes accounts exist (iCloud, Gmail, Exchange, etc.) before targeting one.\nReturns: account names.\nDo not use when: you already know the account, or are working by note id (ids are account-independent).", {}, withErrorHandling(() => {
498
498
  const accounts = notesManager.listAccounts();
499
499
  if (accounts.length === 0) {
500
500
  return successResponse("No Notes accounts found", { accounts: [], count: 0 });
@@ -509,7 +509,7 @@ server.tool("list-accounts", {}, withErrorHandling(() => {
509
509
  // Collaboration Tools
510
510
  // =============================================================================
511
511
  // --- list-shared-notes ---
512
- server.tool("list-shared-notes", {}, withErrorHandling(() => {
512
+ server.tool("list-shared-notes", "Use when: finding notes shared with collaborators.\nReturns: shared notes with title, account, and id.\nDo not use when: searching all notes (search-notes).\nNote: edits or deletes to these notes affect all collaborators.", {}, withErrorHandling(() => {
513
513
  const sharedNotes = notesManager.listSharedNotes();
514
514
  if (sharedNotes.length === 0) {
515
515
  return successResponse("No shared notes found. You have no notes shared with collaborators.", { notes: [], count: 0 });
@@ -527,7 +527,7 @@ server.tool("list-shared-notes", {}, withErrorHandling(() => {
527
527
  // Diagnostics Tools
528
528
  // =============================================================================
529
529
  // --- get-sync-status ---
530
- server.tool("get-sync-status", {}, withErrorHandling(() => {
530
+ server.tool("get-sync-status", "Use when: checking whether iCloud sync is in progress before trusting read results.\nReturns: sync active/idle, pending upload count, and seconds since last change.\nDo not use when: you need note data — this is a read-only diagnostics tool.", {}, withErrorHandling(() => {
531
531
  const status = getSyncStatus();
532
532
  if (status.error) {
533
533
  return successResponse(`⚠️ Sync status unknown: ${status.error}`, { ...status });
@@ -553,7 +553,7 @@ server.tool("get-sync-status", {}, withErrorHandling(() => {
553
553
  return successResponse(lines.join("\n"), { ...status });
554
554
  }, "Error checking sync status"));
555
555
  // --- health-check ---
556
- server.tool("health-check", {}, withErrorHandling(() => {
556
+ server.tool("health-check", "Use when: a quick check that Notes.app is reachable and (optionally) Full Disk Access is granted for checklist features.\nReturns: pass/fail per check.\nDo not use when: you need detailed, actionable setup diagnostics (use doctor).\nRead-only.", {}, withErrorHandling(() => {
557
557
  const result = notesManager.healthCheck();
558
558
  const statusIcon = result.healthy ? "✓" : "✗";
559
559
  const statusText = result.healthy ? "All checks passed" : "Issues detected";
@@ -571,14 +571,14 @@ server.tool("health-check", {}, withErrorHandling(() => {
571
571
  return successResponse(`${statusIcon} ${statusText}\n\n${checkLines}\n${fdaLine}`);
572
572
  }, "Error running health check"));
573
573
  // --- doctor ---
574
- server.tool("doctor", {}, withErrorHandling(() => {
574
+ server.tool("doctor", "Use when: diagnosing setup problems (Notes.app automation permission, account state, Full Disk Access) with actionable guidance.\nReturns: a detailed report plus structured fields.\nDo not use when: you just need a quick pass/fail (health-check).\nRead-only.", {}, withErrorHandling(() => {
575
575
  // Richer than health-check: Notes.app permission, account state, and Full
576
576
  // Disk Access with actionable messages + structuredContent (#22).
577
577
  const report = runDoctor(notesManager);
578
578
  return successResponse(formatDoctorReport(report), { ...report });
579
579
  }, "Error running doctor"));
580
580
  // --- get-notes-stats ---
581
- server.tool("get-notes-stats", {}, withErrorHandling(() => {
581
+ server.tool("get-notes-stats", "Use when: summarizing the library — total notes, per-account/folder counts, and recent activity.\nReturns: aggregate statistics; flags partial coverage when some scopes were unreadable.\nDo not use when: you need individual notes (list-notes/search-notes).\nRead-only.", {}, withErrorHandling(() => {
582
582
  const stats = notesManager.getNotesStats();
583
583
  // Format the output
584
584
  const lines = [];
@@ -614,7 +614,7 @@ server.tool("get-notes-stats", {}, withErrorHandling(() => {
614
614
  return successResponse(lines.join("\n"), { ...stats });
615
615
  }, "Error getting notes statistics"));
616
616
  // --- list-attachments ---
617
- server.tool("list-attachments", {
617
+ server.tool("list-attachments", "Use when: listing the attachments of one note, by id (preferred) or title.\nReturns: each attachment's name, content type, and id (use with save-attachment/fetch-attachment).\nDo not use when: you want the attachment bytes (fetch-attachment) or a file on disk (save-attachment).", {
618
618
  id: z.string().optional().describe("Note ID (preferred - more reliable than title)"),
619
619
  title: z.string().optional().describe("Note title (use id instead when available)"),
620
620
  account: z
@@ -654,7 +654,7 @@ server.tool("list-attachments", {
654
654
  return successResponse(`Found ${attachments.length} attachment(s) in "${title}":\n${attachmentList}`, { attachments, count: attachments.length });
655
655
  }, "Error listing attachments"));
656
656
  // --- batch-delete-notes ---
657
- server.tool("batch-delete-notes", {
657
+ server.tool("batch-delete-notes", "Use when: permanently deleting multiple notes by id in one call.\nReturns: per-id success/failure counts.\nDo not use when: deleting a single note (delete-note).\nSafety: requires explicit user confirmation; this is destructive and not undoable. Prefer search-notes/list-notes first to confirm the exact ids being deleted.", {
658
658
  ids: z.array(z.string()).describe("Array of note IDs to delete"),
659
659
  }, withErrorHandling(({ ids }) => {
660
660
  if (ids.length === 0) {
@@ -673,7 +673,7 @@ server.tool("batch-delete-notes", {
673
673
  return succeeded > 0 ? successResponse(lines.join("\n")) : errorResponse(lines.join("\n"));
674
674
  }, "Error performing batch delete"));
675
675
  // --- batch-move-notes ---
676
- server.tool("batch-move-notes", {
676
+ server.tool("batch-move-notes", "Use when: moving multiple notes by id into one destination folder.\nReturns: per-id success/failure counts.\nDo not use when: moving a single note (move-note).\nNote: the destination folder must already exist (create-folder).", {
677
677
  ids: z.array(z.string()).describe("Array of note IDs to move"),
678
678
  folder: z.string().describe("Destination folder name"),
679
679
  account: z
@@ -697,7 +697,7 @@ server.tool("batch-move-notes", {
697
697
  return succeeded > 0 ? successResponse(lines.join("\n")) : errorResponse(lines.join("\n"));
698
698
  }, "Error performing batch move"));
699
699
  // --- save-attachment ---
700
- server.tool("save-attachment", {
700
+ server.tool("save-attachment", "Use when: writing one note attachment to a file on disk.\nReturns: the saved path.\nDo not use when: you want the bytes in-memory as base64 (fetch-attachment).\nSafety: writes a file; savePath must be absolute and under the home directory, a temp dir, or /Volumes. Get the ids from list-attachments first.", {
701
701
  noteId: z.string().min(1, "noteId is required").describe("CoreData note id (from search/list)"),
702
702
  attachmentId: z
703
703
  .string()
@@ -719,7 +719,7 @@ server.tool("save-attachment", {
719
719
  });
720
720
  }, "Error saving attachment"));
721
721
  // --- fetch-attachment ---
722
- server.tool("fetch-attachment", {
722
+ server.tool("fetch-attachment", "Use when: retrieving one note attachment's bytes inline as base64 (no file written).\nReturns: name, content type, byte count, and base64 data.\nDo not use when: you want it saved to disk (save-attachment).\nNote: get the ids from list-attachments first.", {
723
723
  noteId: z.string().min(1, "noteId is required").describe("CoreData note id (from search/list)"),
724
724
  attachmentId: z
725
725
  .string()
@@ -733,7 +733,7 @@ server.tool("fetch-attachment", {
733
733
  return successResponse(`Fetched "${r.name ?? "attachment"}" (${r.contentType ?? "unknown type"}, ${r.bytes ?? 0} bytes) as base64.`, { name: r.name, contentType: r.contentType, bytes: r.bytes, base64: r.base64 });
734
734
  }, "Error fetching attachment"));
735
735
  // --- export-notes-json ---
736
- server.tool("export-notes-json", {}, withErrorHandling(() => {
736
+ server.tool("export-notes-json", "Use when: exporting the entire notes library as structured JSON for backup or bulk processing.\nReturns: a summary plus the full JSON of all notes, folders, and accounts.\nDo not use when: you need a single note (get-note-content) — this reads everything and can be large.\nRead-only.", {}, withErrorHandling(() => {
737
737
  const exportData = notesManager.exportNotesAsJson();
738
738
  const { summary } = exportData;
739
739
  return {
@@ -751,7 +751,7 @@ server.tool("export-notes-json", {}, withErrorHandling(() => {
751
751
  };
752
752
  }, "Error exporting notes"));
753
753
  // --- get-note-markdown ---
754
- server.tool("get-note-markdown", {
754
+ server.tool("get-note-markdown", "Use when: reading a note as Markdown, with checklist items annotated [x]/[ ] when Full Disk Access is granted.\nReturns: the note's Markdown.\nDo not use when: you need the raw HTML/plaintext body (get-note-content) or only metadata (get-note-details).\nNote: falls back to plain lists (no checkmarks) without Full Disk Access.", {
755
755
  id: z.string().optional().describe("Note ID (preferred - more reliable than title)"),
756
756
  title: z.string().optional().describe("Note title (use id instead when available)"),
757
757
  account: z
@@ -778,7 +778,7 @@ server.tool("get-note-markdown", {
778
778
  return successResponse(markdown, { markdown });
779
779
  }, "Error getting note as markdown"));
780
780
  // --- get-checklist-state ---
781
- server.tool("get-checklist-state", {
781
+ server.tool("get-checklist-state", "Use when: reading the checked/unchecked state of a note's checklist items, by id.\nReturns: each item's text and done state plus checked/total counts.\nDo not use when: you only have a title (get the id via search-notes first) or want the full body text (get-note-content).\nNote: requires Full Disk Access; reads the NoteStore database directly.", {
782
782
  id: z.string().min(1, "Note ID is required. Use search-notes to find the note ID first."),
783
783
  }, withErrorHandling(({ id }) => {
784
784
  // Verify the note exists and is accessible
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.1.0",
4
- "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and Codex",
3
+ "version": "2.1.1",
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",
7
7
  "types": "build/index.d.ts",
@@ -27,7 +27,7 @@
27
27
  "format": "prettier --write src",
28
28
  "format:check": "prettier --check src",
29
29
  "typecheck": "tsc --noEmit",
30
- "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json .claude-plugin/marketplace.json .agents/plugins/marketplace.json codex/.codex-plugin/plugin.json",
30
+ "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin .agents/plugins codex .hermes-plugin .antigravity-plugin",
31
31
  "prepublishOnly": "npm run lint && npm run test && npm run build",
32
32
  "prepare": "husky; npm run build"
33
33
  },
@@ -81,7 +81,7 @@
81
81
  "vitest": "^2.0.0"
82
82
  },
83
83
  "volta": {
84
- "node": "22.13.1"
84
+ "node": "24.17.0"
85
85
  },
86
86
  "lint-staged": {
87
87
  "src/**/*.ts": [