apple-notes-mcp 1.3.0 → 1.3.1

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
@@ -107,10 +107,10 @@ Creates a new note in Apple Notes.
107
107
 
108
108
  | Parameter | Type | Required | Description |
109
109
  |-----------|------|----------|-------------|
110
- | `title` | string | Yes | The title of the note (becomes first line) |
111
- | `content` | string | Yes | The body content of the note |
110
+ | `title` | string | Yes | The title of the note. Automatically prepended as `<h1>` — do NOT include the title in `content` |
111
+ | `content` | string | Yes | The body content of the note (do not repeat the title here) |
112
112
  | `tags` | string[] | No | Tags for organization (stored in metadata) |
113
- | `format` | string | No | Content format: `"plaintext"` (default) or `"html"`. When `"html"`, content is used as raw HTML for rich formatting |
113
+ | `format` | string | No | Content format: `"plaintext"` (default) or `"html"`. In both formats, the title is automatically prepended as `<h1>`. In plaintext mode, newlines become `<br>`, tabs become `<br>`, and backslashes are preserved as HTML entities |
114
114
 
115
115
  **Example:**
116
116
  ```json
@@ -125,11 +125,13 @@ Creates a new note in Apple Notes.
125
125
  ```json
126
126
  {
127
127
  "title": "Status Report",
128
- "content": "<h1>Status Report</h1><h2>Summary</h2><p>All tasks <b>on track</b>.</p><ul><li>Feature A: complete</li><li>Feature B: in progress</li></ul>",
128
+ "content": "<h2>Summary</h2><p>All tasks <b>on track</b>.</p><ul><li>Feature A: complete</li><li>Feature B: in progress</li></ul>",
129
129
  "format": "html"
130
130
  }
131
131
  ```
132
132
 
133
+ > **Note:** The title is automatically prepended as `<h1>` in both plaintext and HTML formats. Do not include a `<h1>` title tag in the `content` parameter, or the title will appear twice.
134
+
133
135
  **Returns:** Confirmation message with note title and ID. Save the ID for subsequent operations like `update-note`, `delete-note`, etc.
134
136
 
135
137
  ---
@@ -293,7 +295,7 @@ Updates an existing note's content and/or title.
293
295
  ```json
294
296
  {
295
297
  "id": "x-coredata://ABC123/ICNote/p456",
296
- "newContent": "<h1>Updated Report</h1><p>New findings with <b>bold</b> emphasis.</p><pre><code>console.log('hello');</code></pre>",
298
+ "newContent": "<p>New findings with <b>bold</b> emphasis.</p><pre><code>console.log('hello');</code></pre>",
297
299
  "format": "html"
298
300
  }
299
301
  ```
@@ -399,32 +399,41 @@ export class AppleNotesManager {
399
399
  * // Create in a different account
400
400
  * const gmail = manager.createNote("Draft", "...", [], undefined, "Gmail");
401
401
  *
402
- * // Create with HTML formatting
403
- * const html = manager.createNote("Report", "<h1>Report</h1><p>Details here</p>",
402
+ * // Create with HTML formatting (no need for <h1> — title is auto-prepended)
403
+ * const html = manager.createNote("Report", "<p>Details here</p>",
404
404
  * [], undefined, undefined, "html");
405
405
  * ```
406
406
  */
407
407
  createNote(title, content, tags = [], folder, account, format = "plaintext") {
408
408
  const targetAccount = this.resolveAccount(account);
409
- // Escape content for AppleScript embedding
410
- const safeTitle = escapeForAppleScript(title);
411
- const safeContent = format === "html" ? escapeHtmlForAppleScript(content) : escapeForAppleScript(content);
409
+ // Build body HTML: title as <h1>, content follows.
410
+ // We set only 'body' (not 'name') to avoid title duplication —
411
+ // Notes.app auto-uses the first line of body as the note's display title.
412
+ const htmlTitle = title.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
413
+ const bodyContent = format === "html"
414
+ ? content
415
+ : content
416
+ .replace(/&/g, "&amp;")
417
+ .replace(/\\/g, "&#92;")
418
+ .replace(/</g, "&lt;")
419
+ .replace(/>/g, "&gt;")
420
+ .replace(/\n/g, "<br>")
421
+ .replace(/\t/g, "<br>");
422
+ const safeBody = escapeHtmlForAppleScript(`<h1>${htmlTitle}</h1>${bodyContent}`);
412
423
  // Build the AppleScript command
413
- // Notes.app uses 'name' for the title and 'body' for content
414
- // We capture the ID of the newly created note
415
424
  let createCommand;
416
425
  if (folder) {
417
426
  // Create note in specific folder
418
427
  const safeFolder = escapeForAppleScript(folder);
419
428
  createCommand = `
420
- set newNote to make new note at folder "${safeFolder}" with properties {name:"${safeTitle}", body:"${safeContent}"}
429
+ set newNote to make new note at folder "${safeFolder}" with properties {body:"${safeBody}"}
421
430
  return id of newNote
422
431
  `;
423
432
  }
424
433
  else {
425
434
  // Create note in default location
426
435
  createCommand = `
427
- set newNote to make new note with properties {name:"${safeTitle}", body:"${safeContent}"}
436
+ set newNote to make new note with properties {body:"${safeBody}"}
428
437
  return id of newNote
429
438
  `;
430
439
  }
@@ -333,6 +333,62 @@ describe("AppleNotesManager", () => {
333
333
  // Double quotes must be escaped for AppleScript string embedding
334
334
  expect(mockExecuteAppleScript).toHaveBeenCalledWith(expect.stringContaining('<div class=\\"test\\">Content</div>'));
335
335
  });
336
+ it("sets title as h1 in body, not as name property", () => {
337
+ mockExecuteAppleScript.mockReturnValue({
338
+ success: true,
339
+ output: "note id x-coredata://12345/ICNote/p203",
340
+ });
341
+ manager.createNote("My Title", "Body content");
342
+ const script = mockExecuteAppleScript.mock.calls[0][0];
343
+ // Title must appear as h1 in body
344
+ expect(script).toContain("<h1>My Title</h1>");
345
+ // name property must NOT be set (causes title duplication in Notes.app)
346
+ expect(script).not.toContain('name:"My Title"');
347
+ });
348
+ it("HTML-encodes special chars in title for h1 tag", () => {
349
+ mockExecuteAppleScript.mockReturnValue({
350
+ success: true,
351
+ output: "note id x-coredata://12345/ICNote/p204",
352
+ });
353
+ manager.createNote("Q&A: <Hello> World", "Content");
354
+ expect(mockExecuteAppleScript).toHaveBeenCalledWith(expect.stringContaining("<h1>Q&amp;A: &lt;Hello&gt; World</h1>"));
355
+ });
356
+ it("HTML-encodes special chars in plaintext content", () => {
357
+ mockExecuteAppleScript.mockReturnValue({
358
+ success: true,
359
+ output: "note id x-coredata://12345/ICNote/p205",
360
+ });
361
+ manager.createNote("Title", "Price: <10 & >5\nNext line");
362
+ const script = mockExecuteAppleScript.mock.calls[0][0];
363
+ expect(script).toContain("Price: &lt;10 &amp; &gt;5<br>Next line");
364
+ });
365
+ it("prepends h1 title before html content", () => {
366
+ mockExecuteAppleScript.mockReturnValue({
367
+ success: true,
368
+ output: "note id x-coredata://12345/ICNote/p206",
369
+ });
370
+ manager.createNote("Report", "<h2>Section</h2><div>Details</div>", [], undefined, undefined, "html");
371
+ const script = mockExecuteAppleScript.mock.calls[0][0];
372
+ expect(script).toContain("<h1>Report</h1><h2>Section</h2><div>Details</div>");
373
+ });
374
+ it("encodes backslashes as HTML entities in plaintext content", () => {
375
+ mockExecuteAppleScript.mockReturnValue({
376
+ success: true,
377
+ output: "note id x-coredata://12345/ICNote/p207",
378
+ });
379
+ manager.createNote("Title", "path\\to\\file");
380
+ const script = mockExecuteAppleScript.mock.calls[0][0];
381
+ expect(script).toContain("path&#92;to&#92;file");
382
+ });
383
+ it("converts tabs to br in plaintext content", () => {
384
+ mockExecuteAppleScript.mockReturnValue({
385
+ success: true,
386
+ output: "note id x-coredata://12345/ICNote/p208",
387
+ });
388
+ manager.createNote("Title", "col1\tcol2\tcol3");
389
+ const script = mockExecuteAppleScript.mock.calls[0][0];
390
+ expect(script).toContain("col1<br>col2<br>col3");
391
+ });
336
392
  });
337
393
  // ---------------------------------------------------------------------------
338
394
  // Note Search
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
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",