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.
- package/README.md +27 -3
- package/build/index.js +26 -26
- 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
|
[](https://github.com/sweetrb/apple-notes-mcp/actions/workflows/ci.yml)
|
|
7
7
|
[](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
|
-
|
|
1009
|
-
|
|
1010
|
-
- [apple-
|
|
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.
|
|
4
|
-
"description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and
|
|
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
|
|
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": "
|
|
84
|
+
"node": "24.17.0"
|
|
85
85
|
},
|
|
86
86
|
"lint-staged": {
|
|
87
87
|
"src/**/*.ts": [
|