apple-notes-mcp 1.4.2 → 1.4.4
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
|
@@ -712,6 +712,22 @@ If installed from source, use this configuration:
|
|
|
712
712
|
}
|
|
713
713
|
```
|
|
714
714
|
|
|
715
|
+
#### Running from a clone in Claude Code (project-scope `.mcp.json`)
|
|
716
|
+
|
|
717
|
+
This repo ships a `.mcp.json` at its root so that, when you run `claude` from inside a clone, the server is registered automatically as a **project-scope** server — no manual config needed. After `npm run build`, just launch Claude Code from the repo directory and approve the server when prompted.
|
|
718
|
+
|
|
719
|
+
The entrypoint is written as:
|
|
720
|
+
|
|
721
|
+
```json
|
|
722
|
+
"args": ["${CLAUDE_PROJECT_DIR:-.}/build/index.js"]
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
`CLAUDE_PROJECT_DIR` is the variable Claude Code injects into a project/user-scoped server's environment, and it resolves to the repo root. **You must launch `claude` from inside the repo** for this to work — the bare `.` fallback is only a last resort and is *not* reliable, because it resolves against the launching process's working directory, not the repo.
|
|
726
|
+
|
|
727
|
+
> **Why not `${CLAUDE_PLUGIN_ROOT}`?** `CLAUDE_PLUGIN_ROOT` is set **only** for marketplace plugin installs, never for a project-scope clone, so it can't drive the clone workflow. Conversely, a plugin install can't use `CLAUDE_PROJECT_DIR` (in a plugin, that points at the *user's* project, not the plugin's own directory). Claude Code does **not** support nested defaults like `${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR:-.}}`, so a single entrypoint string cannot serve both contexts. The two distribution paths are therefore decoupled: the **plugin** carries its own MCP config in `.claude-plugin/plugin.json` (using `${CLAUDE_PLUGIN_ROOT}`), while the root `.mcp.json` is dedicated to the **clone** workflow (using `${CLAUDE_PROJECT_DIR:-.}`). Because `plugin.json` declares its own `mcpServers`, the plugin does not also auto-load the root `.mcp.json`, so there is no double-registration.
|
|
728
|
+
|
|
729
|
+
> **Heads-up on scope precedence:** project-scope (`.mcp.json`) outranks user-scope. If you *also* have an `apple-notes` entry registered at user scope (e.g. an absolute path in `~/.claude.json`), the project-scope entry wins and the user-scope one is ignored entirely. Pick one — for local development on this repo, the project-scope `.mcp.json` is the intended source. To pin a specific local build instead, register it at **local** scope (`claude mcp add apple-notes -s local -- node /abs/path/build/index.js`), which outranks project scope.
|
|
730
|
+
|
|
715
731
|
---
|
|
716
732
|
|
|
717
733
|
## Full Disk Access for Checklist Features
|
|
@@ -828,6 +844,12 @@ The `\\\\` in JSON becomes `\\` in the actual string, which represents a single
|
|
|
828
844
|
- Use `\\` to represent each literal backslash
|
|
829
845
|
- See "Backslash Escaping" section under Known Limitations
|
|
830
846
|
|
|
847
|
+
### `apple-notes` server fails to connect when run from a clone
|
|
848
|
+
- Launch `claude` from **inside the repo directory** so `CLAUDE_PROJECT_DIR` resolves to the repo root (the bare `.` fallback is unreliable — it points at the launching process's working directory)
|
|
849
|
+
- Run `npm run build` first — the entrypoint is `${CLAUDE_PROJECT_DIR:-.}/build/index.js`, which won't exist until you build
|
|
850
|
+
- Run `claude mcp list` to check for a conflicting `apple-notes` entry at another scope (project-scope outranks user-scope, but local-scope outranks project-scope)
|
|
851
|
+
- Approve the pending project-scope server when Claude Code prompts you
|
|
852
|
+
|
|
831
853
|
---
|
|
832
854
|
|
|
833
855
|
## Development
|
package/build/index.js
CHANGED
|
File without changes
|
|
@@ -97,6 +97,22 @@ export function escapeHtmlForAppleScript(htmlContent) {
|
|
|
97
97
|
// We do NOT re-encode HTML entities since content is already HTML from Notes.app
|
|
98
98
|
return htmlContent.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Escapes a plain (non-HTML) string for safe embedding in an AppleScript string literal.
|
|
102
|
+
*
|
|
103
|
+
* Use this for folder names, account names, and other metadata that Apple Notes
|
|
104
|
+
* stores as plain text — NOT for note body content (use escapeForAppleScript instead).
|
|
105
|
+
* HTML-encoding ampersands here would produce `folder "R&D"`, which Apple Notes
|
|
106
|
+
* would fail to match against the real folder named "R&D".
|
|
107
|
+
*
|
|
108
|
+
* @param text - Plain string (folder name, account name, etc.)
|
|
109
|
+
* @returns String safe for AppleScript string embedding
|
|
110
|
+
*/
|
|
111
|
+
export function escapePlainStringForAppleScript(text) {
|
|
112
|
+
if (!text)
|
|
113
|
+
return "";
|
|
114
|
+
return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
115
|
+
}
|
|
100
116
|
// =============================================================================
|
|
101
117
|
// Input Validation & Sanitization
|
|
102
118
|
// =============================================================================
|
|
@@ -154,7 +170,7 @@ export function sanitizeId(id) {
|
|
|
154
170
|
*/
|
|
155
171
|
function sanitizeAccountName(account) {
|
|
156
172
|
validateLength(account, MAX_ACCOUNT_LENGTH, "Account name");
|
|
157
|
-
return
|
|
173
|
+
return escapePlainStringForAppleScript(account);
|
|
158
174
|
}
|
|
159
175
|
/**
|
|
160
176
|
* Counter for generating unique fallback IDs within the same millisecond.
|
|
@@ -325,7 +341,7 @@ export function buildFolderReference(folderPath) {
|
|
|
325
341
|
// Build inside-out: last part is innermost, first part is outermost
|
|
326
342
|
return parts
|
|
327
343
|
.reverse()
|
|
328
|
-
.map((part) => `folder "${
|
|
344
|
+
.map((part) => `folder "${escapePlainStringForAppleScript(part)}"`)
|
|
329
345
|
.join(" of ");
|
|
330
346
|
}
|
|
331
347
|
/**
|
|
@@ -242,7 +242,7 @@ describe("buildFolderReference", () => {
|
|
|
242
242
|
it("handles special characters in folder names", () => {
|
|
243
243
|
const result = buildFolderReference("Food & Drink/🥘 Recipes");
|
|
244
244
|
expect(result).toContain('folder "🥘 Recipes"');
|
|
245
|
-
expect(result).toContain('folder "Food &
|
|
245
|
+
expect(result).toContain('folder "Food & Drink"');
|
|
246
246
|
});
|
|
247
247
|
it("handles escaped slashes in folder names", () => {
|
|
248
248
|
const result = buildFolderReference("Travel/Spain\\/Portugal 2023");
|