apple-notes-mcp 2.5.6 → 2.5.8

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 (34) hide show
  1. package/README.md +8 -5
  2. package/build/index.js +42669 -1080
  3. package/package.json +3 -3
  4. package/build/index.test.js +0 -446
  5. package/build/services/__fixtures__/notesNormalizedHtml.js +0 -32
  6. package/build/services/appleNotesManager.js +0 -2629
  7. package/build/services/appleNotesManager.test.js +0 -2389
  8. package/build/services/attachmentSave.test.js +0 -85
  9. package/build/services/fileConfig.js +0 -51
  10. package/build/services/fileConfig.test.js +0 -48
  11. package/build/services/notesHtmlMarkdown.test.js +0 -55
  12. package/build/tools/doctor.js +0 -50
  13. package/build/tools/doctor.test.js +0 -42
  14. package/build/tools/resourcesAndPrompts.js +0 -70
  15. package/build/tools/resourcesAndPrompts.test.js +0 -63
  16. package/build/types.js +0 -13
  17. package/build/utils/applescript.js +0 -421
  18. package/build/utils/applescript.test.js +0 -342
  19. package/build/utils/attachmentFs.js +0 -97
  20. package/build/utils/attachmentFs.test.js +0 -69
  21. package/build/utils/checklistParser.js +0 -259
  22. package/build/utils/checklistParser.test.js +0 -230
  23. package/build/utils/contentWarnings.js +0 -44
  24. package/build/utils/contentWarnings.test.js +0 -52
  25. package/build/utils/hashtags.js +0 -56
  26. package/build/utils/hashtags.test.js +0 -45
  27. package/build/utils/jxa.js +0 -139
  28. package/build/utils/jxa.test.js +0 -134
  29. package/build/utils/noteMetadata.js +0 -135
  30. package/build/utils/noteMetadata.test.js +0 -106
  31. package/build/utils/protobuf.js +0 -151
  32. package/build/utils/protobuf.test.js +0 -138
  33. package/build/utils/syncDetection.js +0 -242
  34. package/build/utils/syncDetection.test.js +0 -228
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.5.6",
3
+ "version": "2.5.8",
4
4
  "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -52,12 +52,12 @@
52
52
  "@typescript-eslint/eslint-plugin": "^8.0.0",
53
53
  "@typescript-eslint/parser": "^8.0.0",
54
54
  "@vitest/coverage-v8": "^3.2.6",
55
+ "esbuild": "^0.28.1",
55
56
  "eslint": "^9.0.0",
56
57
  "globals": "^17.0.0",
57
58
  "husky": "^9.1.7",
58
59
  "lint-staged": "^16.2.7",
59
60
  "prettier": "^3.0.0",
60
- "tsc-alias": "^1.8.10",
61
61
  "tsconfig-paths": "^4.2.0",
62
62
  "typescript": "^5.0.0",
63
63
  "typescript-eslint": "^8.51.0",
@@ -73,7 +73,7 @@
73
73
  ]
74
74
  },
75
75
  "scripts": {
76
- "build": "tsc && tsc-alias",
76
+ "build": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --format=esm --outfile=build/index.js --banner:js=\"import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);\"",
77
77
  "start": "node build/index.js",
78
78
  "dev": "tsc --watch",
79
79
  "test": "vitest run",
@@ -1,446 +0,0 @@
1
- /**
2
- * Integration Tests for Apple Notes MCP Server
3
- *
4
- * These tests verify the MCP tool handlers work correctly.
5
- * The AppleNotesManager is mocked to test tool response formatting
6
- * and error handling without requiring actual AppleScript execution.
7
- */
8
- import { describe, it, expect, vi, beforeEach } from "vitest";
9
- import { AppleNotesManager } from "./services/appleNotesManager.js";
10
- // Mock the AppleNotesManager class
11
- vi.mock("@/services/appleNotesManager.js", () => {
12
- return {
13
- AppleNotesManager: vi.fn().mockImplementation(() => ({
14
- createNote: vi.fn(),
15
- searchNotes: vi.fn(),
16
- getNoteContent: vi.fn(),
17
- getNoteById: vi.fn(),
18
- getNoteDetails: vi.fn(),
19
- updateNote: vi.fn(),
20
- deleteNote: vi.fn(),
21
- moveNote: vi.fn(),
22
- listNotes: vi.fn(),
23
- listFolders: vi.fn(),
24
- createFolder: vi.fn(),
25
- deleteFolder: vi.fn(),
26
- listAccounts: vi.fn(),
27
- })),
28
- };
29
- });
30
- // Create a typed mock manager for testing
31
- const createMockManager = () => {
32
- const manager = new AppleNotesManager();
33
- return manager;
34
- };
35
- let mockManager;
36
- // =============================================================================
37
- // Response Helper Tests
38
- // =============================================================================
39
- describe("Response Helpers", () => {
40
- // We test these indirectly through the tool handlers since they're not exported
41
- // The pattern we're looking for is consistent response formatting
42
- beforeEach(() => {
43
- vi.clearAllMocks();
44
- mockManager = createMockManager();
45
- });
46
- describe("success response format", () => {
47
- it("returns content array with text type", async () => {
48
- mockManager.createNote.mockReturnValue({
49
- id: "123",
50
- title: "Test Note",
51
- content: "Test content",
52
- tags: [],
53
- created: new Date(),
54
- modified: new Date(),
55
- account: "iCloud",
56
- });
57
- // Simulate what the tool handler would return
58
- const response = {
59
- content: [{ type: "text", text: 'Note created: "Test Note"' }],
60
- };
61
- expect(response.content).toHaveLength(1);
62
- expect(response.content[0].type).toBe("text");
63
- expect(response.content[0].text).toContain("Note created");
64
- });
65
- });
66
- describe("error response format", () => {
67
- it("includes isError flag", () => {
68
- const errorResponse = {
69
- content: [{ type: "text", text: "Failed to create note" }],
70
- isError: true,
71
- };
72
- expect(errorResponse.isError).toBe(true);
73
- expect(errorResponse.content[0].text).toContain("Failed");
74
- });
75
- });
76
- });
77
- // =============================================================================
78
- // Tool Handler Tests
79
- // =============================================================================
80
- describe("Tool Handlers", () => {
81
- beforeEach(() => {
82
- vi.clearAllMocks();
83
- mockManager = createMockManager();
84
- });
85
- // ---------------------------------------------------------------------------
86
- // create-note
87
- // ---------------------------------------------------------------------------
88
- describe("create-note tool", () => {
89
- it("calls createNote with correct parameters", () => {
90
- const params = {
91
- title: "Shopping List",
92
- content: "Eggs, Milk, Bread",
93
- tags: ["groceries"],
94
- };
95
- mockManager.createNote.mockReturnValue({
96
- id: "123",
97
- title: params.title,
98
- content: params.content,
99
- tags: params.tags,
100
- created: new Date(),
101
- modified: new Date(),
102
- account: "iCloud",
103
- });
104
- // Simulate the tool handler call
105
- const result = mockManager.createNote(params.title, params.content, params.tags);
106
- expect(mockManager.createNote).toHaveBeenCalledWith("Shopping List", "Eggs, Milk, Bread", [
107
- "groceries",
108
- ]);
109
- expect(result).not.toBeNull();
110
- });
111
- it("handles creation failure gracefully", () => {
112
- mockManager.createNote.mockReturnValue(null);
113
- const result = mockManager.createNote("Test", "Content", []);
114
- expect(result).toBeNull();
115
- });
116
- });
117
- // ---------------------------------------------------------------------------
118
- // search-notes
119
- // ---------------------------------------------------------------------------
120
- describe("search-notes tool", () => {
121
- it("returns list of matching note titles", () => {
122
- mockManager.searchNotes.mockReturnValue([
123
- { title: "Meeting Notes", id: "1" },
124
- { title: "Project Notes", id: "2" },
125
- ]);
126
- const results = mockManager.searchNotes("notes", false, undefined);
127
- expect(results).toHaveLength(2);
128
- expect(results[0].title).toBe("Meeting Notes");
129
- });
130
- it("handles empty search results", () => {
131
- mockManager.searchNotes.mockReturnValue([]);
132
- const results = mockManager.searchNotes("nonexistent", false);
133
- expect(results).toHaveLength(0);
134
- });
135
- it("supports content search", () => {
136
- mockManager.searchNotes.mockReturnValue([{ title: "Found Note", id: "1" }]);
137
- mockManager.searchNotes("keyword", true, "iCloud");
138
- expect(mockManager.searchNotes).toHaveBeenCalledWith("keyword", true, "iCloud");
139
- });
140
- });
141
- // ---------------------------------------------------------------------------
142
- // get-note-content
143
- // ---------------------------------------------------------------------------
144
- describe("get-note-content tool", () => {
145
- it("returns note HTML content", () => {
146
- const htmlContent = "<div>Shopping List</div><div>- Eggs</div>";
147
- mockManager.getNoteContent.mockReturnValue(htmlContent);
148
- const result = mockManager.getNoteContent("Shopping List");
149
- expect(result).toBe(htmlContent);
150
- });
151
- it("returns empty string for missing note", () => {
152
- mockManager.getNoteContent.mockReturnValue("");
153
- const result = mockManager.getNoteContent("Missing");
154
- expect(result).toBe("");
155
- });
156
- });
157
- // ---------------------------------------------------------------------------
158
- // get-note-by-id
159
- // ---------------------------------------------------------------------------
160
- describe("get-note-by-id tool", () => {
161
- it("returns note metadata as JSON", () => {
162
- const note = {
163
- id: "x-coredata://ABC123/ICNote/p100",
164
- title: "Test Note",
165
- content: "",
166
- tags: [],
167
- created: new Date("2025-01-01"),
168
- modified: new Date("2025-01-02"),
169
- shared: false,
170
- passwordProtected: false,
171
- };
172
- mockManager.getNoteById.mockReturnValue(note);
173
- const result = mockManager.getNoteById("x-coredata://ABC123/ICNote/p100");
174
- expect(result).not.toBeNull();
175
- expect(result?.id).toBe("x-coredata://ABC123/ICNote/p100");
176
- expect(result?.title).toBe("Test Note");
177
- });
178
- it("returns null for invalid ID", () => {
179
- mockManager.getNoteById.mockReturnValue(null);
180
- const result = mockManager.getNoteById("invalid-id");
181
- expect(result).toBeNull();
182
- });
183
- });
184
- // ---------------------------------------------------------------------------
185
- // get-note-details
186
- // ---------------------------------------------------------------------------
187
- describe("get-note-details tool", () => {
188
- it("returns full note metadata", () => {
189
- const note = {
190
- id: "x-coredata://ABC/ICNote/p1",
191
- title: "Project Plan",
192
- content: "",
193
- tags: [],
194
- created: new Date(),
195
- modified: new Date(),
196
- shared: true,
197
- passwordProtected: false,
198
- account: "iCloud",
199
- };
200
- mockManager.getNoteDetails.mockReturnValue(note);
201
- const result = mockManager.getNoteDetails("Project Plan");
202
- expect(result?.shared).toBe(true);
203
- expect(result?.account).toBe("iCloud");
204
- });
205
- });
206
- // ---------------------------------------------------------------------------
207
- // update-note
208
- // ---------------------------------------------------------------------------
209
- describe("update-note tool", () => {
210
- it("returns true on successful update", () => {
211
- mockManager.updateNote.mockReturnValue(true);
212
- const result = mockManager.updateNote("Old Title", "New Title", "New content");
213
- expect(result).toBe(true);
214
- });
215
- it("returns false when note not found", () => {
216
- mockManager.updateNote.mockReturnValue(false);
217
- const result = mockManager.updateNote("Missing", undefined, "Content");
218
- expect(result).toBe(false);
219
- });
220
- it("preserves title when newTitle is undefined", () => {
221
- mockManager.updateNote.mockReturnValue(true);
222
- mockManager.updateNote("Keep Title", undefined, "Updated content");
223
- expect(mockManager.updateNote).toHaveBeenCalledWith("Keep Title", undefined, "Updated content");
224
- });
225
- });
226
- // ---------------------------------------------------------------------------
227
- // delete-note
228
- // ---------------------------------------------------------------------------
229
- describe("delete-note tool", () => {
230
- it("returns true on successful deletion", () => {
231
- mockManager.deleteNote.mockReturnValue(true);
232
- const result = mockManager.deleteNote("Old Note");
233
- expect(result).toBe(true);
234
- });
235
- it("returns false when deletion fails", () => {
236
- mockManager.deleteNote.mockReturnValue(false);
237
- const result = mockManager.deleteNote("Protected Note");
238
- expect(result).toBe(false);
239
- });
240
- });
241
- // ---------------------------------------------------------------------------
242
- // move-note
243
- // ---------------------------------------------------------------------------
244
- describe("move-note tool", () => {
245
- it("returns true when move succeeds", () => {
246
- mockManager.moveNote.mockReturnValue(true);
247
- const result = mockManager.moveNote("My Note", "Archive");
248
- expect(result).toBe(true);
249
- expect(mockManager.moveNote).toHaveBeenCalledWith("My Note", "Archive");
250
- });
251
- it("returns false when source note not found", () => {
252
- mockManager.moveNote.mockReturnValue(false);
253
- const result = mockManager.moveNote("Missing", "Archive");
254
- expect(result).toBe(false);
255
- });
256
- it("returns false when destination folder not found", () => {
257
- mockManager.moveNote.mockReturnValue(false);
258
- const result = mockManager.moveNote("My Note", "Nonexistent");
259
- expect(result).toBe(false);
260
- });
261
- });
262
- // ---------------------------------------------------------------------------
263
- // list-notes
264
- // ---------------------------------------------------------------------------
265
- describe("list-notes tool", () => {
266
- it("returns array of note titles", () => {
267
- mockManager.listNotes.mockReturnValue(["Note A", "Note B", "Note C"]);
268
- const result = mockManager.listNotes();
269
- expect(result).toEqual(["Note A", "Note B", "Note C"]);
270
- });
271
- it("returns empty array when no notes", () => {
272
- mockManager.listNotes.mockReturnValue([]);
273
- const result = mockManager.listNotes();
274
- expect(result).toEqual([]);
275
- });
276
- it("filters by folder when specified", () => {
277
- mockManager.listNotes.mockReturnValue(["Work Note 1", "Work Note 2"]);
278
- mockManager.listNotes("iCloud", "Work");
279
- expect(mockManager.listNotes).toHaveBeenCalledWith("iCloud", "Work");
280
- });
281
- });
282
- // ---------------------------------------------------------------------------
283
- // list-folders
284
- // ---------------------------------------------------------------------------
285
- describe("list-folders tool", () => {
286
- it("returns array of Folder objects", () => {
287
- mockManager.listFolders.mockReturnValue([
288
- { id: "", name: "Notes", account: "iCloud" },
289
- { id: "", name: "Archive", account: "iCloud" },
290
- ]);
291
- const result = mockManager.listFolders();
292
- expect(result).toHaveLength(2);
293
- expect(result[0].name).toBe("Notes");
294
- });
295
- it("returns empty array when no folders", () => {
296
- mockManager.listFolders.mockReturnValue([]);
297
- const result = mockManager.listFolders();
298
- expect(result).toEqual([]);
299
- });
300
- });
301
- // ---------------------------------------------------------------------------
302
- // create-folder
303
- // ---------------------------------------------------------------------------
304
- describe("create-folder tool", () => {
305
- it("returns Folder object on success", () => {
306
- mockManager.createFolder.mockReturnValue({
307
- id: "folder-123",
308
- name: "New Project",
309
- account: "iCloud",
310
- });
311
- const result = mockManager.createFolder("New Project");
312
- expect(result).not.toBeNull();
313
- expect(result?.name).toBe("New Project");
314
- });
315
- it("returns null when folder already exists", () => {
316
- mockManager.createFolder.mockReturnValue(null);
317
- const result = mockManager.createFolder("Existing");
318
- expect(result).toBeNull();
319
- });
320
- });
321
- // ---------------------------------------------------------------------------
322
- // delete-folder
323
- // ---------------------------------------------------------------------------
324
- describe("delete-folder tool", () => {
325
- it("returns true on successful deletion", () => {
326
- mockManager.deleteFolder.mockReturnValue(true);
327
- const result = mockManager.deleteFolder("Empty Folder");
328
- expect(result).toBe(true);
329
- });
330
- it("returns false when folder contains notes", () => {
331
- mockManager.deleteFolder.mockReturnValue(false);
332
- const result = mockManager.deleteFolder("Non-Empty");
333
- expect(result).toBe(false);
334
- });
335
- });
336
- // ---------------------------------------------------------------------------
337
- // list-accounts
338
- // ---------------------------------------------------------------------------
339
- describe("list-accounts tool", () => {
340
- it("returns array of Account objects", () => {
341
- mockManager.listAccounts.mockReturnValue([
342
- { name: "iCloud" },
343
- { name: "Gmail" },
344
- { name: "Exchange" },
345
- ]);
346
- const result = mockManager.listAccounts();
347
- expect(result).toHaveLength(3);
348
- expect(result[0].name).toBe("iCloud");
349
- });
350
- it("returns empty array when no accounts configured", () => {
351
- mockManager.listAccounts.mockReturnValue([]);
352
- const result = mockManager.listAccounts();
353
- expect(result).toEqual([]);
354
- });
355
- });
356
- });
357
- // =============================================================================
358
- // Error Handling Tests
359
- // =============================================================================
360
- describe("Error Handling", () => {
361
- beforeEach(() => {
362
- vi.clearAllMocks();
363
- mockManager = createMockManager();
364
- });
365
- it("handles manager throwing exceptions", () => {
366
- mockManager.createNote.mockImplementation(() => {
367
- throw new Error("Unexpected error");
368
- });
369
- expect(() => mockManager.createNote("Test", "Content", [])).toThrow("Unexpected error");
370
- });
371
- it("handles null returns gracefully", () => {
372
- mockManager.getNoteById.mockReturnValue(null);
373
- mockManager.getNoteDetails.mockReturnValue(null);
374
- mockManager.createNote.mockReturnValue(null);
375
- mockManager.createFolder.mockReturnValue(null);
376
- expect(mockManager.getNoteById("invalid")).toBeNull();
377
- expect(mockManager.getNoteDetails("missing")).toBeNull();
378
- expect(mockManager.createNote("fail", "content", [])).toBeNull();
379
- expect(mockManager.createFolder("exists")).toBeNull();
380
- });
381
- it("handles empty string returns", () => {
382
- mockManager.getNoteContent.mockReturnValue("");
383
- expect(mockManager.getNoteContent("missing")).toBe("");
384
- });
385
- it("handles empty array returns", () => {
386
- mockManager.searchNotes.mockReturnValue([]);
387
- mockManager.listNotes.mockReturnValue([]);
388
- mockManager.listFolders.mockReturnValue([]);
389
- mockManager.listAccounts.mockReturnValue([]);
390
- expect(mockManager.searchNotes("none")).toEqual([]);
391
- expect(mockManager.listNotes()).toEqual([]);
392
- expect(mockManager.listFolders()).toEqual([]);
393
- expect(mockManager.listAccounts()).toEqual([]);
394
- });
395
- it("handles false returns for destructive operations", () => {
396
- mockManager.deleteNote.mockReturnValue(false);
397
- mockManager.deleteFolder.mockReturnValue(false);
398
- mockManager.updateNote.mockReturnValue(false);
399
- mockManager.moveNote.mockReturnValue(false);
400
- expect(mockManager.deleteNote("protected")).toBe(false);
401
- expect(mockManager.deleteFolder("non-empty")).toBe(false);
402
- expect(mockManager.updateNote("missing", undefined, "content")).toBe(false);
403
- expect(mockManager.moveNote("missing", "folder")).toBe(false);
404
- });
405
- });
406
- // =============================================================================
407
- // Input Validation Tests
408
- // =============================================================================
409
- describe("Input Validation", () => {
410
- beforeEach(() => {
411
- vi.clearAllMocks();
412
- mockManager = createMockManager();
413
- });
414
- describe("title parameter", () => {
415
- it("handles titles with special characters", () => {
416
- mockManager.getNoteContent.mockReturnValue("content");
417
- mockManager.getNoteContent('Note\'s "Title" with <special> chars');
418
- expect(mockManager.getNoteContent).toHaveBeenCalledWith('Note\'s "Title" with <special> chars');
419
- });
420
- it("handles titles with unicode", () => {
421
- mockManager.getNoteContent.mockReturnValue("content");
422
- mockManager.getNoteContent("日本語ノート 🎉");
423
- expect(mockManager.getNoteContent).toHaveBeenCalledWith("日本語ノート 🎉");
424
- });
425
- });
426
- describe("account parameter", () => {
427
- it("defaults to iCloud when not specified", () => {
428
- mockManager.createNote.mockReturnValue({
429
- id: "1",
430
- title: "Test",
431
- content: "Content",
432
- tags: [],
433
- created: new Date(),
434
- modified: new Date(),
435
- account: "iCloud",
436
- });
437
- const result = mockManager.createNote("Test", "Content", []);
438
- expect(result?.account).toBe("iCloud");
439
- });
440
- it("uses specified account", () => {
441
- mockManager.listNotes.mockReturnValue(["Gmail Note"]);
442
- mockManager.listNotes("Gmail");
443
- expect(mockManager.listNotes).toHaveBeenCalledWith("Gmail");
444
- });
445
- });
446
- });
@@ -1,32 +0,0 @@
1
- export const NOTES_NORMALIZED_HTML_FIXTURES = [
2
- {
3
- name: "headingAndParagraphs",
4
- description: "An <h1> title and two <div> paragraphs separated by a spacer row",
5
- html: "<div><h1>Meeting Notes</h1></div><div>First line.</div><div><br></div><div>Second line.</div>",
6
- expectedMarkdown: "# Meeting Notes\n\nFirst line.\n \n\nSecond line.",
7
- },
8
- {
9
- name: "bulletList",
10
- description: "An <h2> section heading followed by a native <ul> bullet list",
11
- html: "<div><h2>Tasks</h2></div><ul><li>Buy milk</li><li>Walk dog</li></ul>",
12
- expectedMarkdown: "## Tasks\n\n- Buy milk\n- Walk dog",
13
- },
14
- {
15
- name: "inlineEmphasis",
16
- description: "Inline <b> and <i> emphasis inside a paragraph div",
17
- html: "<div>This is <b>bold</b> and <i>italic</i> text.</div>",
18
- expectedMarkdown: "This is **bold** and _italic_ text.",
19
- },
20
- {
21
- name: "codeSpan",
22
- description: "A <tt> code span — the tag is dropped, only its text survives",
23
- html: "<div>Run <tt>npm install</tt> first.</div>",
24
- expectedMarkdown: "Run npm install first.",
25
- },
26
- {
27
- name: "spacerRuns",
28
- description: "Consecutive <div><br></div> spacer rows between two paragraphs",
29
- html: "<div>A</div><div><br></div><div><br></div><div>B</div>",
30
- expectedMarkdown: "A\n \n\n \n\nB",
31
- },
32
- ];