apple-notes-mcp 1.2.18 → 1.3.0
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 +109 -8
- package/build/index.js +66 -14
- package/build/services/appleNotesManager.js +228 -37
- package/build/services/appleNotesManager.test.js +281 -1
- package/build/utils/checklistParser.js +259 -0
- package/build/utils/checklistParser.test.js +230 -0
- package/build/utils/protobuf.js +151 -0
- package/build/utils/protobuf.test.js +138 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ The AI assistant communicates with this server, which then uses AppleScript to i
|
|
|
25
25
|
If you're using [Claude Code](https://claude.com/product/claude-code) (in Terminal or VS Code), just ask Claude to install it:
|
|
26
26
|
|
|
27
27
|
```
|
|
28
|
-
Install the apple-notes-mcp MCP server so you can help me manage my Apple Notes
|
|
28
|
+
Install the sweetrb/apple-notes-mcp MCP server so you can help me manage my Apple Notes
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Claude will handle the installation and configuration automatically.
|
|
@@ -45,7 +45,7 @@ This method also installs a **skill** that teaches Claude when and how to use Ap
|
|
|
45
45
|
|
|
46
46
|
**1. Install the server:**
|
|
47
47
|
```bash
|
|
48
|
-
npm install -g apple-notes-mcp
|
|
48
|
+
npm install -g github:sweetrb/apple-notes-mcp
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
**2. Add to Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
@@ -86,6 +86,7 @@ On first use, macOS will ask for permission to automate Notes.app. Click "OK" to
|
|
|
86
86
|
| **Folder Management** | Create, list, and delete folders |
|
|
87
87
|
| **Multi-Account** | Work with iCloud, Gmail, Exchange, or any configured account |
|
|
88
88
|
| **Batch Operations** | Delete or move multiple notes at once |
|
|
89
|
+
| **Checklist State** | Read checklist done/undone state directly from the Notes database |
|
|
89
90
|
| **Export** | Export all notes as JSON or get individual notes as Markdown |
|
|
90
91
|
| **Attachments** | List attachments in notes |
|
|
91
92
|
| **Sync Awareness** | Detect iCloud sync in progress, warn about incomplete results |
|
|
@@ -109,6 +110,7 @@ Creates a new note in Apple Notes.
|
|
|
109
110
|
| `title` | string | Yes | The title of the note (becomes first line) |
|
|
110
111
|
| `content` | string | Yes | The body content of the note |
|
|
111
112
|
| `tags` | string[] | No | Tags for organization (stored in metadata) |
|
|
113
|
+
| `format` | string | No | Content format: `"plaintext"` (default) or `"html"`. When `"html"`, content is used as raw HTML for rich formatting |
|
|
112
114
|
|
|
113
115
|
**Example:**
|
|
114
116
|
```json
|
|
@@ -119,6 +121,15 @@ Creates a new note in Apple Notes.
|
|
|
119
121
|
}
|
|
120
122
|
```
|
|
121
123
|
|
|
124
|
+
**Example - HTML formatting:**
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"title": "Status Report",
|
|
128
|
+
"content": "<h1>Status Report</h1><h2>Summary</h2><p>All tasks <b>on track</b>.</p><ul><li>Feature A: complete</li><li>Feature B: in progress</li></ul>",
|
|
129
|
+
"format": "html"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
122
133
|
**Returns:** Confirmation message with note title and ID. Save the ID for subsequent operations like `update-note`, `delete-note`, etc.
|
|
123
134
|
|
|
124
135
|
---
|
|
@@ -132,6 +143,9 @@ Searches for notes by title or content.
|
|
|
132
143
|
| `query` | string | Yes | Text to search for |
|
|
133
144
|
| `searchContent` | boolean | No | If `true`, searches note body; if `false` (default), searches titles only |
|
|
134
145
|
| `account` | string | No | Account to search in (defaults to iCloud) |
|
|
146
|
+
| `folder` | string | No | Limit search to a specific folder |
|
|
147
|
+
| `modifiedSince` | string | No | ISO 8601 date string to filter notes modified on or after this date (e.g., `"2025-01-01"`) |
|
|
148
|
+
| `limit` | number | No | Maximum number of results to return |
|
|
135
149
|
|
|
136
150
|
**Example - Search titles:**
|
|
137
151
|
```json
|
|
@@ -148,6 +162,16 @@ Searches for notes by title or content.
|
|
|
148
162
|
}
|
|
149
163
|
```
|
|
150
164
|
|
|
165
|
+
**Example - Search recent notes with limit:**
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"query": "todo",
|
|
169
|
+
"searchContent": true,
|
|
170
|
+
"modifiedSince": "2025-01-01",
|
|
171
|
+
"limit": 10
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
151
175
|
**Returns:** List of matching notes with titles, folder names, and IDs. Use the returned ID for subsequent operations like `get-note-content`, `update-note`, etc.
|
|
152
176
|
|
|
153
177
|
---
|
|
@@ -233,9 +257,10 @@ Updates an existing note's content and/or title.
|
|
|
233
257
|
|-----------|------|----------|-------------|
|
|
234
258
|
| `id` | string | No | Note ID (preferred - more reliable than title) |
|
|
235
259
|
| `title` | string | No | Current title of the note to update (use `id` instead when available) |
|
|
236
|
-
| `newTitle` | string | No | New title (if changing the title) |
|
|
260
|
+
| `newTitle` | string | No | New title (if changing the title; ignored when `format` is `"html"`) |
|
|
237
261
|
| `newContent` | string | Yes | New content for the note body |
|
|
238
262
|
| `account` | string | No | Account containing the note (defaults to iCloud, ignored if `id` is provided) |
|
|
263
|
+
| `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) |
|
|
239
264
|
|
|
240
265
|
**Note:** Either `id` or `title` must be provided. Using `id` is recommended.
|
|
241
266
|
|
|
@@ -264,6 +289,15 @@ Updates an existing note's content and/or title.
|
|
|
264
289
|
}
|
|
265
290
|
```
|
|
266
291
|
|
|
292
|
+
**Example - Update with HTML formatting:**
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"id": "x-coredata://ABC123/ICNote/p456",
|
|
296
|
+
"newContent": "<h1>Updated Report</h1><p>New findings with <b>bold</b> emphasis.</p><pre><code>console.log('hello');</code></pre>",
|
|
297
|
+
"format": "html"
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
267
301
|
**Returns:** Confirmation message, or error if note not found.
|
|
268
302
|
|
|
269
303
|
---
|
|
@@ -335,12 +369,14 @@ Moves a note to a different folder.
|
|
|
335
369
|
|
|
336
370
|
#### `list-notes`
|
|
337
371
|
|
|
338
|
-
Lists all notes, optionally filtered by folder.
|
|
372
|
+
Lists all notes, optionally filtered by folder, date, and limit.
|
|
339
373
|
|
|
340
374
|
| Parameter | Type | Required | Description |
|
|
341
375
|
|-----------|------|----------|-------------|
|
|
342
376
|
| `account` | string | No | Account to list notes from (defaults to iCloud) |
|
|
343
377
|
| `folder` | string | No | Filter to notes in this folder only |
|
|
378
|
+
| `modifiedSince` | string | No | ISO 8601 date string to filter notes modified on or after this date (e.g., `"2025-01-01"`) |
|
|
379
|
+
| `limit` | number | No | Maximum number of notes to return |
|
|
344
380
|
|
|
345
381
|
**Example - All notes:**
|
|
346
382
|
```json
|
|
@@ -354,6 +390,14 @@ Lists all notes, optionally filtered by folder.
|
|
|
354
390
|
}
|
|
355
391
|
```
|
|
356
392
|
|
|
393
|
+
**Example - Recent notes with limit:**
|
|
394
|
+
```json
|
|
395
|
+
{
|
|
396
|
+
"modifiedSince": "2025-06-01",
|
|
397
|
+
"limit": 20
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
357
401
|
**Returns:** List of note titles.
|
|
358
402
|
|
|
359
403
|
---
|
|
@@ -476,7 +520,7 @@ Exports all notes as a JSON structure.
|
|
|
476
520
|
|
|
477
521
|
#### `get-note-markdown`
|
|
478
522
|
|
|
479
|
-
Gets a note's content as Markdown instead of HTML.
|
|
523
|
+
Gets a note's content as Markdown instead of HTML. If the note contains checklists and Full Disk Access is granted, checklist items are automatically annotated with `[x]` (done) or `[ ]` (undone).
|
|
480
524
|
|
|
481
525
|
| Parameter | Type | Required | Description |
|
|
482
526
|
|-----------|------|----------|-------------|
|
|
@@ -484,7 +528,35 @@ Gets a note's content as Markdown instead of HTML.
|
|
|
484
528
|
| `title` | string | No | Note title |
|
|
485
529
|
| `account` | string | No | Account containing the note |
|
|
486
530
|
|
|
487
|
-
**Returns:** Note content converted to Markdown format.
|
|
531
|
+
**Returns:** Note content converted to Markdown format. Checklist items include `[x]`/`[ ]` prefixes when database access is available.
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
#### `get-checklist-state`
|
|
536
|
+
|
|
537
|
+
Reads checklist done/undone state for a note. This bypasses the AppleScript limitation where `body of note` strips checklist state, by reading directly from the NoteStore SQLite database.
|
|
538
|
+
|
|
539
|
+
**Requires:** Full Disk Access for the MCP host process (see [Full Disk Access Setup](#full-disk-access-for-checklist-features)).
|
|
540
|
+
|
|
541
|
+
| Parameter | Type | Required | Description |
|
|
542
|
+
|-----------|------|----------|-------------|
|
|
543
|
+
| `id` | string | Yes | Note ID (use `search-notes` to find it first) |
|
|
544
|
+
|
|
545
|
+
**Example:**
|
|
546
|
+
```json
|
|
547
|
+
{
|
|
548
|
+
"id": "x-coredata://ABC123/ICNote/p456"
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**Returns:** Checklist items with done/undone state and progress count:
|
|
553
|
+
```
|
|
554
|
+
Checklist for "Shopping List" (2/4 done):
|
|
555
|
+
[x] Buy milk
|
|
556
|
+
[x] Get bread
|
|
557
|
+
[ ] Pick up laundry
|
|
558
|
+
[ ] Call dentist
|
|
559
|
+
```
|
|
488
560
|
|
|
489
561
|
---
|
|
490
562
|
|
|
@@ -595,7 +667,7 @@ AI: [calls move-note with title="Old Meeting Notes", folder="Archive"]
|
|
|
595
667
|
### npm (Recommended)
|
|
596
668
|
|
|
597
669
|
```bash
|
|
598
|
-
npm install -g apple-notes-mcp
|
|
670
|
+
npm install -g github:sweetrb/apple-notes-mcp
|
|
599
671
|
```
|
|
600
672
|
|
|
601
673
|
### From Source
|
|
@@ -621,6 +693,30 @@ If installed from source, use this configuration:
|
|
|
621
693
|
|
|
622
694
|
---
|
|
623
695
|
|
|
696
|
+
## Full Disk Access for Checklist Features
|
|
697
|
+
|
|
698
|
+
The `get-checklist-state` tool and checklist annotations in `get-note-markdown` read directly from the Apple Notes SQLite database. This requires **Full Disk Access** for the process running the MCP server.
|
|
699
|
+
|
|
700
|
+
### How to Grant Full Disk Access
|
|
701
|
+
|
|
702
|
+
1. Open **System Settings** (or System Preferences on older macOS)
|
|
703
|
+
2. Go to **Privacy & Security > Full Disk Access**
|
|
704
|
+
3. Click the **+** button
|
|
705
|
+
4. Add the application that hosts the MCP server:
|
|
706
|
+
- **Claude Desktop**: Add `/Applications/Claude.app`
|
|
707
|
+
- **Terminal**: Add `/Applications/Utilities/Terminal.app`
|
|
708
|
+
- **VS Code**: Add `/Applications/Visual Studio Code.app`
|
|
709
|
+
- **iTerm**: Add `/Applications/iTerm.app`
|
|
710
|
+
5. Restart the application after granting access
|
|
711
|
+
|
|
712
|
+
### Without Full Disk Access
|
|
713
|
+
|
|
714
|
+
All other tools work normally without Full Disk Access. Only checklist state features are affected:
|
|
715
|
+
- `get-checklist-state` will return an error explaining that database access is needed
|
|
716
|
+
- `get-note-markdown` will return plain list items without `[x]`/`[ ]` annotations (graceful fallback)
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
624
720
|
## Security and Privacy
|
|
625
721
|
|
|
626
722
|
- **Local only** - All operations happen locally via AppleScript. No data is sent to external servers.
|
|
@@ -637,8 +733,9 @@ If installed from source, use this configuration:
|
|
|
637
733
|
| macOS only | Apple Notes and AppleScript are macOS-specific |
|
|
638
734
|
| No attachment content | Attachments can be listed but not downloaded via AppleScript |
|
|
639
735
|
| No pinned notes | Pin status is not exposed via AppleScript |
|
|
640
|
-
|
|
|
736
|
+
| Limited rich formatting | Use `format: "html"` on create/update for headings, lists, bold, code blocks; some complex formatting may not render |
|
|
641
737
|
| Title matching | Most operations require exact title matches |
|
|
738
|
+
| Checklist state | Requires Full Disk Access to read done/undone state from the database |
|
|
642
739
|
|
|
643
740
|
### Backslash Escaping (Important for AI Agents)
|
|
644
741
|
|
|
@@ -717,3 +814,7 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
717
814
|
## Contributing
|
|
718
815
|
|
|
719
816
|
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
817
|
+
|
|
818
|
+
## Related Projects
|
|
819
|
+
|
|
820
|
+
- [apple-mail-mcp](https://github.com/sweetrb/apple-mail-mcp) - MCP server for Apple Mail
|
package/build/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
25
25
|
import { z } from "zod";
|
|
26
26
|
import { AppleNotesManager } from "./services/appleNotesManager.js";
|
|
27
27
|
import { getSyncStatus, withSyncAwarenessSync } from "./utils/syncDetection.js";
|
|
28
|
+
import { getChecklistItems, hasFullDiskAccess } from "./utils/checklistParser.js";
|
|
28
29
|
// Read version from package.json to keep it in sync
|
|
29
30
|
const require = createRequire(import.meta.url);
|
|
30
31
|
const { version } = require("../package.json");
|
|
@@ -112,9 +113,14 @@ const folderNameSchema = {
|
|
|
112
113
|
server.tool("create-note", {
|
|
113
114
|
title: z.string().min(1, "Title is required"),
|
|
114
115
|
content: z.string().min(1, "Content is required"),
|
|
116
|
+
format: z
|
|
117
|
+
.enum(["plaintext", "html"])
|
|
118
|
+
.optional()
|
|
119
|
+
.default("plaintext")
|
|
120
|
+
.describe("Content format: 'plaintext' (default) or 'html' for rich formatting"),
|
|
115
121
|
tags: z.array(z.string()).optional().describe("Tags for organization"),
|
|
116
|
-
}, withErrorHandling(({ title, content, tags = [] }) => {
|
|
117
|
-
const note = notesManager.createNote(title, content, tags);
|
|
122
|
+
}, withErrorHandling(({ title, content, format = "plaintext", tags = [] }) => {
|
|
123
|
+
const note = notesManager.createNote(title, content, tags, undefined, undefined, format);
|
|
118
124
|
if (!note) {
|
|
119
125
|
return errorResponse(`Failed to create note "${title}". Check that Notes.app is configured and accessible.`);
|
|
120
126
|
}
|
|
@@ -126,11 +132,18 @@ server.tool("search-notes", {
|
|
|
126
132
|
searchContent: z.boolean().optional().describe("Search note content instead of titles"),
|
|
127
133
|
account: z.string().optional().describe("Account to search in"),
|
|
128
134
|
folder: z.string().optional().describe("Limit search to a specific folder"),
|
|
129
|
-
|
|
135
|
+
modifiedSince: z
|
|
136
|
+
.string()
|
|
137
|
+
.optional()
|
|
138
|
+
.describe("ISO 8601 date string to filter notes modified on or after this date (e.g., '2025-01-01'). Useful for searching only recent notes in large collections."),
|
|
139
|
+
limit: z.number().int().positive().optional().describe("Maximum number of results to return"),
|
|
140
|
+
}, withErrorHandling(({ query, searchContent = false, account, folder, modifiedSince, limit }) => {
|
|
130
141
|
// Use sync-aware wrapper for this read operation
|
|
131
|
-
const { result: notes, syncBefore, syncInterference, } = withSyncAwarenessSync("search-notes", () => notesManager.searchNotes(query, searchContent, account, folder));
|
|
142
|
+
const { result: notes, syncBefore, syncInterference, } = withSyncAwarenessSync("search-notes", () => notesManager.searchNotes(query, searchContent, account, folder, modifiedSince, limit));
|
|
132
143
|
const searchType = searchContent ? "content" : "titles";
|
|
133
144
|
const folderInfo = folder ? ` in folder "${folder}"` : "";
|
|
145
|
+
const dateInfo = modifiedSince ? ` modified since ${modifiedSince}` : "";
|
|
146
|
+
const limitInfo = limit ? ` (limit: ${limit})` : "";
|
|
134
147
|
// Build sync warning if needed
|
|
135
148
|
const syncWarnings = [];
|
|
136
149
|
if (syncBefore.syncDetected) {
|
|
@@ -141,7 +154,7 @@ server.tool("search-notes", {
|
|
|
141
154
|
}
|
|
142
155
|
const syncNote = syncWarnings.length > 0 ? `\n\n${syncWarnings.join(" ")}` : "";
|
|
143
156
|
if (notes.length === 0) {
|
|
144
|
-
return successResponse(`No notes found matching "${query}" in ${searchType}${folderInfo}${syncNote}`);
|
|
157
|
+
return successResponse(`No notes found matching "${query}" in ${searchType}${folderInfo}${dateInfo}${syncNote}`);
|
|
145
158
|
}
|
|
146
159
|
// Format each note with ID and folder info, highlighting Recently Deleted
|
|
147
160
|
const noteList = notes
|
|
@@ -156,7 +169,7 @@ server.tool("search-notes", {
|
|
|
156
169
|
return ` - ${n.title}${idSuffix}`;
|
|
157
170
|
})
|
|
158
171
|
.join("\n");
|
|
159
|
-
return successResponse(`Found ${notes.length} notes (searched ${searchType}${folderInfo}):\n${noteList}${syncNote}`);
|
|
172
|
+
return successResponse(`Found ${notes.length} notes (searched ${searchType}${folderInfo}${dateInfo}${limitInfo}):\n${noteList}${syncNote}`);
|
|
160
173
|
}, "Error searching notes"));
|
|
161
174
|
// --- get-note-content ---
|
|
162
175
|
server.tool("get-note-content", {
|
|
@@ -244,11 +257,16 @@ server.tool("update-note", {
|
|
|
244
257
|
title: z.string().optional().describe("Current note title (use id instead when available)"),
|
|
245
258
|
newTitle: z.string().optional().describe("New title for the note"),
|
|
246
259
|
newContent: z.string().min(1, "New content is required"),
|
|
260
|
+
format: z
|
|
261
|
+
.enum(["plaintext", "html"])
|
|
262
|
+
.optional()
|
|
263
|
+
.default("plaintext")
|
|
264
|
+
.describe("Content format: 'plaintext' (default) or 'html' for rich formatting"),
|
|
247
265
|
account: z
|
|
248
266
|
.string()
|
|
249
267
|
.optional()
|
|
250
268
|
.describe("Account containing the note (ignored if id is provided)"),
|
|
251
|
-
}, withErrorHandling(({ id, title, newTitle, newContent, account }) => {
|
|
269
|
+
}, withErrorHandling(({ id, title, newTitle, newContent, format = "plaintext", account }) => {
|
|
252
270
|
// Prefer ID-based update if provided
|
|
253
271
|
if (id) {
|
|
254
272
|
// Check for password protection first for better error message
|
|
@@ -259,7 +277,7 @@ server.tool("update-note", {
|
|
|
259
277
|
if (note.passwordProtected) {
|
|
260
278
|
return errorResponse(`Note "${note.title}" is password-protected and cannot be updated. Unlock it in Notes.app first.`);
|
|
261
279
|
}
|
|
262
|
-
const success = notesManager.updateNoteById(id, newTitle, newContent);
|
|
280
|
+
const success = notesManager.updateNoteById(id, newTitle, newContent, format);
|
|
263
281
|
if (!success) {
|
|
264
282
|
return errorResponse(`Failed to update note "${note.title}"`);
|
|
265
283
|
}
|
|
@@ -282,7 +300,7 @@ server.tool("update-note", {
|
|
|
282
300
|
if (note.passwordProtected) {
|
|
283
301
|
return errorResponse(`Note "${title}" is password-protected and cannot be updated. Unlock it in Notes.app first.`);
|
|
284
302
|
}
|
|
285
|
-
const success = notesManager.updateNote(title, newTitle, newContent, account);
|
|
303
|
+
const success = notesManager.updateNote(title, newTitle, newContent, account, format);
|
|
286
304
|
if (!success) {
|
|
287
305
|
return errorResponse(`Failed to update note "${title}"`);
|
|
288
306
|
}
|
|
@@ -377,12 +395,19 @@ server.tool("move-note", {
|
|
|
377
395
|
server.tool("list-notes", {
|
|
378
396
|
account: z.string().optional().describe("Account to list notes from"),
|
|
379
397
|
folder: z.string().optional().describe("Filter to specific folder"),
|
|
380
|
-
|
|
398
|
+
modifiedSince: z
|
|
399
|
+
.string()
|
|
400
|
+
.optional()
|
|
401
|
+
.describe("ISO 8601 date string to filter notes modified on or after this date (e.g., '2025-01-01'). Useful for listing only recent notes in large collections."),
|
|
402
|
+
limit: z.number().int().positive().optional().describe("Maximum number of notes to return"),
|
|
403
|
+
}, withErrorHandling(({ account, folder, modifiedSince, limit }) => {
|
|
381
404
|
// Use sync-aware wrapper for this read operation
|
|
382
|
-
const { result: notes, syncBefore, syncInterference, } = withSyncAwarenessSync("list-notes", () => notesManager.listNotes(account, folder));
|
|
405
|
+
const { result: notes, syncBefore, syncInterference, } = withSyncAwarenessSync("list-notes", () => notesManager.listNotes(account, folder, modifiedSince, limit));
|
|
383
406
|
// Build context string for the response
|
|
384
407
|
const location = folder ? ` in folder "${folder}"` : "";
|
|
385
408
|
const acct = account ? ` (${account})` : "";
|
|
409
|
+
const dateInfo = modifiedSince ? ` modified since ${modifiedSince}` : "";
|
|
410
|
+
const limitInfo = limit ? ` (limit: ${limit})` : "";
|
|
386
411
|
// Build sync warning if needed
|
|
387
412
|
const syncWarnings = [];
|
|
388
413
|
if (syncBefore.syncDetected) {
|
|
@@ -393,10 +418,10 @@ server.tool("list-notes", {
|
|
|
393
418
|
}
|
|
394
419
|
const syncNote = syncWarnings.length > 0 ? `\n\n${syncWarnings.join(" ")}` : "";
|
|
395
420
|
if (notes.length === 0) {
|
|
396
|
-
return successResponse(`No notes found${location}${acct}${syncNote}`);
|
|
421
|
+
return successResponse(`No notes found${location}${acct}${dateInfo}${syncNote}`);
|
|
397
422
|
}
|
|
398
423
|
const noteList = notes.map((t) => ` - ${t}`).join("\n");
|
|
399
|
-
return successResponse(`Found ${notes.length} notes${location}${acct}:\n${noteList}${syncNote}`);
|
|
424
|
+
return successResponse(`Found ${notes.length} notes${location}${acct}${dateInfo}${limitInfo}:\n${noteList}${syncNote}`);
|
|
400
425
|
}, "Error listing notes"));
|
|
401
426
|
// =============================================================================
|
|
402
427
|
// Folder Tools
|
|
@@ -509,7 +534,12 @@ server.tool("health-check", {}, withErrorHandling(() => {
|
|
|
509
534
|
return ` ${icon} ${c.name}: ${c.message}`;
|
|
510
535
|
})
|
|
511
536
|
.join("\n");
|
|
512
|
-
|
|
537
|
+
// Check Full Disk Access for checklist features
|
|
538
|
+
const fdaAvailable = hasFullDiskAccess();
|
|
539
|
+
const fdaLine = fdaAvailable
|
|
540
|
+
? " ✓ full_disk_access: Granted (checklist features available)"
|
|
541
|
+
: " ⓘ full_disk_access: Not granted (optional — needed for get-checklist-state and checklist annotations in get-note-markdown). Grant in System Settings > Privacy & Security > Full Disk Access.";
|
|
542
|
+
return successResponse(`${statusIcon} ${statusText}\n\n${checkLines}\n${fdaLine}`);
|
|
513
543
|
}, "Error running health check"));
|
|
514
544
|
// --- get-notes-stats ---
|
|
515
545
|
server.tool("get-notes-stats", {}, withErrorHandling(() => {
|
|
@@ -662,6 +692,28 @@ server.tool("get-note-markdown", {
|
|
|
662
692
|
}
|
|
663
693
|
return successResponse(markdown);
|
|
664
694
|
}, "Error getting note as markdown"));
|
|
695
|
+
// --- get-checklist-state ---
|
|
696
|
+
server.tool("get-checklist-state", {
|
|
697
|
+
id: z.string().min(1, "Note ID is required. Use search-notes to find the note ID first."),
|
|
698
|
+
}, withErrorHandling(({ id }) => {
|
|
699
|
+
// Verify the note exists and is accessible
|
|
700
|
+
const note = notesManager.getNoteById(id);
|
|
701
|
+
if (!note) {
|
|
702
|
+
return errorResponse(`Note with ID "${id}" not found`);
|
|
703
|
+
}
|
|
704
|
+
if (note.passwordProtected) {
|
|
705
|
+
return errorResponse(`Note "${note.title}" is password-protected and cannot be read. Unlock it in Notes.app first.`);
|
|
706
|
+
}
|
|
707
|
+
const result = getChecklistItems(id);
|
|
708
|
+
if (!result.items) {
|
|
709
|
+
return errorResponse(result.message || "Failed to read checklist state.");
|
|
710
|
+
}
|
|
711
|
+
const summary = result.items
|
|
712
|
+
.map((item) => `${item.done ? "[x]" : "[ ]"} ${item.text}`)
|
|
713
|
+
.join("\n");
|
|
714
|
+
const checked = result.items.filter((i) => i.done).length;
|
|
715
|
+
return successResponse(`Checklist for "${note.title}" (${checked}/${result.items.length} done):\n${summary}`);
|
|
716
|
+
}, "Error reading checklist state"));
|
|
665
717
|
// =============================================================================
|
|
666
718
|
// Server Startup
|
|
667
719
|
// =============================================================================
|