apple-notes-mcp 1.2.19 → 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 CHANGED
@@ -143,6 +143,9 @@ Searches for notes by title or content.
143
143
  | `query` | string | Yes | Text to search for |
144
144
  | `searchContent` | boolean | No | If `true`, searches note body; if `false` (default), searches titles only |
145
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 |
146
149
 
147
150
  **Example - Search titles:**
148
151
  ```json
@@ -159,6 +162,16 @@ Searches for notes by title or content.
159
162
  }
160
163
  ```
161
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
+
162
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.
163
176
 
164
177
  ---
@@ -356,12 +369,14 @@ Moves a note to a different folder.
356
369
 
357
370
  #### `list-notes`
358
371
 
359
- Lists all notes, optionally filtered by folder.
372
+ Lists all notes, optionally filtered by folder, date, and limit.
360
373
 
361
374
  | Parameter | Type | Required | Description |
362
375
  |-----------|------|----------|-------------|
363
376
  | `account` | string | No | Account to list notes from (defaults to iCloud) |
364
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 |
365
380
 
366
381
  **Example - All notes:**
367
382
  ```json
@@ -375,6 +390,14 @@ Lists all notes, optionally filtered by folder.
375
390
  }
376
391
  ```
377
392
 
393
+ **Example - Recent notes with limit:**
394
+ ```json
395
+ {
396
+ "modifiedSince": "2025-06-01",
397
+ "limit": 20
398
+ }
399
+ ```
400
+
378
401
  **Returns:** List of note titles.
379
402
 
380
403
  ---
package/build/index.js CHANGED
@@ -132,11 +132,18 @@ server.tool("search-notes", {
132
132
  searchContent: z.boolean().optional().describe("Search note content instead of titles"),
133
133
  account: z.string().optional().describe("Account to search in"),
134
134
  folder: z.string().optional().describe("Limit search to a specific folder"),
135
- }, withErrorHandling(({ query, searchContent = false, account, folder }) => {
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 }) => {
136
141
  // Use sync-aware wrapper for this read operation
137
- 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));
138
143
  const searchType = searchContent ? "content" : "titles";
139
144
  const folderInfo = folder ? ` in folder "${folder}"` : "";
145
+ const dateInfo = modifiedSince ? ` modified since ${modifiedSince}` : "";
146
+ const limitInfo = limit ? ` (limit: ${limit})` : "";
140
147
  // Build sync warning if needed
141
148
  const syncWarnings = [];
142
149
  if (syncBefore.syncDetected) {
@@ -147,7 +154,7 @@ server.tool("search-notes", {
147
154
  }
148
155
  const syncNote = syncWarnings.length > 0 ? `\n\n${syncWarnings.join(" ")}` : "";
149
156
  if (notes.length === 0) {
150
- return successResponse(`No notes found matching "${query}" in ${searchType}${folderInfo}${syncNote}`);
157
+ return successResponse(`No notes found matching "${query}" in ${searchType}${folderInfo}${dateInfo}${syncNote}`);
151
158
  }
152
159
  // Format each note with ID and folder info, highlighting Recently Deleted
153
160
  const noteList = notes
@@ -162,7 +169,7 @@ server.tool("search-notes", {
162
169
  return ` - ${n.title}${idSuffix}`;
163
170
  })
164
171
  .join("\n");
165
- 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}`);
166
173
  }, "Error searching notes"));
167
174
  // --- get-note-content ---
168
175
  server.tool("get-note-content", {
@@ -388,12 +395,19 @@ server.tool("move-note", {
388
395
  server.tool("list-notes", {
389
396
  account: z.string().optional().describe("Account to list notes from"),
390
397
  folder: z.string().optional().describe("Filter to specific folder"),
391
- }, withErrorHandling(({ account, folder }) => {
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 }) => {
392
404
  // Use sync-aware wrapper for this read operation
393
- 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));
394
406
  // Build context string for the response
395
407
  const location = folder ? ` in folder "${folder}"` : "";
396
408
  const acct = account ? ` (${account})` : "";
409
+ const dateInfo = modifiedSince ? ` modified since ${modifiedSince}` : "";
410
+ const limitInfo = limit ? ` (limit: ${limit})` : "";
397
411
  // Build sync warning if needed
398
412
  const syncWarnings = [];
399
413
  if (syncBefore.syncDetected) {
@@ -404,10 +418,10 @@ server.tool("list-notes", {
404
418
  }
405
419
  const syncNote = syncWarnings.length > 0 ? `\n\n${syncWarnings.join(" ")}` : "";
406
420
  if (notes.length === 0) {
407
- return successResponse(`No notes found${location}${acct}${syncNote}`);
421
+ return successResponse(`No notes found${location}${acct}${dateInfo}${syncNote}`);
408
422
  }
409
423
  const noteList = notes.map((t) => ` - ${t}`).join("\n");
410
- 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}`);
411
425
  }, "Error listing notes"));
412
426
  // =============================================================================
413
427
  // Folder Tools
@@ -144,6 +144,33 @@ export function parseAppleScriptDate(appleScriptDate) {
144
144
  // Return parsed date if valid, otherwise current date as fallback
145
145
  return isNaN(parsed.getTime()) ? new Date() : parsed;
146
146
  }
147
+ /**
148
+ * Generates AppleScript code that creates a date variable with the given values.
149
+ *
150
+ * This approach is locale-independent, unlike `date "M/D/YYYY"` coercion which
151
+ * depends on the system's date format settings and would fail on non-US locales.
152
+ *
153
+ * @param date - JavaScript Date object
154
+ * @param varName - AppleScript variable name to assign (default: "thresholdDate")
155
+ * @returns AppleScript code that sets up the date variable
156
+ *
157
+ * @example
158
+ * buildAppleScriptDateVar(new Date("2025-06-15T00:00:00"))
159
+ * // Returns multi-line AppleScript that sets thresholdDate to June 15, 2025 midnight
160
+ */
161
+ export function buildAppleScriptDateVar(date, varName = "thresholdDate") {
162
+ const year = date.getFullYear();
163
+ const month = date.getMonth() + 1;
164
+ const day = date.getDate();
165
+ const timeInSeconds = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds();
166
+ return [
167
+ `set ${varName} to current date`,
168
+ `set year of ${varName} to ${year}`,
169
+ `set month of ${varName} to ${month}`,
170
+ `set day of ${varName} to ${day}`,
171
+ `set time of ${varName} to ${timeInSeconds}`,
172
+ ].join("\n");
173
+ }
147
174
  /**
148
175
  * Parses AppleScript note properties output into structured data.
149
176
  *
@@ -433,6 +460,8 @@ export class AppleNotesManager {
433
460
  * @param searchContent - If true, search note bodies; if false, search titles
434
461
  * @param account - Account to search in (defaults to iCloud)
435
462
  * @param folder - Optional folder to limit search to
463
+ * @param modifiedSince - Optional ISO 8601 date string to filter notes modified on or after this date
464
+ * @param limit - Optional maximum number of results to return (default: no limit)
436
465
  * @returns Array of matching notes (with minimal metadata)
437
466
  *
438
467
  * @example
@@ -445,25 +474,53 @@ export class AppleNotesManager {
445
474
  *
446
475
  * // Search within a specific folder
447
476
  * const workNotes = manager.searchNotes("deadline", false, "iCloud", "Work");
477
+ *
478
+ * // Search only recently modified notes
479
+ * const recentNotes = manager.searchNotes("todo", true, undefined, undefined, "2025-01-01");
480
+ *
481
+ * // Search with a result limit
482
+ * const topResults = manager.searchNotes("project", false, undefined, undefined, undefined, 10);
448
483
  * ```
449
484
  */
450
- searchNotes(query, searchContent = false, account, folder) {
485
+ searchNotes(query, searchContent = false, account, folder, modifiedSince, limit) {
451
486
  const targetAccount = this.resolveAccount(account);
452
487
  const safeQuery = escapeForAppleScript(query);
488
+ const safeLimit = limit !== undefined && limit > 0 ? Math.floor(limit) : undefined;
453
489
  // Build the where clause based on search type
454
490
  // AppleScript uses 'name' for title and 'body' for content
455
- const whereClause = searchContent
456
- ? `body contains "${safeQuery}"`
457
- : `name contains "${safeQuery}"`;
491
+ const whereParts = [];
492
+ if (searchContent) {
493
+ whereParts.push(`body contains "${safeQuery}"`);
494
+ }
495
+ else {
496
+ whereParts.push(`name contains "${safeQuery}"`);
497
+ }
498
+ // Add date filter if specified (uses locale-safe date variable)
499
+ let dateSetup = "";
500
+ if (modifiedSince) {
501
+ const date = new Date(modifiedSince);
502
+ if (!isNaN(date.getTime())) {
503
+ dateSetup = buildAppleScriptDateVar(date) + "\n";
504
+ whereParts.push(`modification date >= thresholdDate`);
505
+ }
506
+ }
507
+ const whereClause = whereParts.join(" and ");
458
508
  // Build the notes source - either all notes or notes in a specific folder
459
509
  const notesSource = folder ? `notes of folder "${escapeForAppleScript(folder)}"` : "notes";
510
+ // Build the limit logic for the repeat loop
511
+ // Note: The limit only reduces iteration over already-matched results from the whose clause,
512
+ // not the query itself. It controls output size, not AppleScript query performance.
513
+ const limitCheck = safeLimit !== undefined
514
+ ? `
515
+ if (count of resultList) >= ${safeLimit} then exit repeat`
516
+ : "";
460
517
  // Get names, IDs, and folder for each matching note
461
518
  // We use a repeat loop to get all properties, separated by a delimiter
462
519
  // Note: Some notes may have inaccessible containers, so we wrap in try/on error
463
520
  const searchCommand = `
464
- set matchingNotes to ${notesSource} where ${whereClause}
521
+ ${dateSetup}set matchingNotes to ${notesSource} where ${whereClause}
465
522
  set resultList to {}
466
- repeat with n in matchingNotes
523
+ repeat with n in matchingNotes${limitCheck}
467
524
  try
468
525
  set noteName to name of n
469
526
  set noteId to id of n
@@ -785,15 +842,60 @@ export class AppleNotesManager {
785
842
  return true;
786
843
  }
787
844
  /**
788
- * Lists all notes in an account, optionally filtered by folder.
845
+ * Lists all notes in an account, optionally filtered by folder, date, and limit.
789
846
  *
790
847
  * @param account - Account to list notes from (defaults to iCloud)
791
848
  * @param folder - Optional folder to filter by
849
+ * @param modifiedSince - Optional ISO 8601 date string to filter notes modified on or after this date
850
+ * @param limit - Optional maximum number of results to return (default: no limit)
792
851
  * @returns Array of note titles
793
852
  */
794
- listNotes(account, folder) {
853
+ listNotes(account, folder, modifiedSince, limit) {
795
854
  const targetAccount = this.resolveAccount(account);
796
- // Build command based on whether folder filter is specified
855
+ const safeLimit = limit !== undefined && limit > 0 ? Math.floor(limit) : undefined;
856
+ // When date or limit filters are needed, use a repeat loop for fine-grained control
857
+ if (modifiedSince || safeLimit !== undefined) {
858
+ const baseNotesSource = folder
859
+ ? `notes of folder "${escapeForAppleScript(folder)}"`
860
+ : "notes";
861
+ // Use whose clause for date filtering (locale-safe, no sort order assumption)
862
+ let dateSetup = "";
863
+ let notesSource = baseNotesSource;
864
+ if (modifiedSince) {
865
+ const date = new Date(modifiedSince);
866
+ if (!isNaN(date.getTime())) {
867
+ dateSetup = buildAppleScriptDateVar(date) + "\n";
868
+ notesSource = `(${baseNotesSource} whose modification date >= thresholdDate)`;
869
+ }
870
+ }
871
+ // Build the limit check
872
+ const limitCheck = safeLimit !== undefined
873
+ ? `
874
+ if (count of resultList) >= ${safeLimit} then exit repeat`
875
+ : "";
876
+ const listCommand = `
877
+ ${dateSetup}set resultList to {}
878
+ repeat with n in ${notesSource}${limitCheck}
879
+ set end of resultList to name of n
880
+ end repeat
881
+ set AppleScript's text item delimiters to "|||"
882
+ return resultList as text
883
+ `;
884
+ const script = buildAccountScopedScript({ account: targetAccount }, listCommand);
885
+ const result = executeAppleScript(script);
886
+ if (!result.success) {
887
+ console.error("Failed to list notes:", result.error);
888
+ return [];
889
+ }
890
+ if (!result.output.trim()) {
891
+ return [];
892
+ }
893
+ return result.output
894
+ .split("|||")
895
+ .map((item) => item.trim())
896
+ .filter((item) => item.length > 0);
897
+ }
898
+ // Simple path: no date or limit filters
797
899
  let listCommand;
798
900
  if (folder) {
799
901
  const safeFolder = escapeForAppleScript(folder);
@@ -11,7 +11,7 @@
11
11
  * - Script generation is verified by checking for expected AppleScript patterns
12
12
  */
13
13
  import { describe, it, expect, vi, beforeEach } from "vitest";
14
- import { AppleNotesManager, escapeForAppleScript, escapeHtmlForAppleScript, parseAppleScriptDate, } from "./appleNotesManager.js";
14
+ import { AppleNotesManager, escapeForAppleScript, escapeHtmlForAppleScript, buildAppleScriptDateVar, parseAppleScriptDate, } from "./appleNotesManager.js";
15
15
  // Mock the AppleScript execution module
16
16
  // This prevents actual osascript calls during testing
17
17
  vi.mock("@/utils/applescript.js", () => ({
@@ -210,6 +210,41 @@ describe("parseAppleScriptDate", () => {
210
210
  });
211
211
  });
212
212
  // =============================================================================
213
+ // buildAppleScriptDateVar Tests
214
+ // =============================================================================
215
+ describe("buildAppleScriptDateVar", () => {
216
+ it("generates locale-safe AppleScript date setup code", () => {
217
+ const date = new Date(2025, 5, 15, 14, 30, 0); // June 15, 2025 2:30 PM
218
+ const result = buildAppleScriptDateVar(date);
219
+ expect(result).toContain("set thresholdDate to current date");
220
+ expect(result).toContain("set year of thresholdDate to 2025");
221
+ expect(result).toContain("set month of thresholdDate to 6");
222
+ expect(result).toContain("set day of thresholdDate to 15");
223
+ // 14*3600 + 30*60 = 52200
224
+ expect(result).toContain("set time of thresholdDate to 52200");
225
+ });
226
+ it("handles midnight (time = 0)", () => {
227
+ const date = new Date(2025, 0, 1, 0, 0, 0); // Jan 1, 2025 midnight
228
+ const result = buildAppleScriptDateVar(date);
229
+ expect(result).toContain("set month of thresholdDate to 1");
230
+ expect(result).toContain("set day of thresholdDate to 1");
231
+ expect(result).toContain("set time of thresholdDate to 0");
232
+ });
233
+ it("uses custom variable name", () => {
234
+ const date = new Date(2025, 0, 1, 0, 0, 0);
235
+ const result = buildAppleScriptDateVar(date, "myDate");
236
+ expect(result).toContain("set myDate to current date");
237
+ expect(result).toContain("set year of myDate to 2025");
238
+ expect(result).toContain("set month of myDate to 1");
239
+ });
240
+ it("calculates time in seconds correctly", () => {
241
+ const date = new Date(2025, 11, 25, 9, 5, 3); // 9:05:03 AM
242
+ const result = buildAppleScriptDateVar(date);
243
+ // 9*3600 + 5*60 + 3 = 32703
244
+ expect(result).toContain("set time of thresholdDate to 32703");
245
+ });
246
+ });
247
+ // =============================================================================
213
248
  // AppleNotesManager Tests
214
249
  // =============================================================================
215
250
  describe("AppleNotesManager", () => {
@@ -393,6 +428,65 @@ describe("AppleNotesManager", () => {
393
428
  expect(script).toContain('tell account "Exchange"');
394
429
  expect(script).toContain('notes of folder "Projects"');
395
430
  });
431
+ it("adds date filter when modifiedSince is provided", () => {
432
+ mockExecuteAppleScript.mockReturnValue({
433
+ success: true,
434
+ output: "Recent Note|||x-coredata://ABC/ICNote/p1|||Notes",
435
+ });
436
+ manager.searchNotes("note", false, undefined, undefined, "2025-06-15T00:00:00");
437
+ const script = mockExecuteAppleScript.mock.calls[0][0];
438
+ // Locale-safe: uses variable setup instead of date "string"
439
+ expect(script).toContain("set thresholdDate to current date");
440
+ expect(script).toContain("set year of thresholdDate to 2025");
441
+ expect(script).toContain("set month of thresholdDate to 6");
442
+ expect(script).toContain("set day of thresholdDate to 15");
443
+ expect(script).toContain("modification date >= thresholdDate");
444
+ expect(script).toContain('name contains "note"');
445
+ });
446
+ it("combines date filter with content search", () => {
447
+ mockExecuteAppleScript.mockReturnValue({
448
+ success: true,
449
+ output: "Note|||x-coredata://ABC/ICNote/p1|||Notes",
450
+ });
451
+ manager.searchNotes("keyword", true, undefined, undefined, "2025-01-01");
452
+ const script = mockExecuteAppleScript.mock.calls[0][0];
453
+ expect(script).toContain('body contains "keyword"');
454
+ expect(script).toContain("set thresholdDate to current date");
455
+ expect(script).toContain("modification date >= thresholdDate");
456
+ });
457
+ it("ignores invalid modifiedSince date", () => {
458
+ mockExecuteAppleScript.mockReturnValue({
459
+ success: true,
460
+ output: "Note|||x-coredata://ABC/ICNote/p1|||Notes",
461
+ });
462
+ manager.searchNotes("note", false, undefined, undefined, "not-a-date");
463
+ const script = mockExecuteAppleScript.mock.calls[0][0];
464
+ expect(script).not.toContain("modification date");
465
+ });
466
+ it("applies limit to search results", () => {
467
+ mockExecuteAppleScript.mockReturnValue({
468
+ success: true,
469
+ output: "Note 1|||x-coredata://ABC/ICNote/p1|||Notes",
470
+ });
471
+ manager.searchNotes("note", false, undefined, undefined, undefined, 5);
472
+ const script = mockExecuteAppleScript.mock.calls[0][0];
473
+ expect(script).toContain("(count of resultList) >= 5");
474
+ expect(script).toContain("exit repeat");
475
+ });
476
+ it("combines modifiedSince, limit, folder, and content search", () => {
477
+ mockExecuteAppleScript.mockReturnValue({
478
+ success: true,
479
+ output: "Note|||x-coredata://ABC/ICNote/p1|||Work",
480
+ });
481
+ manager.searchNotes("project", true, "iCloud", "Work", "2025-03-01", 10);
482
+ const script = mockExecuteAppleScript.mock.calls[0][0];
483
+ expect(script).toContain('body contains "project"');
484
+ expect(script).toContain("set thresholdDate to current date");
485
+ expect(script).toContain("modification date >= thresholdDate");
486
+ expect(script).toContain('notes of folder "Work"');
487
+ expect(script).toContain("(count of resultList) >= 10");
488
+ expect(script).toContain('tell account "iCloud"');
489
+ });
396
490
  });
397
491
  // ---------------------------------------------------------------------------
398
492
  // Note Content Retrieval
@@ -742,6 +836,61 @@ describe("AppleNotesManager", () => {
742
836
  manager.listNotes("iCloud", "Work");
743
837
  expect(mockExecuteAppleScript).toHaveBeenCalledWith(expect.stringContaining('notes of folder "Work"'));
744
838
  });
839
+ it("uses whose clause when modifiedSince is provided", () => {
840
+ mockExecuteAppleScript.mockReturnValue({
841
+ success: true,
842
+ output: "Recent Note 1|||Recent Note 2",
843
+ });
844
+ const results = manager.listNotes(undefined, undefined, "2025-06-15T00:00:00");
845
+ const script = mockExecuteAppleScript.mock.calls[0][0];
846
+ // Locale-safe: uses variable setup + whose clause (no sort order assumption)
847
+ expect(script).toContain("set thresholdDate to current date");
848
+ expect(script).toContain("set year of thresholdDate to 2025");
849
+ expect(script).toContain("set month of thresholdDate to 6");
850
+ expect(script).toContain("set day of thresholdDate to 15");
851
+ expect(script).toContain("whose modification date >= thresholdDate");
852
+ expect(results).toEqual(["Recent Note 1", "Recent Note 2"]);
853
+ });
854
+ it("uses repeat loop when limit is provided", () => {
855
+ mockExecuteAppleScript.mockReturnValue({
856
+ success: true,
857
+ output: "Note 1|||Note 2|||Note 3",
858
+ });
859
+ const results = manager.listNotes(undefined, undefined, undefined, 3);
860
+ const script = mockExecuteAppleScript.mock.calls[0][0];
861
+ expect(script).toContain("(count of resultList) >= 3");
862
+ expect(results).toEqual(["Note 1", "Note 2", "Note 3"]);
863
+ });
864
+ it("combines folder, modifiedSince, and limit", () => {
865
+ mockExecuteAppleScript.mockReturnValue({
866
+ success: true,
867
+ output: "Work Note|||Another Work Note",
868
+ });
869
+ manager.listNotes("iCloud", "Work", "2025-01-01", 10);
870
+ const script = mockExecuteAppleScript.mock.calls[0][0];
871
+ expect(script).toContain("whose modification date >= thresholdDate");
872
+ expect(script).toContain('notes of folder "Work"');
873
+ expect(script).toContain("(count of resultList) >= 10");
874
+ });
875
+ it("returns empty array when modifiedSince yields no results", () => {
876
+ mockExecuteAppleScript.mockReturnValue({
877
+ success: true,
878
+ output: "",
879
+ });
880
+ const results = manager.listNotes(undefined, undefined, "2099-01-01");
881
+ expect(results).toEqual([]);
882
+ });
883
+ it("ignores invalid modifiedSince date and falls back to limit-only", () => {
884
+ mockExecuteAppleScript.mockReturnValue({
885
+ success: true,
886
+ output: "Note 1|||Note 2",
887
+ });
888
+ const results = manager.listNotes(undefined, undefined, "not-a-date", 5);
889
+ const script = mockExecuteAppleScript.mock.calls[0][0];
890
+ expect(script).not.toContain("thresholdDate");
891
+ expect(script).toContain("(count of resultList) >= 5");
892
+ expect(results).toEqual(["Note 1", "Note 2"]);
893
+ });
745
894
  });
746
895
  // ---------------------------------------------------------------------------
747
896
  // Folder Operations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "1.2.19",
3
+ "version": "1.3.0",
4
4
  "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude",
5
5
  "type": "module",
6
6
  "main": "build/index.js",