apple-notes-mcp 2.8.11 → 2.8.13

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.
Files changed (3) hide show
  1. package/README.md +21 -3
  2. package/build/index.js +66 -45
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -565,8 +565,22 @@ visible text after saving, not byte-identical rich formatting. Warns when the
565
565
  note is shared with collaborators.
566
566
 
567
567
  **Safety:** The append is rejected if the note changed since it was read, rich
568
- metadata is unavailable, or the note contains attachments or other native
569
- objects. Existing link destinations are verified after saving.
568
+ metadata is unavailable, or the note contains attachments. Existing link
569
+ destinations are verified after saving.
570
+
571
+ **Notes containing native objects** (a table, a checklist, native tags) cannot be
572
+ spliced, so they are routed to the native end-append bridge — see
573
+ [`append-native`](#append-native). That path additionally requires `scopeText`,
574
+ keeps the default blank-line `separator` and `position: "after"`, and accepts a
575
+ fixed HTML subset rather than anything Notes.app can render:
576
+
577
+ | | Native append |
578
+ |---|---|
579
+ | Elements | `<a>` `<b>` `<br>` `<code>` `<del>` `<div>` `<em>` `<h1>` `<h2>` `<h3>` `<i>` `<li>` `<ol>` `<p>` `<s>` `<span>` `<strong>` `<table>` `<tbody>` `<td>` `<th>` `<thead>` `<tr>` `<tt>` `<u>` `<ul>` |
580
+ | Attributes | `href` on `<a>` (`https:`, `http:`, `notes:`, `applenotes:`, `mailto:` only) and a `font-size` style on `<span>`, e.g. `<span style="font-size: 18px">` — the form Notes itself stores a heading as |
581
+ | Refused | every other element and attribute, by name, naming the accepted subset; `<table>` here (use [`create-table`](#create-table)); comments, doctype and processing instructions |
582
+
583
+ Ordinary notes take the guarded HTML path and are not restricted to that subset.
570
584
 
571
585
  ---
572
586
 
@@ -1050,7 +1064,11 @@ Appends bounded plaintext, semantic HTML, or Markdown while preserving existing
1050
1064
  native objects. The regular `append-to-note` tool routes protected notes here
1051
1065
  when `scopeText` is supplied. Use a distinctive phrase of plain words without
1052
1066
  punctuation, hashtags, or paths because Notes search may not resolve them
1053
- literally.
1067
+ literally. `format: "html"` accepts the fixed subset tabulated under
1068
+ [`append-to-note`](#append-to-note); content outside it is refused by name, with
1069
+ the accepted subset in the error. A transport failure names the Shortcut it was
1070
+ waiting on, so a missing or unapproved bridge is identified rather than guessed
1071
+ at.
1054
1072
 
1055
1073
  #### `create-checklist-item`
1056
1074
 
package/build/index.js CHANGED
@@ -39708,7 +39708,7 @@ function visibleCharacters(html) {
39708
39708
  token++;
39709
39709
  if (part[0].startsWith("<")) continue;
39710
39710
  for (const item of part[0].matchAll(
39711
- /&(?:#[0-9]+;?|#x[0-9a-f]+;?|(?:amp|lt|gt|quot|apos|nbsp)(?:;|(?![a-z0-9=])))|[\s\S]/gi
39711
+ /&(?:#[0-9]+;?|#x[0-9a-f]+;?|(?:amp|lt|gt|quot|nbsp);?|apos;)|[\s\S]/gi
39712
39712
  )) {
39713
39713
  const value = decodeEntity(item[0]);
39714
39714
  for (let i = 0; i < value.length; i++) {
@@ -39770,7 +39770,8 @@ function enrichNoteRead(id2, rawBody) {
39770
39770
  warning: "Native tags, inline objects or checklists are present. Their state is not writable through AppleScript; full-body edits are blocked to preserve them."
39771
39771
  } : {}
39772
39772
  };
39773
- } catch {
39773
+ } catch (err) {
39774
+ const detail = err instanceof Error ? err.message : String(err);
39774
39775
  return {
39775
39776
  content: rawBody,
39776
39777
  links: metadata?.links ?? [],
@@ -39778,7 +39779,7 @@ function enrichNoteRead(id2, rawBody) {
39778
39779
  complete: false,
39779
39780
  writable: false,
39780
39781
  revision: metadata?.revision ?? "unavailable",
39781
- warning: "Rich Notes metadata could not be read or matched. Links/native tags may be missing from this view. Full-body edits are blocked; check Full Disk Access and retry after sync."
39782
+ warning: `Rich Notes metadata could not be read or matched. Links/native tags may be missing from this view. Full-body edits are blocked; check Full Disk Access and retry after sync. (${detail})`
39782
39783
  };
39783
39784
  }
39784
39785
  }
@@ -42832,7 +42833,7 @@ function runNativeTagsShortcut(input) {
42832
42833
  });
42833
42834
  } catch {
42834
42835
  throw new Error(
42835
- "Native tag operation did not complete cleanly (possibly waiting for macOS permission). Do not retry automatically; read the exact note and check Shortcuts"
42836
+ `Native tag operation did not complete cleanly; the "${status.shortcut}" Shortcut may be waiting for macOS permission. Do not retry automatically; read the exact note and check that Shortcut in Shortcuts.app`
42836
42837
  );
42837
42838
  } finally {
42838
42839
  rmSync2(directory, { recursive: true, force: true });
@@ -42930,49 +42931,62 @@ function readBackgroundSnapshot(manager, id2) {
42930
42931
  checklist: getChecklistItems(id2).items || []
42931
42932
  };
42932
42933
  }
42934
+ var NATIVE_APPEND_ELEMENTS = [
42935
+ "a",
42936
+ "b",
42937
+ "br",
42938
+ "code",
42939
+ "del",
42940
+ "div",
42941
+ "em",
42942
+ "h1",
42943
+ "h2",
42944
+ "h3",
42945
+ "i",
42946
+ "li",
42947
+ "ol",
42948
+ "p",
42949
+ "s",
42950
+ "span",
42951
+ "strong",
42952
+ "table",
42953
+ "tbody",
42954
+ "td",
42955
+ "th",
42956
+ "thead",
42957
+ "tr",
42958
+ "tt",
42959
+ "u",
42960
+ "ul"
42961
+ ];
42962
+ var NATIVE_APPEND_SPAN_STYLE = /^font-size\s*:\s*\d{1,3}(?:\.\d+)?(?:px|pt)\s*;?$/i;
42963
+ var NATIVE_APPEND_HTML_SUBSET = `Native append accepts ${NATIVE_APPEND_ELEMENTS.map((e) => `<${e}>`).join(" ")}, with href on <a> and a font-size style on <span> as the only attributes; everything else needs update-note.`;
42933
42964
  function validateAppendContent(content, format) {
42934
42965
  if (!content || content.length > 1024 * 1024 || content.includes("\0"))
42935
42966
  throw new Error("Invalid append content (limit 1 MiB)");
42936
42967
  if (format === "html") {
42937
- const allowed = /* @__PURE__ */ new Set([
42938
- "div",
42939
- "p",
42940
- "br",
42941
- "b",
42942
- "strong",
42943
- "i",
42944
- "em",
42945
- "u",
42946
- "s",
42947
- "del",
42948
- "h1",
42949
- "h2",
42950
- "h3",
42951
- "ul",
42952
- "ol",
42953
- "li",
42954
- "a",
42955
- "table",
42956
- "thead",
42957
- "tbody",
42958
- "tr",
42959
- "td",
42960
- "th"
42961
- ]);
42968
+ const allowed = new Set(NATIVE_APPEND_ELEMENTS);
42962
42969
  for (const tag of content.matchAll(/<\/?\s*([a-z][a-z0-9]*)\b([^>]*)>/gi)) {
42963
- if (!allowed.has(tag[1].toLowerCase()))
42964
- throw new Error(`Unsupported HTML element: ${tag[1]}`);
42970
+ const name = tag[1].toLowerCase();
42971
+ if (!allowed.has(name))
42972
+ throw new Error(`Unsupported HTML element: <${name}>. ${NATIVE_APPEND_HTML_SUBSET}`);
42965
42973
  let attrs = tag[2].replace(/\/$/, "").trim();
42966
- if (tag[1].toLowerCase() === "a")
42974
+ if (name === "a")
42967
42975
  attrs = attrs.replace(/\bhref\s*=\s*(["'])(.*?)\1/gi, (_s, _q, url) => {
42968
42976
  if (!/^(https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) || Array.from(url).some((c) => c.charCodeAt(0) < 33))
42969
42977
  throw new Error("Unsupported link URL");
42970
42978
  return "";
42971
42979
  });
42980
+ if (name === "span")
42981
+ attrs = attrs.replace(/\bstyle\s*=\s*(["'])(.*?)\1/gi, (_s, _q, style) => {
42982
+ if (!NATIVE_APPEND_SPAN_STYLE.test(style.trim()))
42983
+ throw new Error(
42984
+ `Unsupported <span> style: ${style.trim().slice(0, 80)}. Native append accepts font-size only, as in <span style="font-size: 18px">.`
42985
+ );
42986
+ return "";
42987
+ });
42972
42988
  if (attrs.trim())
42973
- throw new Error(
42974
- "Unsupported HTML attributes; use semantic formatting and explicit blank paragraphs"
42975
- );
42989
+ throw new Error(`Unsupported HTML attributes on <${name}>. ${NATIVE_APPEND_HTML_SUBSET}`);
42976
42990
  }
42977
42991
  if (/<!--|<!|<\?|<[^>]*$/u.test(content)) throw new Error("Unsupported HTML markup");
42978
42992
  }
@@ -42982,7 +42996,9 @@ function validateAppendContent(content, format) {
42982
42996
  function runBackgroundShortcut(input) {
42983
42997
  const status = backgroundStatus();
42984
42998
  if (!status.installed)
42985
- throw new Error("Install the supplied Background Operations shortcut once");
42999
+ throw new Error(
43000
+ `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
43001
+ );
42986
43002
  const directory = mkdtempSync3(join8(tmpdir3(), "apple-notes-background-"));
42987
43003
  try {
42988
43004
  const file = join8(directory, "request.json");
@@ -43004,12 +43020,16 @@ function runBackgroundShortcut(input) {
43004
43020
  }),
43005
43021
  { mode: 384 }
43006
43022
  );
43007
- execFileSync7("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
43008
- encoding: "utf8",
43009
- timeout: 6e4,
43010
- maxBuffer: 1024 * 1024,
43011
- stdio: ["ignore", "pipe", "pipe"]
43012
- });
43023
+ try {
43024
+ execFileSync7("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
43025
+ encoding: "utf8",
43026
+ timeout: 6e4,
43027
+ maxBuffer: 1024 * 1024,
43028
+ stdio: ["ignore", "pipe", "pipe"]
43029
+ });
43030
+ } catch (error2) {
43031
+ throw Object.assign(error2, { shortcut: status.shortcut });
43032
+ }
43013
43033
  } finally {
43014
43034
  rmSync3(directory, { recursive: true, force: true });
43015
43035
  }
@@ -43096,7 +43116,8 @@ function mutateBackground(request, operation, data, verify, deps) {
43096
43116
  } catch (error2) {
43097
43117
  transportUncertain = true;
43098
43118
  const detail = error2;
43099
- transportMessage = detail?.code === "ETIMEDOUT" ? "Shortcuts timed out; check for an interactive parameter or permission request" : String(detail?.stderr || detail?.message || "Shortcuts failed").trim().slice(0, 500);
43119
+ const named = detail?.shortcut ? `the "${detail.shortcut}" Shortcut` : "the background Shortcut";
43120
+ transportMessage = detail?.code === "ETIMEDOUT" ? `Shortcuts timed out waiting for ${named}; check for an interactive parameter or permission request` : `${named} failed: ${String(detail?.stderr || detail?.message || "no output").trim().slice(0, 400)}`;
43100
43121
  }
43101
43122
  const after = deps.read(request.id);
43102
43123
  try {
@@ -43980,7 +44001,7 @@ function registerNativeOperations(server2, manager) {
43980
44001
  );
43981
44002
  tool(
43982
44003
  "append-native",
43983
- "Use when: appending formatted content to a native-object note without replacing its body.\nReturns: exact-ID preservation and appended-content readback.\nDo not use when: the existing note can be safely handled by append-to-note or the scope phrase is ambiguous.\nSafety: requires a fresh revision and unique existing scope phrase of plain words; avoid punctuation, hashtags, and paths because Notes search may not resolve them literally. Supports bounded plaintext, semantic HTML, and Markdown without external fetching or automatic retries.",
44004
+ "Use when: appending formatted content to a native-object note without replacing its body.\nReturns: exact-ID preservation and appended-content readback.\nDo not use when: the existing note can be safely handled by append-to-note or the scope phrase is ambiguous.\nSafety: requires a fresh revision and unique existing scope phrase of plain words; avoid punctuation, hashtags, and paths because Notes search may not resolve them literally. Supports bounded plaintext, semantic HTML, and Markdown without external fetching or automatic retries.\nHTML subset: " + NATIVE_APPEND_HTML_SUBSET,
43984
44005
  {
43985
44006
  ...common,
43986
44007
  content: external_exports.string().min(1).max(1024 * 1024),
@@ -45022,7 +45043,7 @@ registerTool(
45022
45043
  registerTool(
45023
45044
  "append-to-note",
45024
45045
  {
45025
- description: "Use when: adding content to one exact note after reading it by id.\nReturns: exact id, new content hash, and visible-text readback verification.\nDo not use when: you only have a title or the note changed since the read.\nSafety: protected native-object notes use native end-append with scopeText; ordinary notes retain guarded HTML editing. Notes.app normalizes HTML, so rich formatting is not claimed as byte-identical.",
45046
+ description: "Use when: adding content to one exact note after reading it by id.\nReturns: exact id, new content hash, and visible-text readback verification.\nDo not use when: you only have a title or the note changed since the read.\nSafety: protected native-object notes use native end-append with scopeText; ordinary notes retain guarded HTML editing. Notes.app normalizes HTML, so rich formatting is not claimed as byte-identical.\nNative-append HTML subset (protected notes only; ordinary notes accept any HTML Notes.app renders): " + NATIVE_APPEND_HTML_SUBSET + " Native append also requires scopeText, the default blank-line separator and position 'after'.",
45026
45047
  inputSchema: {
45027
45048
  id: noteIdInput,
45028
45049
  expectedContentHash: expectedContentHashInput,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.8.11",
3
+ "version": "2.8.13",
4
4
  "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",