apple-notes-mcp 2.9.5 → 2.9.6

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
@@ -1834,6 +1834,22 @@ A recording extended with more takes has several fragments. Their transcripts ar
1834
1834
 
1835
1835
  ---
1836
1836
 
1837
+ #### `get-note-drawings`
1838
+
1839
+ Decodes a note's classic PencilKit drawings (`com.apple.drawing.2` and the older `com.apple.drawing` attachments) into strokes and SVG. The PencilKit bytes are read read-only from the NoteStore database and decoded by Apple's public `PKDrawing(data:)` in the [public native helper](#public-native-helper). Modern Paper sketches (`com.apple.paper`) are a different format and are not decoded here.
1840
+
1841
+ **Requires:** Full Disk Access for the MCP host process, and the public native helper built once with `apple-notes-mcp setup --public-helper`.
1842
+
1843
+ | Parameter | Type | Required | Description |
1844
+ |-----------|------|----------|-------------|
1845
+ | `id` | string | Yes | Note ID (use `search-notes` to find it first) |
1846
+ | `format` | string | No | `"json"` (default) returns strokes, `"svg"` returns a standalone SVG document per drawing, `"both"` returns both |
1847
+ | `includePoints` | boolean | No | Include per-point `x`, `y`, `width`, `opacity`, and `force` in JSON strokes (default `true`) |
1848
+
1849
+ **Returns:** `status` (`ok`, `partial`, `error`, or `none` when the note has no classic drawing), `drawingCount`, and one entry per drawing with `attachmentId`, `identifier`, `typeUti`, `status` (`ok`, or `error` with a `code` such as `no_data`, `undecodable`, or `timeout`), `strokeCount`, `bounds`, `truncated`, `strokes` (each with `inkType`, sRGB `color` with alpha, mean `width`, `pointCount`, `bounds`, and `points`), and `svg`. Points are already in drawing coordinates. When a response would exceed `APPLE_NOTES_MCP_EXPORT_MAX_BYTES`, points are dropped and `pointsOmitted` is set. The SVG draws one path per stroke; it is a faithful outline, not a pixel-exact copy of PencilKit's ink textures.
1850
+
1851
+ ---
1852
+
1837
1853
  #### `add-attachment`
1838
1854
 
1839
1855
  Adds one nonempty local file of at most 64 MiB to an exact note using `id`, the
@@ -2436,6 +2452,22 @@ Every tool that does not read the Notes database works normally without Full Dis
2436
2452
  - `get-note-link` returns an error on macOS 26+; on macOS 12–15 it still works via the AppleScript `note link` fallback
2437
2453
  - `get-note-markdown` returns plain list items without `[x]`/`[ ]` annotations (graceful fallback)
2438
2454
  - `get-sync-status` still answers, but reports no pending uploads and no active sync — treat that as "unknown", not "idle"
2455
+ - `get-note-drawings` returns the same Full Disk Access error
2456
+
2457
+ ---
2458
+
2459
+ ## Public native helper
2460
+
2461
+ `get-note-drawings` needs Apple's PencilKit framework, which has no AppleScript or command-line interface. For it, the server uses a small Swift helper that links public Apple frameworks only (AppKit and PencilKit). No prebuilt binary ships with the package. Build it once on your Mac:
2462
+
2463
+ ```bash
2464
+ apple-notes-mcp setup --public-helper # compile, sign, verify, install
2465
+ apple-notes-mcp setup --public-helper --check # report the installed state only
2466
+ ```
2467
+
2468
+ Setup compiles `native/public-helper/apple-notes-public-helper.swift` with `xcrun swiftc` (install the Command Line Tools with `xcode-select --install` if it is missing), signs it ad hoc, runs its `hello` handshake, and installs it in `~/Library/Application Support/apple-notes-mcp/public-helper/` next to a manifest recording the SHA-256 of the source and the binary. Before every use the server re-checks both digests: after an upgrade that changes the helper source, or if the binary is replaced, the helper is refused until you run setup again. `APPLE_NOTES_MCP_PUBLIC_HELPER_DIR` overrides the install folder and `APPLE_NOTES_MCP_PUBLIC_HELPER_TIMEOUT_MS` the per-call timeout.
2469
+
2470
+ The helper never opens the Notes database and never writes under the Notes group container. The server reads the bytes it needs (read-only) and passes them to the helper on stdin; the helper answers with one JSON object on stdout.
2439
2471
 
2440
2472
  ---
2441
2473
 
package/build/index.js CHANGED
@@ -2991,7 +2991,7 @@ var require_compile = __commonJS({
2991
2991
  const schOrFunc = root.refs[ref];
2992
2992
  if (schOrFunc)
2993
2993
  return schOrFunc;
2994
- let _sch = resolve6.call(this, root, ref);
2994
+ let _sch = resolve7.call(this, root, ref);
2995
2995
  if (_sch === void 0) {
2996
2996
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2997
2997
  const { schemaId } = this.opts;
@@ -3018,7 +3018,7 @@ var require_compile = __commonJS({
3018
3018
  function sameSchemaEnv(s1, s2) {
3019
3019
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3020
3020
  }
3021
- function resolve6(root, ref) {
3021
+ function resolve7(root, ref) {
3022
3022
  let sch;
3023
3023
  while (typeof (sch = this.refs[ref]) == "string")
3024
3024
  ref = sch;
@@ -3843,7 +3843,7 @@ var require_fast_uri = __commonJS({
3843
3843
  }
3844
3844
  return uri;
3845
3845
  }
3846
- function resolve6(baseURI, relativeURI, options) {
3846
+ function resolve7(baseURI, relativeURI, options) {
3847
3847
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3848
3848
  const {
3849
3849
  parsed: baseParsed,
@@ -4205,7 +4205,7 @@ var require_fast_uri = __commonJS({
4205
4205
  var fastUri = {
4206
4206
  SCHEMES,
4207
4207
  normalize,
4208
- resolve: resolve6,
4208
+ resolve: resolve7,
4209
4209
  resolveComponent,
4210
4210
  equal,
4211
4211
  serialize,
@@ -24465,14 +24465,14 @@ var require_turndown_cjs = __commonJS({
24465
24465
  } else if (node.nodeType === 1) {
24466
24466
  replacement = replacementForNode.call(self, node);
24467
24467
  }
24468
- return join26(output, replacement);
24468
+ return join27(output, replacement);
24469
24469
  }, "");
24470
24470
  }
24471
24471
  function postProcess(output) {
24472
24472
  var self = this;
24473
24473
  this.rules.forEach(function(rule) {
24474
24474
  if (typeof rule.append === "function") {
24475
- output = join26(output, rule.append(self.options));
24475
+ output = join27(output, rule.append(self.options));
24476
24476
  }
24477
24477
  });
24478
24478
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -24484,7 +24484,7 @@ var require_turndown_cjs = __commonJS({
24484
24484
  if (whitespace.leading || whitespace.trailing) content = content.trim();
24485
24485
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
24486
24486
  }
24487
- function join26(output, replacement) {
24487
+ function join27(output, replacement) {
24488
24488
  var s1 = trimTrailingNewlines(output);
24489
24489
  var s2 = trimLeadingNewlines(replacement);
24490
24490
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -36608,7 +36608,7 @@ var Protocol = class {
36608
36608
  return;
36609
36609
  }
36610
36610
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
36611
- await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
36611
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
36612
36612
  options?.signal?.throwIfAborted();
36613
36613
  }
36614
36614
  } catch (error2) {
@@ -36625,7 +36625,7 @@ var Protocol = class {
36625
36625
  */
36626
36626
  request(request, resultSchema, options) {
36627
36627
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
36628
- return new Promise((resolve6, reject) => {
36628
+ return new Promise((resolve7, reject) => {
36629
36629
  const earlyReject = (error2) => {
36630
36630
  reject(error2);
36631
36631
  };
@@ -36703,7 +36703,7 @@ var Protocol = class {
36703
36703
  if (!parseResult.success) {
36704
36704
  reject(parseResult.error);
36705
36705
  } else {
36706
- resolve6(parseResult.data);
36706
+ resolve7(parseResult.data);
36707
36707
  }
36708
36708
  } catch (error2) {
36709
36709
  reject(error2);
@@ -36964,12 +36964,12 @@ var Protocol = class {
36964
36964
  }
36965
36965
  } catch {
36966
36966
  }
36967
- return new Promise((resolve6, reject) => {
36967
+ return new Promise((resolve7, reject) => {
36968
36968
  if (signal.aborted) {
36969
36969
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
36970
36970
  return;
36971
36971
  }
36972
- const timeoutId = setTimeout(resolve6, interval);
36972
+ const timeoutId = setTimeout(resolve7, interval);
36973
36973
  signal.addEventListener("abort", () => {
36974
36974
  clearTimeout(timeoutId);
36975
36975
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -38282,7 +38282,7 @@ var McpServer = class {
38282
38282
  let task = createTaskResult.task;
38283
38283
  const pollInterval = task.pollInterval ?? 5e3;
38284
38284
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
38285
- await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
38285
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
38286
38286
  const updatedTask = await extra.taskStore.getTask(taskId);
38287
38287
  if (!updatedTask) {
38288
38288
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -38970,12 +38970,12 @@ var StdioServerTransport = class {
38970
38970
  this.onclose?.();
38971
38971
  }
38972
38972
  send(message) {
38973
- return new Promise((resolve6) => {
38973
+ return new Promise((resolve7) => {
38974
38974
  const json2 = serializeMessage(message);
38975
38975
  if (this._stdout.write(json2)) {
38976
- resolve6();
38976
+ resolve7();
38977
38977
  } else {
38978
- this._stdout.once("drain", resolve6);
38978
+ this._stdout.once("drain", resolve7);
38979
38979
  }
38980
38980
  });
38981
38981
  }
@@ -47328,13 +47328,13 @@ var escapeSegment = escapeFolderName;
47328
47328
  function resolveFolders(rows) {
47329
47329
  const byPk = new Map(rows.map((row) => [row.pk, row]));
47330
47330
  const resolved = /* @__PURE__ */ new Map();
47331
- const resolve6 = (pk, seen) => {
47331
+ const resolve7 = (pk, seen) => {
47332
47332
  const cached2 = resolved.get(pk);
47333
47333
  if (cached2) return cached2;
47334
47334
  const row = byPk.get(pk);
47335
47335
  if (!row || seen.has(pk)) return void 0;
47336
47336
  seen.add(pk);
47337
- const parent = row.parent !== null ? resolve6(row.parent, seen) : void 0;
47337
+ const parent = row.parent !== null ? resolve7(row.parent, seen) : void 0;
47338
47338
  const name = row.name ?? "";
47339
47339
  const path10 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
47340
47340
  const plainPath = parent ? `${parent.plainPath}/${name}` : name;
@@ -47348,7 +47348,7 @@ function resolveFolders(rows) {
47348
47348
  resolved.set(pk, info);
47349
47349
  return info;
47350
47350
  };
47351
- for (const row of rows) resolve6(row.pk, /* @__PURE__ */ new Set());
47351
+ for (const row of rows) resolve7(row.pk, /* @__PURE__ */ new Set());
47352
47352
  return resolved;
47353
47353
  }
47354
47354
  var coreDataMs = (seconds) => seconds === null || !Number.isFinite(seconds) ? void 0 : CORE_DATA_EPOCH_MS2 + seconds * 1e3;
@@ -52495,27 +52495,627 @@ function formatShortcutSetup(report) {
52495
52495
  return lines.join("\n");
52496
52496
  }
52497
52497
 
52498
- // src/services/privateHelperBuild.ts
52499
- import { spawnSync as spawnSync4 } from "node:child_process";
52498
+ // src/services/publicHelper.ts
52499
+ import { spawnSync as spawnSync3 } from "node:child_process";
52500
+ import { createHash as createHash4 } from "node:crypto";
52500
52501
  import {
52501
52502
  chmodSync,
52502
- existsSync as existsSync15,
52503
+ existsSync as existsSync14,
52503
52504
  mkdirSync as mkdirSync6,
52504
52505
  mkdtempSync as mkdtempSync5,
52506
+ readFileSync as readFileSync4,
52505
52507
  renameSync,
52506
52508
  rmSync as rmSync5,
52507
52509
  writeFileSync as writeFileSync4
52508
52510
  } from "node:fs";
52509
- import { release as release3 } from "node:os";
52510
- import { join as join25 } from "node:path";
52511
-
52512
- // src/services/privateHelper.ts
52513
- import { spawnSync as spawnSync3 } from "node:child_process";
52514
- import { createHash as createHash4 } from "node:crypto";
52515
- import { existsSync as existsSync14, readFileSync as readFileSync4 } from "node:fs";
52516
- import { homedir as homedir19 } from "node:os";
52511
+ import { homedir as homedir19, release as release3 } from "node:os";
52517
52512
  import { dirname as dirname8, join as join24, resolve as resolve5 } from "node:path";
52518
52513
  import { fileURLToPath as fileURLToPath2 } from "node:url";
52514
+ var PUBLIC_HELPER_PROTOCOL = 1;
52515
+ var PUBLIC_HELPER_DIR_ENV = "APPLE_NOTES_MCP_PUBLIC_HELPER_DIR";
52516
+ var PUBLIC_HELPER_TIMEOUT_ENV = "APPLE_NOTES_MCP_PUBLIC_HELPER_TIMEOUT_MS";
52517
+ var PUBLIC_HELPER_BINARY = "apple-notes-public-helper";
52518
+ var PUBLIC_HELPER_SOURCE = "native/public-helper/apple-notes-public-helper.swift";
52519
+ var PUBLIC_HELPER_MANIFEST = "manifest.json";
52520
+ var PUBLIC_HELPER_SETUP_COMMAND = "apple-notes-mcp setup --public-helper";
52521
+ var PUBLIC_HELPER_ACTIONS = /* @__PURE__ */ new Set(["hello", "decode_drawing"]);
52522
+ var DEFAULT_TIMEOUT_MS2 = 3e4;
52523
+ var MAX_OUTPUT_BYTES = 256 * 1024 * 1024;
52524
+ var publicManifestSchema = external_exports.object({
52525
+ schemaVersion: external_exports.literal(1),
52526
+ protocolVersion: external_exports.number().int(),
52527
+ sourceSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
52528
+ binarySha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
52529
+ builtAt: external_exports.string(),
52530
+ osVersion: external_exports.string(),
52531
+ compiler: external_exports.string()
52532
+ });
52533
+ function packageRoot(fromDir = dirname8(fileURLToPath2(import.meta.url))) {
52534
+ let dir = fromDir;
52535
+ for (; ; ) {
52536
+ const candidate = join24(dir, "package.json");
52537
+ if (existsSync14(candidate)) {
52538
+ try {
52539
+ if (JSON.parse(readFileSync4(candidate, "utf8")).name === "apple-notes-mcp")
52540
+ return dir;
52541
+ } catch {
52542
+ }
52543
+ }
52544
+ const parent = dirname8(dir);
52545
+ if (parent === dir) return resolve5(fromDir, "..");
52546
+ dir = parent;
52547
+ }
52548
+ }
52549
+ function defaultPublicHelperDeps(overrides = {}) {
52550
+ return {
52551
+ env: process.env,
52552
+ platform: process.platform,
52553
+ sourcePath: join24(packageRoot(), PUBLIC_HELPER_SOURCE),
52554
+ exists: existsSync14,
52555
+ readFile: (path10) => readFileSync4(path10),
52556
+ spawn: spawnSync3,
52557
+ ...overrides
52558
+ };
52559
+ }
52560
+ function publicHelperInstallDir(env = process.env) {
52561
+ const override = env[PUBLIC_HELPER_DIR_ENV]?.trim();
52562
+ if (override) return override;
52563
+ return join24(homedir19(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
52564
+ }
52565
+ function sha256Hex(data) {
52566
+ return createHash4("sha256").update(data).digest("hex");
52567
+ }
52568
+ function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
52569
+ const installDir = publicHelperInstallDir(deps.env);
52570
+ const binaryPath = join24(installDir, PUBLIC_HELPER_BINARY);
52571
+ const base = { installDir, binaryPath, sourcePath: deps.sourcePath, manifest: null };
52572
+ const fail = (reason, detail) => ({
52573
+ ...base,
52574
+ ready: false,
52575
+ reason,
52576
+ detail
52577
+ });
52578
+ if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
52579
+ if (!deps.exists(deps.sourcePath))
52580
+ return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
52581
+ const manifestPath = join24(installDir, PUBLIC_HELPER_MANIFEST);
52582
+ if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
52583
+ return fail(
52584
+ "helper_not_installed",
52585
+ `The public native helper is not built. Run \`${PUBLIC_HELPER_SETUP_COMMAND}\`.`
52586
+ );
52587
+ let manifest;
52588
+ try {
52589
+ manifest = publicManifestSchema.parse(JSON.parse(deps.readFile(manifestPath).toString("utf8")));
52590
+ } catch {
52591
+ return fail(
52592
+ "helper_manifest_invalid",
52593
+ `The helper manifest is unreadable. Run \`${PUBLIC_HELPER_SETUP_COMMAND}\`.`
52594
+ );
52595
+ }
52596
+ if (manifest.sourceSha256 !== sha256Hex(deps.readFile(deps.sourcePath)) || manifest.protocolVersion !== PUBLIC_HELPER_PROTOCOL)
52597
+ return {
52598
+ ...fail(
52599
+ "helper_stale",
52600
+ `The installed helper was built from a different source than this apple-notes-mcp version ships. Run \`${PUBLIC_HELPER_SETUP_COMMAND}\` again.`
52601
+ ),
52602
+ manifest
52603
+ };
52604
+ if (sha256Hex(deps.readFile(binaryPath)) !== manifest.binarySha256)
52605
+ return {
52606
+ ...fail(
52607
+ "helper_modified",
52608
+ `The helper binary no longer matches the checksum recorded when it was built. Run \`${PUBLIC_HELPER_SETUP_COMMAND}\` to rebuild it.`
52609
+ ),
52610
+ manifest
52611
+ };
52612
+ return { ...base, manifest, ready: true, reason: null, detail: null };
52613
+ }
52614
+ var UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
52615
+ "unsupported_platform",
52616
+ "helper_not_installed",
52617
+ "helper_stale",
52618
+ "helper_modified",
52619
+ "helper_manifest_invalid"
52620
+ ]);
52621
+ var PublicHelperError = class extends CodedError {
52622
+ constructor(code, message) {
52623
+ super(message, {
52624
+ code: UNAVAILABLE_CODES.has(code) ? "unsupported" : "operation_failed",
52625
+ helperCode: code
52626
+ });
52627
+ this.code = code;
52628
+ this.name = "PublicHelperError";
52629
+ }
52630
+ code;
52631
+ };
52632
+ var errorSchema = external_exports.object({ status: external_exports.literal("error"), code: external_exports.string(), message: external_exports.string() });
52633
+ var publicHelloSchema = external_exports.object({
52634
+ status: external_exports.literal("ok"),
52635
+ protocolVersion: external_exports.number().int(),
52636
+ sourceSha256: external_exports.string(),
52637
+ actions: external_exports.array(external_exports.string())
52638
+ }).passthrough();
52639
+ function callPublicHelper(action, fields = {}, deps = defaultPublicHelperDeps(), options = {}) {
52640
+ if (!PUBLIC_HELPER_ACTIONS.has(action))
52641
+ throw new PublicHelperError(
52642
+ "unknown_action",
52643
+ `"${action}" is not an action the server sends to the public helper.`
52644
+ );
52645
+ let binaryPath = options.binaryPath;
52646
+ if (!binaryPath) {
52647
+ const install = inspectPublicHelper(deps);
52648
+ if (!install.ready)
52649
+ throw new PublicHelperError(install.reason ?? "helper_not_installed", install.detail ?? "");
52650
+ binaryPath = install.binaryPath;
52651
+ }
52652
+ const timeout = Number.parseInt(deps.env[PUBLIC_HELPER_TIMEOUT_ENV] || "", 10) || options.timeoutMs || DEFAULT_TIMEOUT_MS2;
52653
+ const result = deps.spawn(binaryPath, [], {
52654
+ input: JSON.stringify({ protocol: PUBLIC_HELPER_PROTOCOL, action, ...fields }),
52655
+ encoding: "utf8",
52656
+ timeout,
52657
+ killSignal: "SIGKILL",
52658
+ maxBuffer: MAX_OUTPUT_BYTES
52659
+ });
52660
+ const errno = result.error?.code;
52661
+ if (errno === "ETIMEDOUT" || result.signal && result.status === null)
52662
+ throw new PublicHelperError("timeout", `The helper did not answer within ${timeout} ms.`);
52663
+ if (result.error)
52664
+ throw new PublicHelperError(
52665
+ "helper_unreachable",
52666
+ `Could not run the helper: ${result.error.message}`
52667
+ );
52668
+ let parsed;
52669
+ try {
52670
+ parsed = JSON.parse(String(result.stdout ?? "").trim());
52671
+ } catch {
52672
+ throw new PublicHelperError(
52673
+ "invalid_response",
52674
+ `The helper exited with status ${result.status} and no JSON response.`
52675
+ );
52676
+ }
52677
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
52678
+ throw new PublicHelperError("invalid_response", "The helper response is not a JSON object.");
52679
+ const object3 = parsed;
52680
+ if (result.status !== 0 || object3.status !== "ok") {
52681
+ const error2 = errorSchema.safeParse(object3);
52682
+ if (!error2.success)
52683
+ throw new PublicHelperError(
52684
+ "invalid_response",
52685
+ `The helper failed with an unrecognized response (exit ${result.status}).`
52686
+ );
52687
+ throw new PublicHelperError(error2.data.code, error2.data.message);
52688
+ }
52689
+ return object3;
52690
+ }
52691
+ function defaultPublicHelperBuildDeps() {
52692
+ return {
52693
+ ...defaultPublicHelperDeps(),
52694
+ osVersion: () => {
52695
+ const r = spawnSync3("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
52696
+ return r.status === 0 ? r.stdout.trim() : `Darwin ${release3()}`;
52697
+ },
52698
+ now: () => /* @__PURE__ */ new Date()
52699
+ };
52700
+ }
52701
+ function sourceDigestSwift(sourceSha) {
52702
+ return `let helperSourceSHA256 = "${sourceSha}"
52703
+ `;
52704
+ }
52705
+ var PUBLIC_HELPER_BUNDLE_ID = "apple-notes-mcp.public-helper";
52706
+ function publicHelperInfoPlist() {
52707
+ return [
52708
+ '<?xml version="1.0" encoding="UTF-8"?>',
52709
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
52710
+ '<plist version="1.0">',
52711
+ "<dict>",
52712
+ " <key>CFBundleIdentifier</key>",
52713
+ ` <string>${PUBLIC_HELPER_BUNDLE_ID}</string>`,
52714
+ " <key>CFBundleName</key>",
52715
+ " <string>apple-notes-mcp public helper</string>",
52716
+ " <key>CFBundleInfoDictionaryVersion</key>",
52717
+ " <string>6.0</string>",
52718
+ "</dict>",
52719
+ "</plist>",
52720
+ ""
52721
+ ].join("\n");
52722
+ }
52723
+ function publicHelperCompileArguments(sourcePath, digestPath, plistPath, outputPath) {
52724
+ return [
52725
+ "swiftc",
52726
+ "-O",
52727
+ "-parse-as-library",
52728
+ "-framework",
52729
+ "AppKit",
52730
+ "-framework",
52731
+ "PencilKit",
52732
+ "-Xlinker",
52733
+ "-sectcreate",
52734
+ "-Xlinker",
52735
+ "__TEXT",
52736
+ "-Xlinker",
52737
+ "__info_plist",
52738
+ "-Xlinker",
52739
+ plistPath,
52740
+ sourcePath,
52741
+ digestPath,
52742
+ "-o",
52743
+ outputPath
52744
+ ];
52745
+ }
52746
+ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
52747
+ const steps = [];
52748
+ const done = (ok) => ({
52749
+ ok,
52750
+ checkOnly,
52751
+ steps,
52752
+ installation: inspectPublicHelper(deps)
52753
+ });
52754
+ if (checkOnly) {
52755
+ const installation2 = inspectPublicHelper(deps);
52756
+ steps.push({
52757
+ step: "inspect installed helper",
52758
+ ok: installation2.ready,
52759
+ detail: installation2.ready ? installation2.binaryPath : installation2.detail ?? void 0
52760
+ });
52761
+ return { ok: installation2.ready, checkOnly, steps, installation: installation2 };
52762
+ }
52763
+ if (deps.platform !== "darwin") {
52764
+ steps.push({ step: "platform", ok: false, detail: "macOS only" });
52765
+ return done(false);
52766
+ }
52767
+ if (!deps.exists(deps.sourcePath)) {
52768
+ steps.push({ step: "locate source", ok: false, detail: deps.sourcePath });
52769
+ return done(false);
52770
+ }
52771
+ const sourceSha = sha256Hex(deps.readFile(deps.sourcePath));
52772
+ steps.push({ step: "locate source", ok: true, detail: `sha256 ${sourceSha}` });
52773
+ const version3 = deps.spawn("/usr/bin/xcrun", ["swiftc", "--version"], { encoding: "utf8" });
52774
+ if (version3.status !== 0) {
52775
+ steps.push({
52776
+ step: "find compiler",
52777
+ ok: false,
52778
+ detail: "No Swift compiler found. Install the Command Line Tools with `xcode-select --install`."
52779
+ });
52780
+ return done(false);
52781
+ }
52782
+ const compiler = String(version3.stdout || version3.stderr || "").split("\n").find((line) => line.includes("Swift version"))?.trim() || "swiftc";
52783
+ steps.push({ step: "find compiler", ok: true, detail: compiler });
52784
+ const installDir = publicHelperInstallDir(deps.env);
52785
+ mkdirSync6(installDir, { recursive: true, mode: 448 });
52786
+ const staging = mkdtempSync5(join24(installDir, ".staging-"));
52787
+ try {
52788
+ const stagedBinary = join24(staging, PUBLIC_HELPER_BINARY);
52789
+ const digestPath = join24(staging, "source-digest.swift");
52790
+ const plistPath = join24(staging, "Info.plist");
52791
+ writeFileSync4(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
52792
+ writeFileSync4(plistPath, publicHelperInfoPlist(), { mode: 384 });
52793
+ const compile = deps.spawn(
52794
+ "/usr/bin/xcrun",
52795
+ publicHelperCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
52796
+ { encoding: "utf8", timeout: 3e5 }
52797
+ );
52798
+ if (compile.status !== 0) {
52799
+ steps.push({
52800
+ step: "compile",
52801
+ ok: false,
52802
+ detail: String(compile.stderr || compile.error?.message || "swiftc failed").slice(0, 4e3)
52803
+ });
52804
+ return done(false);
52805
+ }
52806
+ steps.push({ step: "compile", ok: true });
52807
+ const sign = deps.spawn(
52808
+ "/usr/bin/codesign",
52809
+ ["--force", "--sign", "-", "--identifier", PUBLIC_HELPER_BUNDLE_ID, stagedBinary],
52810
+ { encoding: "utf8" }
52811
+ );
52812
+ if (sign.status !== 0) {
52813
+ steps.push({
52814
+ step: "ad-hoc sign",
52815
+ ok: false,
52816
+ detail: String(sign.stderr || "codesign failed")
52817
+ });
52818
+ return done(false);
52819
+ }
52820
+ steps.push({ step: "ad-hoc sign", ok: true });
52821
+ let hello;
52822
+ try {
52823
+ hello = publicHelloSchema.parse(
52824
+ callPublicHelper("hello", {}, deps, { binaryPath: stagedBinary })
52825
+ );
52826
+ } catch (error2) {
52827
+ steps.push({
52828
+ step: "handshake",
52829
+ ok: false,
52830
+ detail: error2 instanceof Error ? error2.message : String(error2)
52831
+ });
52832
+ return done(false);
52833
+ }
52834
+ if (hello.protocolVersion !== PUBLIC_HELPER_PROTOCOL || hello.sourceSha256 !== sourceSha) {
52835
+ steps.push({
52836
+ step: "handshake",
52837
+ ok: false,
52838
+ detail: `helper reported protocol ${hello.protocolVersion}, source ${hello.sourceSha256}`
52839
+ });
52840
+ return done(false);
52841
+ }
52842
+ steps.push({ step: "handshake", ok: true, detail: `actions: ${hello.actions.join(", ")}` });
52843
+ const manifest = {
52844
+ schemaVersion: 1,
52845
+ protocolVersion: hello.protocolVersion,
52846
+ sourceSha256: sourceSha,
52847
+ binarySha256: sha256Hex(deps.readFile(stagedBinary)),
52848
+ builtAt: deps.now().toISOString(),
52849
+ osVersion: deps.osVersion(),
52850
+ compiler
52851
+ };
52852
+ chmodSync(stagedBinary, 448);
52853
+ renameSync(stagedBinary, join24(installDir, PUBLIC_HELPER_BINARY));
52854
+ writeFileSync4(
52855
+ join24(installDir, PUBLIC_HELPER_MANIFEST),
52856
+ JSON.stringify(manifest, null, 2) + "\n",
52857
+ { mode: 384 }
52858
+ );
52859
+ steps.push({ step: "install", ok: true, detail: installDir });
52860
+ } finally {
52861
+ rmSync5(staging, { recursive: true, force: true });
52862
+ }
52863
+ const installation = inspectPublicHelper(deps);
52864
+ steps.push({
52865
+ step: "verify installation",
52866
+ ok: installation.ready,
52867
+ detail: installation.ready ? void 0 : installation.detail ?? void 0
52868
+ });
52869
+ return { ok: installation.ready, checkOnly, steps, installation };
52870
+ }
52871
+ function formatPublicHelperBuild(report) {
52872
+ const lines = ["Apple Notes MCP public native helper", ""];
52873
+ for (const step of report.steps)
52874
+ lines.push(`${step.ok ? "\u2713" : "\u2717"} ${step.step}${step.detail ? `: ${step.detail}` : ""}`);
52875
+ lines.push("");
52876
+ if (report.ok) lines.push(`Installed at ${report.installation.binaryPath}.`);
52877
+ else if (report.checkOnly) lines.push(`Run \`${PUBLIC_HELPER_SETUP_COMMAND}\` to build it.`);
52878
+ else lines.push("The helper was not installed. Fix the failed step above and run setup again.");
52879
+ return lines.join("\n");
52880
+ }
52881
+
52882
+ // src/utils/noteStoreQuery.ts
52883
+ function parseNoteObjectId(noteId3) {
52884
+ const match = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/ICNote\/p(\d{1,15})$/.exec(noteId3);
52885
+ if (!match)
52886
+ throw new NoteStoreError(
52887
+ `Invalid note ID format: "${noteId3}". Expected format: x-coredata://UUID/ICNote/pNNN`,
52888
+ "invalid_input"
52889
+ );
52890
+ return { store: match[1], pk: Number(match[2]) };
52891
+ }
52892
+ function queryNoteScoped(sql, pk, dbPath2 = NOTES_DB_PATH7) {
52893
+ return runReadOnlySql(dbPath2, sql, { pk: { int: pk } }).split("\n");
52894
+ }
52895
+ var NOTE_STATE_SQL = `SELECT json_object('found', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk AND n.Z_ENT = ${entity("ICNote")}), 'locked', (SELECT COALESCE(n.ZISPASSWORDPROTECTED, 0) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk));`;
52896
+ function assertNoteReadable(stateLine, noteId3) {
52897
+ let state;
52898
+ try {
52899
+ state = JSON.parse(stateLine || "{}");
52900
+ } catch {
52901
+ throw new NoteStoreError("Unexpected response from the Notes database.", "query_error");
52902
+ }
52903
+ if (!state.found)
52904
+ throw new CodedError(`No note found in the database for ID "${noteId3}".`, {
52905
+ code: "not_found"
52906
+ });
52907
+ if (state.locked)
52908
+ throw new CodedError(
52909
+ "This note is password-protected; its attachments are encrypted and cannot be read.",
52910
+ { code: "unsupported" }
52911
+ );
52912
+ }
52913
+
52914
+ // src/utils/noteDrawings.ts
52915
+ function drawingRowsSql(columns, dataColumn) {
52916
+ return [
52917
+ NOTE_STATE_SQL,
52918
+ `SELECT json_group_array(json_object('pk', a.Z_PK, 'identifier', a.ZIDENTIFIER, 'uti', a.ZTYPEUTI, 'data', hex(a.${dataColumn}))) FROM (SELECT * FROM ZICCLOUDSYNCINGOBJECT a WHERE a.ZNOTE = @pk AND a.ZTYPEUTI IN ('com.apple.drawing.2', 'com.apple.drawing') AND ${notTombstonedSql(columns, "a")} ORDER BY a.Z_PK) a;`
52919
+ ].join("\n");
52920
+ }
52921
+ function readDrawingRows2(noteId3, dbPath2 = NOTES_DB_PATH7) {
52922
+ const { store, pk } = parseNoteObjectId(noteId3);
52923
+ const columns = readColumns(dbPath2);
52924
+ const dataColumn = columns.has("ZMERGEABLEDATA1") ? "ZMERGEABLEDATA1" : columns.has("ZMERGEABLEDATA") ? "ZMERGEABLEDATA" : null;
52925
+ if (!dataColumn)
52926
+ throw new NoteStoreError(
52927
+ "This macOS version's Notes database has no drawing data column.",
52928
+ "schema"
52929
+ );
52930
+ const [stateLine, rowsLine] = queryNoteScoped(drawingRowsSql(columns, dataColumn), pk, dbPath2);
52931
+ assertNoteReadable(stateLine, noteId3);
52932
+ const rows = JSON.parse(rowsLine || "[]");
52933
+ return rows.map((row) => ({
52934
+ pk: row.pk,
52935
+ attachmentId: `x-coredata://${store}/ICAttachment/p${row.pk}`,
52936
+ identifier: row.identifier ?? "",
52937
+ typeUti: row.uti,
52938
+ data: row.data ? Buffer.from(row.data, "hex") : null
52939
+ }));
52940
+ }
52941
+ function svgNumber(value) {
52942
+ if (!Number.isFinite(value)) return "0";
52943
+ const rounded = Math.round(value * 100) / 100;
52944
+ return Object.is(rounded, -0) ? "0" : String(rounded);
52945
+ }
52946
+ function escapeAttribute2(value) {
52947
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
52948
+ }
52949
+ var clampChannel = (value) => Math.min(255, Math.max(0, Math.round(value || 0)));
52950
+ var clampUnit = (value) => Math.min(1, Math.max(0, Number.isFinite(value) ? value : 1));
52951
+ function drawingViewBox(strokes, fallback) {
52952
+ let minX = Infinity;
52953
+ let minY = Infinity;
52954
+ let maxX = -Infinity;
52955
+ let maxY = -Infinity;
52956
+ for (const stroke of strokes) {
52957
+ const pad = stroke.width / 2;
52958
+ const extend2 = (x, y, r) => {
52959
+ minX = Math.min(minX, x - r);
52960
+ minY = Math.min(minY, y - r);
52961
+ maxX = Math.max(maxX, x + r);
52962
+ maxY = Math.max(maxY, y + r);
52963
+ };
52964
+ if (stroke.points?.length) for (const p of stroke.points) extend2(p.x, p.y, pad);
52965
+ else {
52966
+ extend2(stroke.bounds.x, stroke.bounds.y, 0);
52967
+ extend2(stroke.bounds.x + stroke.bounds.width, stroke.bounds.y + stroke.bounds.height, 0);
52968
+ }
52969
+ }
52970
+ if (minX === Infinity) return fallback ?? { x: 0, y: 0, width: 1, height: 1 };
52971
+ return { x: minX, y: minY, width: Math.max(1, maxX - minX), height: Math.max(1, maxY - minY) };
52972
+ }
52973
+ function drawingToSvg(strokes, fallback) {
52974
+ const box = drawingViewBox(strokes, fallback);
52975
+ const parts = [
52976
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${svgNumber(box.x)} ${svgNumber(box.y)} ${svgNumber(box.width)} ${svgNumber(box.height)}" width="${svgNumber(box.width)}" height="${svgNumber(box.height)}">`
52977
+ ];
52978
+ for (const stroke of strokes) {
52979
+ const points = stroke.points ?? [];
52980
+ if (points.length === 0) continue;
52981
+ const c = stroke.color;
52982
+ const color = `rgb(${clampChannel(c.red)},${clampChannel(c.green)},${clampChannel(c.blue)})`;
52983
+ const shared = `stroke="${color}" stroke-opacity="${svgNumber(clampUnit(c.alpha))}" data-ink="${escapeAttribute2(stroke.inkType)}"`;
52984
+ if (points.length === 1) {
52985
+ parts.push(
52986
+ `<circle cx="${svgNumber(points[0].x)}" cy="${svgNumber(points[0].y)}" r="${svgNumber(stroke.width / 2)}" fill="${color}" fill-opacity="${svgNumber(clampUnit(c.alpha))}" data-ink="${escapeAttribute2(stroke.inkType)}"/>`
52987
+ );
52988
+ continue;
52989
+ }
52990
+ const d = points.map((p, i) => `${i === 0 ? "M" : "L"}${svgNumber(p.x)} ${svgNumber(p.y)}`).join(" ");
52991
+ parts.push(
52992
+ `<path d="${d}" fill="none" ${shared} stroke-width="${svgNumber(stroke.width)}" stroke-linecap="round" stroke-linejoin="round"/>`
52993
+ );
52994
+ }
52995
+ parts.push("</svg>");
52996
+ return parts.join("\n");
52997
+ }
52998
+
52999
+ // src/services/noteDrawings.ts
53000
+ var bounds = external_exports.object({ x: external_exports.number(), y: external_exports.number(), width: external_exports.number(), height: external_exports.number() });
53001
+ var strokeSchema = external_exports.object({
53002
+ inkType: external_exports.string(),
53003
+ color: external_exports.object({
53004
+ red: external_exports.number(),
53005
+ green: external_exports.number(),
53006
+ blue: external_exports.number(),
53007
+ alpha: external_exports.number()
53008
+ }),
53009
+ width: external_exports.number(),
53010
+ pointCount: external_exports.number().int(),
53011
+ bounds,
53012
+ points: external_exports.array(
53013
+ external_exports.object({
53014
+ x: external_exports.number(),
53015
+ y: external_exports.number(),
53016
+ width: external_exports.number(),
53017
+ opacity: external_exports.number(),
53018
+ force: external_exports.number()
53019
+ })
53020
+ ).optional(),
53021
+ transformApplied: external_exports.boolean().optional()
53022
+ }).strip();
53023
+ var decodedDrawingSchema = external_exports.object({
53024
+ status: external_exports.literal("ok"),
53025
+ strokeCount: external_exports.number().int(),
53026
+ strokes: external_exports.array(strokeSchema),
53027
+ truncated: external_exports.boolean(),
53028
+ bounds
53029
+ });
53030
+ function decodeRow(row, format, includePoints, deps) {
53031
+ const base = {
53032
+ attachmentId: row.attachmentId,
53033
+ identifier: row.identifier,
53034
+ typeUti: row.typeUti,
53035
+ status: "error"
53036
+ };
53037
+ if (!row.data || row.data.length === 0)
53038
+ return { ...base, code: "no_data", message: "This drawing has no stored PencilKit data." };
53039
+ let decoded;
53040
+ try {
53041
+ const needPoints = includePoints || format !== "json";
53042
+ const raw = callPublicHelper(
53043
+ "decode_drawing",
53044
+ { dataBase64: row.data.toString("base64"), includePoints: needPoints },
53045
+ deps
53046
+ );
53047
+ const parsed = decodedDrawingSchema.safeParse(raw);
53048
+ if (!parsed.success)
53049
+ return { ...base, code: "invalid_response", message: "Unexpected helper response." };
53050
+ decoded = parsed.data;
53051
+ } catch (error2) {
53052
+ const code = error2 instanceof PublicHelperError ? error2.code : "internal_error";
53053
+ return { ...base, code, message: error2 instanceof Error ? error2.message : String(error2) };
53054
+ }
53055
+ const strokes = decoded.strokes;
53056
+ const result = {
53057
+ ...base,
53058
+ status: "ok",
53059
+ strokeCount: decoded.strokeCount,
53060
+ bounds: decoded.bounds,
53061
+ truncated: decoded.truncated
53062
+ };
53063
+ if (format !== "json") result.svg = drawingToSvg(strokes, decoded.bounds);
53064
+ if (format !== "svg")
53065
+ result.strokes = includePoints ? strokes : strokes.map(({ points: _points, ...rest }) => rest);
53066
+ return result;
53067
+ }
53068
+ function getNoteDrawings(noteId3, options = {}) {
53069
+ const format = options.format ?? "json";
53070
+ const includePoints = options.includePoints ?? true;
53071
+ const deps = options.deps ?? defaultPublicHelperDeps();
53072
+ const rows = (options.readRows ?? readDrawingRows2)(noteId3, options.dbPath);
53073
+ if (rows.length === 0) return { id: noteId3, drawingCount: 0, status: "none", drawings: [] };
53074
+ const install = inspectPublicHelper(deps);
53075
+ if (!install.ready)
53076
+ throw new PublicHelperError(install.reason ?? "helper_not_installed", install.detail ?? "");
53077
+ const drawings = rows.map((row) => decodeRow(row, format, includePoints, deps));
53078
+ const ok = drawings.filter((d) => d.status === "ok").length;
53079
+ return {
53080
+ id: noteId3,
53081
+ drawingCount: drawings.length,
53082
+ status: ok === drawings.length ? "ok" : ok ? "partial" : "error",
53083
+ drawings
53084
+ };
53085
+ }
53086
+ function formatNoteDrawings(result) {
53087
+ if (result.drawingCount === 0) return `No classic PencilKit drawings in note ${result.id}.`;
53088
+ const lines = [
53089
+ `${result.drawingCount} classic drawing${result.drawingCount === 1 ? "" : "s"} in note ${result.id} (${result.status}):`
53090
+ ];
53091
+ for (const d of result.drawings)
53092
+ lines.push(
53093
+ d.status === "ok" ? `- ${d.attachmentId}: ${d.strokeCount} stroke${d.strokeCount === 1 ? "" : "s"}${d.truncated ? " (truncated)" : ""}` : `- ${d.attachmentId}: error ${d.code}: ${d.message}`
53094
+ );
53095
+ return lines.join("\n");
53096
+ }
53097
+
53098
+ // src/services/privateHelperBuild.ts
53099
+ import { spawnSync as spawnSync5 } from "node:child_process";
53100
+ import {
53101
+ chmodSync as chmodSync2,
53102
+ existsSync as existsSync16,
53103
+ mkdirSync as mkdirSync7,
53104
+ mkdtempSync as mkdtempSync6,
53105
+ renameSync as renameSync2,
53106
+ rmSync as rmSync6,
53107
+ writeFileSync as writeFileSync5
53108
+ } from "node:fs";
53109
+ import { release as release4 } from "node:os";
53110
+ import { join as join26 } from "node:path";
53111
+
53112
+ // src/services/privateHelper.ts
53113
+ import { spawnSync as spawnSync4 } from "node:child_process";
53114
+ import { createHash as createHash5 } from "node:crypto";
53115
+ import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
53116
+ import { homedir as homedir20 } from "node:os";
53117
+ import { dirname as dirname9, join as join25, resolve as resolve6 } from "node:path";
53118
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
52519
53119
  var PRIVATE_HELPER_PROTOCOL = 1;
52520
53120
  var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
52521
53121
  var HELPER_DIR_ENV = "APPLE_NOTES_MCP_PRIVATE_HELPER_DIR";
@@ -52528,8 +53128,8 @@ var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set([
52528
53128
  var HELPER_BINARY_NAME = "apple-notes-private-helper";
52529
53129
  var HELPER_SOURCE_RELATIVE = "native/private-helper/apple-notes-private-helper.m";
52530
53130
  var MANIFEST_NAME = "manifest.json";
52531
- var DEFAULT_TIMEOUT_MS2 = 2e4;
52532
- var MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
53131
+ var DEFAULT_TIMEOUT_MS3 = 2e4;
53132
+ var MAX_OUTPUT_BYTES2 = 4 * 1024 * 1024;
52533
53133
  var manifestSchema = external_exports.object({
52534
53134
  schemaVersion: external_exports.literal(1),
52535
53135
  protocolVersion: external_exports.number().int(),
@@ -52539,19 +53139,19 @@ var manifestSchema = external_exports.object({
52539
53139
  osVersion: external_exports.string(),
52540
53140
  compiler: external_exports.string()
52541
53141
  });
52542
- function packageRoot(fromDir = dirname8(fileURLToPath2(import.meta.url))) {
53142
+ function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
52543
53143
  let dir = fromDir;
52544
53144
  for (; ; ) {
52545
- const candidate = join24(dir, "package.json");
52546
- if (existsSync14(candidate)) {
53145
+ const candidate = join25(dir, "package.json");
53146
+ if (existsSync15(candidate)) {
52547
53147
  try {
52548
- const pkg = JSON.parse(readFileSync4(candidate, "utf8"));
53148
+ const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
52549
53149
  if (pkg.name === "apple-notes-mcp") return dir;
52550
53150
  } catch {
52551
53151
  }
52552
53152
  }
52553
- const parent = dirname8(dir);
52554
- if (parent === dir) return resolve5(fromDir, "..");
53153
+ const parent = dirname9(dir);
53154
+ if (parent === dir) return resolve6(fromDir, "..");
52555
53155
  dir = parent;
52556
53156
  }
52557
53157
  }
@@ -52559,10 +53159,10 @@ function defaultDeps2(overrides = {}) {
52559
53159
  return {
52560
53160
  env: process.env,
52561
53161
  platform: process.platform,
52562
- sourcePath: join24(packageRoot(), HELPER_SOURCE_RELATIVE),
52563
- exists: existsSync14,
52564
- readFile: (path10) => readFileSync4(path10),
52565
- spawn: spawnSync3,
53162
+ sourcePath: join25(packageRoot2(), HELPER_SOURCE_RELATIVE),
53163
+ exists: existsSync15,
53164
+ readFile: (path10) => readFileSync5(path10),
53165
+ spawn: spawnSync4,
52566
53166
  ...overrides
52567
53167
  };
52568
53168
  }
@@ -52572,14 +53172,14 @@ function privateHelperEnabled(env = process.env) {
52572
53172
  function helperInstallDir(env = process.env) {
52573
53173
  const override = env[HELPER_DIR_ENV]?.trim();
52574
53174
  if (override) return override;
52575
- return join24(homedir19(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
53175
+ return join25(homedir20(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
52576
53176
  }
52577
- function sha256Hex(data) {
52578
- return createHash4("sha256").update(data).digest("hex");
53177
+ function sha256Hex2(data) {
53178
+ return createHash5("sha256").update(data).digest("hex");
52579
53179
  }
52580
53180
  function inspectInstallation(deps = defaultDeps2()) {
52581
53181
  const installDir = helperInstallDir(deps.env);
52582
- const binaryPath = join24(installDir, HELPER_BINARY_NAME);
53182
+ const binaryPath = join25(installDir, HELPER_BINARY_NAME);
52583
53183
  const base = {
52584
53184
  installDir,
52585
53185
  binaryPath,
@@ -52596,8 +53196,8 @@ function inspectInstallation(deps = defaultDeps2()) {
52596
53196
  if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
52597
53197
  if (!deps.exists(deps.sourcePath))
52598
53198
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
52599
- base.expectedSourceSha256 = sha256Hex(deps.readFile(deps.sourcePath));
52600
- const manifestPath = join24(installDir, MANIFEST_NAME);
53199
+ base.expectedSourceSha256 = sha256Hex2(deps.readFile(deps.sourcePath));
53200
+ const manifestPath = join25(installDir, MANIFEST_NAME);
52601
53201
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
52602
53202
  return fail(
52603
53203
  "helper_not_installed",
@@ -52618,7 +53218,7 @@ function inspectInstallation(deps = defaultDeps2()) {
52618
53218
  "helper_stale",
52619
53219
  "The installed helper was built from a different helper source or protocol than this apple-notes-mcp version ships. Run `apple-notes-mcp setup --native-helper` again."
52620
53220
  );
52621
- if (sha256Hex(deps.readFile(binaryPath)) !== manifest.binarySha256)
53221
+ if (sha256Hex2(deps.readFile(binaryPath)) !== manifest.binarySha256)
52622
53222
  return fail(
52623
53223
  "helper_modified",
52624
53224
  "The helper binary does not match the checksum recorded when it was built. Run `apple-notes-mcp setup --native-helper` to rebuild it."
@@ -52635,7 +53235,7 @@ var PrivateHelperError = class extends Error {
52635
53235
  code;
52636
53236
  details;
52637
53237
  };
52638
- var errorSchema = external_exports.object({
53238
+ var errorSchema2 = external_exports.object({
52639
53239
  status: external_exports.literal("error"),
52640
53240
  code: external_exports.string(),
52641
53241
  message: external_exports.string()
@@ -52707,13 +53307,13 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
52707
53307
  throw new PrivateHelperError(install.reason || "helper_not_installed", install.detail || "");
52708
53308
  binaryPath = install.binaryPath;
52709
53309
  }
52710
- const timeout = Number.parseInt(deps.env[TIMEOUT_ENV] || "", 10) || DEFAULT_TIMEOUT_MS2;
53310
+ const timeout = Number.parseInt(deps.env[TIMEOUT_ENV] || "", 10) || DEFAULT_TIMEOUT_MS3;
52711
53311
  const result = deps.spawn(binaryPath, [], {
52712
53312
  input: JSON.stringify({ protocol: PRIVATE_HELPER_PROTOCOL, action, ...fields }),
52713
53313
  encoding: "utf8",
52714
53314
  timeout,
52715
53315
  killSignal: "SIGKILL",
52716
- maxBuffer: MAX_OUTPUT_BYTES,
53316
+ maxBuffer: MAX_OUTPUT_BYTES2,
52717
53317
  env: deps.env
52718
53318
  });
52719
53319
  const errno = result.error?.code;
@@ -52738,7 +53338,7 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
52738
53338
  throw new PrivateHelperError("invalid_response", "The helper response is not a JSON object");
52739
53339
  const object3 = parsed;
52740
53340
  if (result.status !== 0 || object3.status === "error") {
52741
- const error2 = errorSchema.safeParse(object3);
53341
+ const error2 = errorSchema2.safeParse(object3);
52742
53342
  if (!error2.success)
52743
53343
  throw new PrivateHelperError(
52744
53344
  "invalid_response",
@@ -52841,8 +53441,8 @@ function defaultBuildDeps() {
52841
53441
  return {
52842
53442
  ...defaultDeps2(),
52843
53443
  osVersion: () => {
52844
- const r = spawnSync4("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
52845
- return r.status === 0 ? r.stdout.trim() : `Darwin ${release3()}`;
53444
+ const r = spawnSync5("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
53445
+ return r.status === 0 ? r.stdout.trim() : `Darwin ${release4()}`;
52846
53446
  },
52847
53447
  now: () => /* @__PURE__ */ new Date()
52848
53448
  };
@@ -52892,7 +53492,7 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
52892
53492
  return done(false);
52893
53493
  }
52894
53494
  const source = deps.readFile(deps.sourcePath);
52895
- const sourceSha = sha256Hex(source);
53495
+ const sourceSha = sha256Hex2(source);
52896
53496
  steps.push({
52897
53497
  step: "locate source",
52898
53498
  ok: true,
@@ -52911,10 +53511,10 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
52911
53511
  const compiler = String(clangVersion.stdout || "").split("\n")[0] || "clang";
52912
53512
  steps.push({ step: "find compiler", ok: true, detail: compiler });
52913
53513
  const installDir = helperInstallDir(deps.env);
52914
- mkdirSync6(installDir, { recursive: true, mode: 448 });
52915
- const staging = mkdtempSync5(join25(installDir, ".staging-"));
53514
+ mkdirSync7(installDir, { recursive: true, mode: 448 });
53515
+ const staging = mkdtempSync6(join26(installDir, ".staging-"));
52916
53516
  try {
52917
- const stagedBinary = join25(staging, HELPER_BINARY_NAME);
53517
+ const stagedBinary = join26(staging, HELPER_BINARY_NAME);
52918
53518
  const compile = deps.spawn(
52919
53519
  "/usr/bin/xcrun",
52920
53520
  compileArguments(deps.sourcePath, stagedBinary, sourceSha),
@@ -52985,19 +53585,19 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
52985
53585
  schemaVersion: 1,
52986
53586
  protocolVersion: hello.protocolVersion,
52987
53587
  sourceSha256: sourceSha,
52988
- binarySha256: sha256Hex(deps.readFile(stagedBinary)),
53588
+ binarySha256: sha256Hex2(deps.readFile(stagedBinary)),
52989
53589
  builtAt: deps.now().toISOString(),
52990
53590
  osVersion: deps.osVersion(),
52991
53591
  compiler
52992
53592
  };
52993
- chmodSync(stagedBinary, 448);
52994
- renameSync(stagedBinary, join25(installDir, HELPER_BINARY_NAME));
52995
- writeFileSync4(join25(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
53593
+ chmodSync2(stagedBinary, 448);
53594
+ renameSync2(stagedBinary, join26(installDir, HELPER_BINARY_NAME));
53595
+ writeFileSync5(join26(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
52996
53596
  mode: 384
52997
53597
  });
52998
53598
  steps.push({ step: "install", ok: true, detail: installDir });
52999
53599
  } finally {
53000
- if (existsSync15(staging)) rmSync5(staging, { recursive: true, force: true });
53600
+ if (existsSync16(staging)) rmSync6(staging, { recursive: true, force: true });
53001
53601
  }
53002
53602
  const installation = inspectInstallation(deps);
53003
53603
  steps.push({
@@ -53137,6 +53737,11 @@ function registerPrivateHelperTools(server2, manager, depsFactory = () => defaul
53137
53737
  loadFileConfig();
53138
53738
  var require2 = createRequire(import.meta.url);
53139
53739
  var { version: version2 } = require2("../package.json");
53740
+ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--public-helper")) {
53741
+ const report = buildPublicHelper(process.argv.slice(3).includes("--check"));
53742
+ process.stdout.write(formatPublicHelperBuild(report) + "\n");
53743
+ process.exit(report.ok ? 0 : 1);
53744
+ }
53140
53745
  if (process.argv[2] === "setup" && process.argv.slice(3).includes("--native-helper")) {
53141
53746
  const report = buildPrivateHelper(process.argv.slice(3).includes("--check"));
53142
53747
  process.stdout.write(formatHelperBuild(report) + "\n");
@@ -56166,6 +56771,41 @@ ${lines.join("\n")}${more}`,
56166
56771
  );
56167
56772
  }, "Error listing notes")
56168
56773
  );
56774
+ registerTool(
56775
+ "get-note-drawings",
56776
+ {
56777
+ description: "Use when: reading the strokes of a note's classic PencilKit drawings (com.apple.drawing / com.apple.drawing.2 attachments), by note id, as JSON or SVG.\nReturns: per drawing its attachment id, status (ok/error with a code), stroke count, bounds, and strokes (ink type, sRGB color, width, points) and/or a standalone SVG document; overall status ok/partial/error/none.\nDo not use when: the drawing is a modern Paper sketch (com.apple.paper is not decoded here) or you want the attachment file itself (save-attachment).\nNote: read-only. Needs Full Disk Access and the public native helper built once with `apple-notes-mcp setup --public-helper` (compiled locally with PencilKit; no Notes writes).",
56778
+ inputSchema: {
56779
+ id: noteIdInput,
56780
+ format: external_exports.enum(["json", "svg", "both"]).optional().describe(
56781
+ '"json" (default) returns strokes, "svg" returns SVG documents, "both" returns both'
56782
+ ),
56783
+ includePoints: external_exports.boolean().optional().describe(
56784
+ "Include per-point x/y/width/opacity/force in JSON strokes (default true). SVG output always uses the points."
56785
+ )
56786
+ },
56787
+ outputSchema: {
56788
+ id: external_exports.string().optional(),
56789
+ drawingCount: external_exports.number().optional(),
56790
+ status: external_exports.string().optional(),
56791
+ drawings: external_exports.array(external_exports.object({}).passthrough()).optional(),
56792
+ pointsOmitted: external_exports.boolean().optional()
56793
+ }
56794
+ },
56795
+ withErrorHandling(({ id: id2, format, includePoints }) => {
56796
+ let result = getNoteDrawings(id2, { format, includePoints });
56797
+ let pointsOmitted = false;
56798
+ if (Buffer.byteLength(JSON.stringify(result)) > exportMaxResponseBytes() && includePoints !== false && format !== "svg") {
56799
+ result = getNoteDrawings(id2, { format, includePoints: false });
56800
+ pointsOmitted = true;
56801
+ }
56802
+ const text2 = formatNoteDrawings(result) + (pointsOmitted ? "\nStroke points were omitted to stay under the response size limit (APPLE_NOTES_MCP_EXPORT_MAX_BYTES)." : "");
56803
+ return successResponse(text2, {
56804
+ ...result,
56805
+ ...pointsOmitted ? { pointsOmitted } : {}
56806
+ });
56807
+ }, "Error reading drawings")
56808
+ );
56169
56809
  registerTool(
56170
56810
  "list-recent-notes",
56171
56811
  {
@@ -0,0 +1,257 @@
1
+ // apple-notes-public-helper
2
+ //
3
+ // A small command-line helper for apple-notes-mcp that uses PUBLIC Apple
4
+ // frameworks only. It never opens the Notes database and never writes to the
5
+ // Notes group container: the TypeScript server reads the bytes it needs
6
+ // (read-only) and hands them over on stdin.
7
+ //
8
+ // Protocol: one JSON object on stdin, one JSON object on stdout.
9
+ // request: {"protocol": 1, "action": "<name>", ...fields}
10
+ // success: {"status": "ok", ...}
11
+ // failure: {"status": "error", "code": "<code>", "message": "<text>"}
12
+ //
13
+ // Actions:
14
+ // hello protocol version, embedded source digest, action list
15
+ // decode_drawing PencilKit drawing bytes (base64) -> strokes
16
+ // encode_drawing strokes -> PencilKit drawing bytes (base64); used to build
17
+ // synthetic test fixtures, never to write to Notes
18
+ //
19
+ // Build (done by `apple-notes-mcp setup --public-helper`, which also generates
20
+ // the one-line source-digest file that defines helperSourceSHA256 and the
21
+ // Info.plist linked into __TEXT,__info_plist; PencilKit needs a bundle id):
22
+ // xcrun swiftc -O -parse-as-library -framework AppKit -framework PencilKit \
23
+ // -Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist -Xlinker Info.plist \
24
+ // apple-notes-public-helper.swift source-digest.swift -o apple-notes-public-helper
25
+
26
+ import AppKit
27
+ import Foundation
28
+ import PencilKit
29
+
30
+ let protocolVersion = 1
31
+ let maxInputBytes = 96 * 1024 * 1024
32
+ let maxStrokes = 20_000
33
+ let maxPoints = 1_000_000
34
+
35
+ // MARK: - Output
36
+
37
+ struct HelperFailure: Error {
38
+ let code: String
39
+ let message: String
40
+ }
41
+
42
+ func writeJSON(_ object: [String: Any]) {
43
+ let data: Data
44
+ if JSONSerialization.isValidJSONObject(object),
45
+ let encoded = try? JSONSerialization.data(withJSONObject: object, options: [.sortedKeys]) {
46
+ data = encoded
47
+ } else {
48
+ data = Data(#"{"code":"encode_failed","message":"response was not JSON-encodable","status":"error"}"#.utf8)
49
+ }
50
+ FileHandle.standardOutput.write(data)
51
+ FileHandle.standardOutput.write(Data([0x0A]))
52
+ }
53
+
54
+ func finish(_ object: [String: Any]) -> Never {
55
+ writeJSON(object)
56
+ exit(object["status"] as? String == "ok" ? 0 : 1)
57
+ }
58
+
59
+ func fail(_ failure: HelperFailure) -> Never {
60
+ finish(["status": "error", "code": failure.code, "message": failure.message])
61
+ }
62
+
63
+ /// Round to two decimals so large drawings stay compact on the wire.
64
+ func rounded(_ value: CGFloat) -> Double {
65
+ (Double(value) * 100).rounded() / 100
66
+ }
67
+
68
+ // MARK: - Drawings
69
+
70
+ func rgba(_ color: NSColor) -> [String: Any] {
71
+ let srgb = color.usingColorSpace(.sRGB) ?? color
72
+ func channel(_ value: CGFloat) -> Int { Int((min(max(value, 0), 1) * 255).rounded()) }
73
+ return [
74
+ "red": channel(srgb.redComponent),
75
+ "green": channel(srgb.greenComponent),
76
+ "blue": channel(srgb.blueComponent),
77
+ "alpha": rounded(srgb.alphaComponent),
78
+ ]
79
+ }
80
+
81
+ func decodeDrawing(_ request: [String: Any]) throws -> [String: Any] {
82
+ guard let encoded = request["dataBase64"] as? String,
83
+ let data = Data(base64Encoded: encoded)
84
+ else { throw HelperFailure(code: "invalid_request", message: "dataBase64 is required") }
85
+ if data.isEmpty { throw HelperFailure(code: "invalid_request", message: "drawing data is empty") }
86
+ let includePoints = request["includePoints"] as? Bool ?? true
87
+
88
+ let drawing: PKDrawing
89
+ do {
90
+ drawing = try PKDrawing(data: data)
91
+ } catch {
92
+ throw HelperFailure(
93
+ code: "undecodable",
94
+ message: "PencilKit could not read this drawing: \(error.localizedDescription)"
95
+ )
96
+ }
97
+
98
+ var strokes: [[String: Any]] = []
99
+ var pointBudget = maxPoints
100
+ var truncated = false
101
+ for stroke in drawing.strokes {
102
+ if strokes.count >= maxStrokes || pointBudget <= 0 {
103
+ truncated = true
104
+ break
105
+ }
106
+ let path = stroke.path
107
+ let transform = stroke.transform
108
+ var points: [[String: Any]] = []
109
+ var widthSum: CGFloat = 0
110
+ var count = 0
111
+ for point in path {
112
+ count += 1
113
+ widthSum += point.size.width
114
+ if includePoints {
115
+ if pointBudget <= 0 {
116
+ truncated = true
117
+ break
118
+ }
119
+ pointBudget -= 1
120
+ let location = point.location.applying(transform)
121
+ points.append([
122
+ "x": rounded(location.x),
123
+ "y": rounded(location.y),
124
+ "width": rounded(point.size.width),
125
+ "opacity": rounded(point.opacity),
126
+ "force": rounded(point.force),
127
+ ])
128
+ }
129
+ }
130
+ let bounds = stroke.renderBounds
131
+ var entry: [String: Any] = [
132
+ "inkType": stroke.ink.inkType.rawValue,
133
+ "color": rgba(stroke.ink.color),
134
+ "width": count > 0 ? rounded(widthSum / CGFloat(count)) : 0,
135
+ "pointCount": count,
136
+ "bounds": [
137
+ "x": rounded(bounds.origin.x), "y": rounded(bounds.origin.y),
138
+ "width": rounded(bounds.size.width), "height": rounded(bounds.size.height),
139
+ ],
140
+ ]
141
+ if includePoints { entry["points"] = points }
142
+ if !transform.isIdentity { entry["transformApplied"] = true }
143
+ strokes.append(entry)
144
+ }
145
+ let bounds = drawing.bounds
146
+ return [
147
+ "status": "ok",
148
+ "strokeCount": drawing.strokes.count,
149
+ "strokes": strokes,
150
+ "truncated": truncated,
151
+ "bounds": [
152
+ "x": rounded(bounds.origin.x), "y": rounded(bounds.origin.y),
153
+ "width": rounded(bounds.size.width), "height": rounded(bounds.size.height),
154
+ ],
155
+ ]
156
+ }
157
+
158
+ func inkType(named name: String) -> PKInk.InkType {
159
+ switch name {
160
+ case "com.apple.ink.marker", "marker": return .marker
161
+ case "com.apple.ink.pencil", "pencil": return .pencil
162
+ default: return .pen
163
+ }
164
+ }
165
+
166
+ func encodeDrawing(_ request: [String: Any]) throws -> [String: Any] {
167
+ guard let specs = request["strokes"] as? [[String: Any]], !specs.isEmpty else {
168
+ throw HelperFailure(code: "invalid_request", message: "strokes must be a non-empty array")
169
+ }
170
+ if specs.count > maxStrokes {
171
+ throw HelperFailure(code: "invalid_request", message: "too many strokes")
172
+ }
173
+ var strokes: [PKStroke] = []
174
+ let created = Date(timeIntervalSince1970: 0)
175
+ for spec in specs {
176
+ guard let rawPoints = spec["points"] as? [[String: Any]], rawPoints.count >= 2 else {
177
+ throw HelperFailure(code: "invalid_request", message: "each stroke needs at least two points")
178
+ }
179
+ let width = CGFloat((spec["width"] as? NSNumber)?.doubleValue ?? 3)
180
+ let color = spec["color"] as? [String: Any] ?? [:]
181
+ func component(_ key: String, _ fallback: Double) -> CGFloat {
182
+ CGFloat((color[key] as? NSNumber)?.doubleValue ?? fallback)
183
+ }
184
+ let ink = PKInk(
185
+ inkType(named: spec["inkType"] as? String ?? "pen"),
186
+ color: NSColor(
187
+ srgbRed: component("red", 0) / 255,
188
+ green: component("green", 0) / 255,
189
+ blue: component("blue", 0) / 255,
190
+ alpha: component("alpha", 1)
191
+ )
192
+ )
193
+ let last = Double(rawPoints.count - 1)
194
+ let controlPoints = rawPoints.enumerated().map { index, point in
195
+ PKStrokePoint(
196
+ location: CGPoint(
197
+ x: (point["x"] as? NSNumber)?.doubleValue ?? 0,
198
+ y: (point["y"] as? NSNumber)?.doubleValue ?? 0
199
+ ),
200
+ timeOffset: Double(index) / last,
201
+ size: CGSize(width: width, height: width),
202
+ opacity: 1,
203
+ force: 1,
204
+ azimuth: 0,
205
+ altitude: .pi / 2
206
+ )
207
+ }
208
+ strokes.append(PKStroke(ink: ink, path: PKStrokePath(controlPoints: controlPoints, creationDate: created)))
209
+ }
210
+ let data = PKDrawing(strokes: strokes).dataRepresentation()
211
+ return ["status": "ok", "strokeCount": strokes.count, "dataBase64": data.base64EncodedString()]
212
+ }
213
+
214
+ // MARK: - Dispatch
215
+
216
+ let actions = ["hello", "decode_drawing", "encode_drawing"]
217
+
218
+ func handle(_ request: [String: Any]) throws -> [String: Any] {
219
+ guard (request["protocol"] as? NSNumber)?.intValue == protocolVersion else {
220
+ throw HelperFailure(code: "protocol_mismatch", message: "expected protocol \(protocolVersion)")
221
+ }
222
+ switch request["action"] as? String {
223
+ case "hello":
224
+ return [
225
+ "status": "ok",
226
+ "protocolVersion": protocolVersion,
227
+ "sourceSha256": helperSourceSHA256,
228
+ "actions": actions,
229
+ ]
230
+ case "decode_drawing":
231
+ return try decodeDrawing(request)
232
+ case "encode_drawing":
233
+ return try encodeDrawing(request)
234
+ default:
235
+ throw HelperFailure(code: "unknown_action", message: "unknown action")
236
+ }
237
+ }
238
+
239
+ @main
240
+ struct PublicHelper {
241
+ static func main() {
242
+ let input = FileHandle.standardInput.readDataToEndOfFile()
243
+ if input.count > maxInputBytes {
244
+ fail(HelperFailure(code: "invalid_request", message: "request exceeds \(maxInputBytes) bytes"))
245
+ }
246
+ guard let request = (try? JSONSerialization.jsonObject(with: input)) as? [String: Any] else {
247
+ fail(HelperFailure(code: "invalid_request", message: "stdin must be one JSON object"))
248
+ }
249
+ do {
250
+ finish(try handle(request))
251
+ } catch let failure as HelperFailure {
252
+ fail(failure)
253
+ } catch {
254
+ fail(HelperFailure(code: "internal_error", message: error.localizedDescription))
255
+ }
256
+ }
257
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.9.5",
3
+ "version": "2.9.6",
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",