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 +39 -0
- package/build/index.js +0 -0
- package/build/services/appleNotesManager.js +27 -12
- package/build/services/appleNotesManager.test.js +19 -15
- package/package.json +1 -1
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
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
// Step
|
|
55
|
-
|
|
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
|
|
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 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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
36
|
-
it("
|
|
37
|
-
// Single quotes
|
|
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'
|
|
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'
|
|
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(
|
|
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(
|
|
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
|
|
94
|
+
expect(result).toBe("path\to\file");
|
|
95
|
+
});
|
|
96
|
+
it("handles ampersands", () => {
|
|
97
|
+
// Ampersands are HTML-encoded for Notes.app (& becomes &)
|
|
98
|
+
const result = escapeForAppleScript("A && B & C");
|
|
99
|
+
expect(result).toBe("A && B & C");
|
|
95
100
|
});
|
|
96
101
|
it("handles angle brackets (HTML-like content)", () => {
|
|
97
|
-
// Single quotes
|
|
102
|
+
// Single quotes pass through unchanged
|
|
98
103
|
const result = escapeForAppleScript("<script>alert('xss')</script>");
|
|
99
|
-
expect(result).toBe("<script>alert('
|
|
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
|
-
//
|
|
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
|
});
|