apple-notes-mcp 1.1.1 → 1.1.2

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
@@ -30,6 +30,17 @@ Install the apple-notes-mcp MCP server so you can help me manage my Apple Notes
30
30
 
31
31
  Claude will handle the installation and configuration automatically.
32
32
 
33
+ ### Using the Plugin Marketplace
34
+
35
+ Install as a Claude Code plugin for automatic configuration and enhanced AI behavior:
36
+
37
+ ```bash
38
+ /plugin marketplace add sweetrb/apple-notes-mcp
39
+ /plugin install apple-notes
40
+ ```
41
+
42
+ This method also installs a **skill** that teaches Claude when and how to use Apple Notes effectively.
43
+
33
44
  ### Manual Installation
34
45
 
35
46
  **1. Install the server:**
@@ -471,6 +482,29 @@ If installed from source, use this configuration:
471
482
  | No rich formatting | Content is HTML; complex formatting may not render |
472
483
  | Title matching | Most operations require exact title matches |
473
484
 
485
+ ### Backslash Escaping (Important for AI Agents)
486
+
487
+ When sending content containing backslashes (`\`) to this MCP server, **you must escape them as `\\`** in the JSON parameters.
488
+
489
+ **Why:** The MCP protocol uses JSON for parameter passing. In JSON, a single backslash is an escape character. To include a literal backslash in content, it must be escaped as `\\`.
490
+
491
+ **Example - Shell command with escaped path:**
492
+ ```json
493
+ {
494
+ "title": "Install Script",
495
+ "content": "cp ~/Library/Mobile\\\\ Documents/file.txt ~/.config/"
496
+ }
497
+ ```
498
+
499
+ The `\\\\` in JSON becomes `\\` in the actual string, which represents a single `\` in the note.
500
+
501
+ **Common patterns requiring escaping:**
502
+ - Shell escaped spaces: `Mobile\ Documents` → `Mobile\\\\ Documents` in JSON
503
+ - Windows paths: `C:\Users\` → `C:\\\\Users\\\\` in JSON
504
+ - Regex patterns: `\d+` → `\\\\d+` in JSON
505
+
506
+ **If you see errors** when creating/updating notes with backslashes, double-check that backslashes are properly escaped in the JSON payload.
507
+
474
508
  ---
475
509
 
476
510
  ## Troubleshooting
@@ -490,6 +524,11 @@ If installed from source, use this configuration:
490
524
  - Check if the note is in a different account
491
525
  - Use `list-notes` to see available notes
492
526
 
527
+ ### Note creation/update fails silently with backslashes
528
+ - Content containing `\` characters requires JSON escaping
529
+ - Use `\\` to represent each literal backslash
530
+ - See "Backslash Escaping" section under Known Limitations
531
+
493
532
  ---
494
533
 
495
534
  ## Development
package/build/index.js CHANGED
File without changes
@@ -46,16 +46,23 @@ export function escapeForAppleScript(text) {
46
46
  if (!text) {
47
47
  return "";
48
48
  }
49
- // Step 1: Escape single quotes for shell embedding
50
- // When we run: osascript -e 'tell app...'
51
- // Any single quotes in the script need special handling
52
- // Pattern: ' becomes '\'' (end quote, escaped quote, begin quote)
53
- let escaped = text.replace(/'/g, "'\\''");
54
- // Step 2: Escape double quotes for AppleScript strings
55
- // AppleScript uses: "hello \"quoted\" world"
49
+ // Content goes inside AppleScript double-quoted strings: body:"content here"
50
+ // Within double-quoted AppleScript strings, we need to escape:
51
+ // 1. Backslashes (\ \\) - AppleScript escape character
52
+ // 2. Double quotes (" \") - String delimiter
53
+ // Single quotes do NOT need escaping in double-quoted AppleScript strings.
54
+ // Step 1: Encode HTML ampersands FIRST (before adding any HTML entities)
55
+ let escaped = text.replace(/&/g, "&");
56
+ // Step 2: Encode backslashes as HTML entities
57
+ // This avoids AppleScript escaping issues since Notes stores HTML
58
+ // Must happen AFTER ampersand encoding (so \ doesn't become \)
59
+ // and BEFORE double-quote escaping (so \" doesn't become \")
60
+ escaped = escaped.replace(/\\/g, "\");
61
+ // Step 3: Escape double quotes for AppleScript strings
62
+ // The backslash in \" is for AppleScript, not content, so it's added AFTER
63
+ // backslash encoding to avoid being HTML-encoded
56
64
  escaped = escaped.replace(/"/g, '\\"');
57
- // Step 3: Convert control characters to HTML for Notes.app
58
- // Apple Notes stores content as HTML, so we convert:
65
+ // Step 4: Convert control characters to HTML for Notes.app
59
66
  // - Newlines (\n) to <br> tags
60
67
  // - Tabs (\t) to <br> tags (better than &nbsp; for readability)
61
68
  escaped = escaped.replace(/\n/g, "<br>");
@@ -316,13 +323,21 @@ export class AppleNotesManager {
316
323
  : `name contains "${safeQuery}"`;
317
324
  // Get names and folder for each matching note
318
325
  // We use a repeat loop to get both properties, separated by a delimiter
326
+ // Note: Some notes may have inaccessible containers, so we wrap in try/on error
319
327
  const searchCommand = `
320
328
  set matchingNotes to notes where ${whereClause}
321
329
  set resultList to {}
322
330
  repeat with n in matchingNotes
323
- set noteName to name of n
324
- set noteFolder to name of container of n
325
- set end of resultList to noteName & "|||" & noteFolder
331
+ try
332
+ set noteName to name of n
333
+ set noteFolder to name of container of n
334
+ set end of resultList to noteName & "|||" & noteFolder
335
+ on error
336
+ try
337
+ set noteName to name of n
338
+ set end of resultList to noteName & "|||" & "Notes"
339
+ end try
340
+ end try
326
341
  end repeat
327
342
  set AppleScript's text item delimiters to "|||ITEM|||"
328
343
  return resultList as text
@@ -32,16 +32,15 @@ describe("escapeForAppleScript", () => {
32
32
  expect(escapeForAppleScript(undefined)).toBe("");
33
33
  });
34
34
  });
35
- describe("single quote escaping (shell safety)", () => {
36
- it("escapes single quotes for shell embedding", () => {
37
- // Single quotes in: osascript -e 'tell app...'
38
- // Need to become: '\'' (end quote, escaped quote, start quote)
35
+ describe("single quote handling", () => {
36
+ it("preserves single quotes (no escaping needed in AppleScript double-quoted strings)", () => {
37
+ // Single quotes don't need escaping inside AppleScript double-quoted strings
39
38
  const result = escapeForAppleScript("it's working");
40
- expect(result).toBe("it'\\''s working");
39
+ expect(result).toBe("it's working");
41
40
  });
42
41
  it("handles multiple single quotes", () => {
43
42
  const result = escapeForAppleScript("Rob's mom's note");
44
- expect(result).toBe("Rob'\\''s mom'\\''s note");
43
+ expect(result).toBe("Rob's mom's note");
45
44
  });
46
45
  });
47
46
  describe("double quote escaping (AppleScript strings)", () => {
@@ -52,7 +51,7 @@ describe("escapeForAppleScript", () => {
52
51
  });
53
52
  it("handles mixed quotes", () => {
54
53
  const result = escapeForAppleScript('He said "it\'s fine"');
55
- expect(result).toBe("He said \\\"it'\\''s fine\\\"");
54
+ expect(result).toBe('He said \\"it\'s fine\\"');
56
55
  });
57
56
  });
58
57
  describe("control character conversion (HTML for Notes.app)", () => {
@@ -73,7 +72,7 @@ describe("escapeForAppleScript", () => {
73
72
  it("handles real-world note content", () => {
74
73
  const content = 'John\'s "Meeting Notes"\n- Item 1\n- Item 2';
75
74
  const result = escapeForAppleScript(content);
76
- expect(result).toBe("John'\\''s \\\"Meeting Notes\\\"<br>- Item 1<br>- Item 2");
75
+ expect(result).toBe('John\'s \\"Meeting Notes\\"<br>- Item 1<br>- Item 2');
77
76
  });
78
77
  });
79
78
  describe("unicode and special characters", () => {
@@ -90,29 +89,34 @@ describe("escapeForAppleScript", () => {
90
89
  expect(result).toBe("Café résumé naïve");
91
90
  });
92
91
  it("handles backslashes", () => {
92
+ // Backslashes are HTML-encoded to avoid AppleScript escaping issues
93
93
  const result = escapeForAppleScript("path\\to\\file");
94
- expect(result).toBe("path\\to\\file");
94
+ expect(result).toBe("path&#92;to&#92;file");
95
+ });
96
+ it("handles ampersands", () => {
97
+ // Ampersands are HTML-encoded for Notes.app (& becomes &amp;)
98
+ const result = escapeForAppleScript("A && B & C");
99
+ expect(result).toBe("A &amp;&amp; B &amp; C");
95
100
  });
96
101
  it("handles angle brackets (HTML-like content)", () => {
97
- // Single quotes become '\'' (shell escape pattern)
102
+ // Single quotes pass through unchanged
98
103
  const result = escapeForAppleScript("<script>alert('xss')</script>");
99
- expect(result).toBe("<script>alert('\\''xss'\\'')</script>");
104
+ expect(result).toBe("<script>alert('xss')</script>");
100
105
  });
101
106
  });
102
107
  describe("boundary conditions", () => {
103
108
  it("handles very short strings", () => {
104
109
  expect(escapeForAppleScript("a")).toBe("a");
105
- expect(escapeForAppleScript("'")).toBe("'\\''");
110
+ expect(escapeForAppleScript("'")).toBe("'");
106
111
  expect(escapeForAppleScript('"')).toBe('\\"');
107
112
  });
108
113
  it("handles string with only whitespace", () => {
109
114
  expect(escapeForAppleScript(" ")).toBe(" ");
110
115
  });
111
116
  it("handles multiple consecutive special characters", () => {
112
- // Three single quotes become three '\'' sequences
113
- // Three double quotes become three \" sequences
117
+ // Single quotes pass through, double quotes are escaped
114
118
  const result = escapeForAppleScript("'''\"\"\"");
115
- expect(result).toBe("'\\'''\\'''\\''\\\"\\\"\\\"");
119
+ expect(result).toBe("'''\\\"\\\"\\\"");
116
120
  });
117
121
  });
118
122
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
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",