apple-notes-mcp 2.15.5 → 2.15.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.
Files changed (3) hide show
  1. package/README.md +21 -3
  2. package/build/index.js +268 -155
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -761,7 +761,7 @@ Disk Access.
761
761
 
762
762
  #### `get-note-tables`
763
763
 
764
- Reads every native table in one exact note, in body order, from the NoteStore
764
+ Reads a page of native tables in one exact note, in body order, from the NoteStore
765
765
  database. Each table is returned as GitHub-flavored Markdown and as JSON `rows`
766
766
  with stable `rowIds` and `columnIds`. Notes tables have no header row, so the
767
767
  Markdown uses the first row as the header. Pipes are escaped as `\|`,
@@ -770,8 +770,26 @@ backslashes are doubled, and line breaks inside a cell become `<br>`.
770
770
  | Parameter | Type | Required | Description |
771
771
  |-----------|------|----------|-------------|
772
772
  | `id` | string | Yes | Exact note ID (`x-coredata://…/ICNote/pNNN`) |
773
-
774
- `tableCellsComplete` is false when any table or cell could not be decoded. A
773
+ | `offset` | integer | No | Zero-based position of the first table (default 0); use `page.nextOffset` |
774
+ | `limit` | integer | No | Maximum tables per page (default and maximum 500) |
775
+ | `maxBytes` | integer | No | Full serialized response budget in UTF-8 bytes (default 4 MiB, minimum 1024 bytes, maximum 8 MiB) |
776
+
777
+ The byte budget includes both the text response and structured data, including
778
+ repeated Markdown and JSON escaping. Tables are returned whole. `tableCount`
779
+ and `page.totalAvailable` count all tables in the note; `page.returned` counts
780
+ the entries in this response. While `page.hasMore` is true, call again with
781
+ `offset: page.nextOffset` and the same `limit` and `maxBytes`. Each call reads
782
+ the note again, so finish paging before editing it.
783
+
784
+ If one table exceeds an otherwise empty page, its entry contains identifying
785
+ metadata, `complete: false`, `contentOmitted: true`, and a `reason`, without
786
+ partial rows or row/column IDs. The next offset advances past that entry. To
787
+ retrieve its content, retry that offset with a larger `maxBytes`, or export the
788
+ note to a file. If even the metadata exceeds the budget, the tool returns a
789
+ validation error. `page.stoppedAtSizeLimit` reports a byte-budget stop.
790
+
791
+ `tableCellsComplete` is false when any table or cell could not be decoded, or
792
+ when the current page omits a table's content for its byte budget. A
775
793
  cell holding an embedded object is `null` in `rows`, listed in
776
794
  `incompleteCells`, and shown as `[undecoded cell]` in Markdown. A table whose
777
795
  data cannot be decoded at all has `complete: false`, a `reason`, and no rows.
package/build/index.js CHANGED
@@ -27885,8 +27885,8 @@ var ZodSet = class _ZodSet extends ZodType {
27885
27885
  maxSize: { value: maxSize, message: errorUtil.toString(message) }
27886
27886
  });
27887
27887
  }
27888
- size(size, message) {
27889
- return this.min(size, message).max(size, message);
27888
+ size(size2, message) {
27889
+ return this.min(size2, message).max(size2, message);
27890
27890
  }
27891
27891
  nonempty(message) {
27892
27892
  return this.min(1, message);
@@ -36551,7 +36551,7 @@ var Protocol = class {
36551
36551
  if (abortController.signal.aborted) {
36552
36552
  return;
36553
36553
  }
36554
- const response = {
36554
+ const response2 = {
36555
36555
  result,
36556
36556
  jsonrpc: "2.0",
36557
36557
  id: request.id
@@ -36559,11 +36559,11 @@ var Protocol = class {
36559
36559
  if (relatedTaskId && this._taskMessageQueue) {
36560
36560
  await this._enqueueTaskMessage(relatedTaskId, {
36561
36561
  type: "response",
36562
- message: response,
36562
+ message: response2,
36563
36563
  timestamp: Date.now()
36564
36564
  }, capturedTransport?.sessionId);
36565
36565
  } else {
36566
- await capturedTransport?.send(response);
36566
+ await capturedTransport?.send(response2);
36567
36567
  }
36568
36568
  }, async (error2) => {
36569
36569
  if (abortController.signal.aborted) {
@@ -36616,29 +36616,29 @@ var Protocol = class {
36616
36616
  }
36617
36617
  handler(params);
36618
36618
  }
36619
- _onresponse(response) {
36620
- const messageId = Number(response.id);
36619
+ _onresponse(response2) {
36620
+ const messageId = Number(response2.id);
36621
36621
  const resolver = this._requestResolvers.get(messageId);
36622
36622
  if (resolver) {
36623
36623
  this._requestResolvers.delete(messageId);
36624
- if (isJSONRPCResultResponse(response)) {
36625
- resolver(response);
36624
+ if (isJSONRPCResultResponse(response2)) {
36625
+ resolver(response2);
36626
36626
  } else {
36627
- const error2 = new McpError(response.error.code, response.error.message, response.error.data);
36627
+ const error2 = new McpError(response2.error.code, response2.error.message, response2.error.data);
36628
36628
  resolver(error2);
36629
36629
  }
36630
36630
  return;
36631
36631
  }
36632
36632
  const handler = this._responseHandlers.get(messageId);
36633
36633
  if (handler === void 0) {
36634
- this._onerror(new Error(`Received a response for an unknown message ID: ${JSON.stringify(response)}`));
36634
+ this._onerror(new Error(`Received a response for an unknown message ID: ${JSON.stringify(response2)}`));
36635
36635
  return;
36636
36636
  }
36637
36637
  this._responseHandlers.delete(messageId);
36638
36638
  this._cleanupTimeout(messageId);
36639
36639
  let isTaskResponse = false;
36640
- if (isJSONRPCResultResponse(response) && response.result && typeof response.result === "object") {
36641
- const result = response.result;
36640
+ if (isJSONRPCResultResponse(response2) && response2.result && typeof response2.result === "object") {
36641
+ const result = response2.result;
36642
36642
  if (result.task && typeof result.task === "object") {
36643
36643
  const task = result.task;
36644
36644
  if (typeof task.taskId === "string") {
@@ -36650,10 +36650,10 @@ var Protocol = class {
36650
36650
  if (!isTaskResponse) {
36651
36651
  this._progressHandlers.delete(messageId);
36652
36652
  }
36653
- if (isJSONRPCResultResponse(response)) {
36654
- handler(response);
36653
+ if (isJSONRPCResultResponse(response2)) {
36654
+ handler(response2);
36655
36655
  } else {
36656
- const error2 = McpError.fromError(response.error.code, response.error.message, response.error.data);
36656
+ const error2 = McpError.fromError(response2.error.code, response2.error.message, response2.error.data);
36657
36657
  handler(error2);
36658
36658
  }
36659
36659
  }
@@ -36825,15 +36825,15 @@ var Protocol = class {
36825
36825
  const error2 = reason instanceof McpError ? reason : new McpError(ErrorCode.RequestTimeout, String(reason));
36826
36826
  reject(error2);
36827
36827
  };
36828
- this._responseHandlers.set(messageId, (response) => {
36828
+ this._responseHandlers.set(messageId, (response2) => {
36829
36829
  if (options?.signal?.aborted) {
36830
36830
  return;
36831
36831
  }
36832
- if (response instanceof Error) {
36833
- return reject(response);
36832
+ if (response2 instanceof Error) {
36833
+ return reject(response2);
36834
36834
  }
36835
36835
  try {
36836
- const parseResult = safeParse2(resultSchema, response.result);
36836
+ const parseResult = safeParse2(resultSchema, response2.result);
36837
36837
  if (!parseResult.success) {
36838
36838
  reject(parseResult.error);
36839
36839
  } else {
@@ -36851,10 +36851,10 @@ var Protocol = class {
36851
36851
  this._setupTimeout(messageId, timeout, options?.maxTotalTimeout, timeoutHandler, options?.resetTimeoutOnProgress ?? false);
36852
36852
  const relatedTaskId = relatedTask?.taskId;
36853
36853
  if (relatedTaskId) {
36854
- const responseResolver = (response) => {
36854
+ const responseResolver = (response2) => {
36855
36855
  const handler = this._responseHandlers.get(messageId);
36856
36856
  if (handler) {
36857
- handler(response);
36857
+ handler(response2);
36858
36858
  } else {
36859
36859
  this._onerror(new Error(`Response handler missing for side-channeled request ${messageId}`));
36860
36860
  }
@@ -41977,10 +41977,10 @@ function readFileBase64(p) {
41977
41977
  return readFileSync(p).toString("base64");
41978
41978
  }
41979
41979
  function readFileBase64Capped(p, maxBytes = maxAttachmentBytes()) {
41980
- const size = fileSize(p);
41981
- if (size > maxBytes) {
41980
+ const size2 = fileSize(p);
41981
+ if (size2 > maxBytes) {
41982
41982
  throw new Error(
41983
- `Attachment is ${size} bytes, exceeding the ${maxBytes}-byte fetch limit (APPLE_NOTES_MCP_MAX_ATTACHMENT_BYTES). Use save-attachment to export it to disk instead.`
41983
+ `Attachment is ${size2} bytes, exceeding the ${maxBytes}-byte fetch limit (APPLE_NOTES_MCP_MAX_ATTACHMENT_BYTES). Use save-attachment to export it to disk instead.`
41984
41984
  );
41985
41985
  }
41986
41986
  return readFileBase64(p);
@@ -47039,11 +47039,11 @@ function decodeInline(fields) {
47039
47039
  }
47040
47040
  if (font) {
47041
47041
  const name = stringOf(font, 1);
47042
- const size = fixed32Float(first(font, 2));
47043
- if (name || size !== void 0)
47042
+ const size2 = fixed32Float(first(font, 2));
47043
+ if (name || size2 !== void 0)
47044
47044
  inline.font = {
47045
47045
  ...name ? { name } : {},
47046
- ...size !== void 0 && Number.isFinite(size) ? { size } : {}
47046
+ ...size2 !== void 0 && Number.isFinite(size2) ? { size: size2 } : {}
47047
47047
  };
47048
47048
  }
47049
47049
  const attachment = sub3(fields, 12);
@@ -47236,8 +47236,8 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
47236
47236
  let bytes = 0;
47237
47237
  for (let i = start; i < total && blocks.length < limit; i++) {
47238
47238
  let block = doc.blocks[i];
47239
- let size = Buffer.byteLength(JSON.stringify(block));
47240
- if (bytes + size > maxBytes) {
47239
+ let size2 = Buffer.byteLength(JSON.stringify(block));
47240
+ if (bytes + size2 > maxBytes) {
47241
47241
  if (blocks.length) break;
47242
47242
  block = {
47243
47243
  ...block,
@@ -47245,10 +47245,10 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
47245
47245
  textOmitted: true,
47246
47246
  runs: block.runs.map((run) => ({ ...run, text: "" }))
47247
47247
  };
47248
- size = Buffer.byteLength(JSON.stringify(block));
47248
+ size2 = Buffer.byteLength(JSON.stringify(block));
47249
47249
  }
47250
47250
  blocks.push(block);
47251
- bytes += size;
47251
+ bytes += size2;
47252
47252
  }
47253
47253
  const next = start + blocks.length;
47254
47254
  return {
@@ -50065,7 +50065,7 @@ async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHe
50065
50065
  stdio: ["pipe", "pipe", "ignore"]
50066
50066
  });
50067
50067
  const chunks = [];
50068
- let size = 0;
50068
+ let size2 = 0;
50069
50069
  let failure2 = null;
50070
50070
  const stop = (error2) => {
50071
50071
  failure2 ??= error2;
@@ -50082,8 +50082,8 @@ async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHe
50082
50082
  signal?.removeEventListener("abort", onAbort);
50083
50083
  };
50084
50084
  child2.stdout?.on("data", (chunk) => {
50085
- size += chunk.length;
50086
- if (size > MAX_OUTPUT_BYTES2)
50085
+ size2 += chunk.length;
50086
+ if (size2 > MAX_OUTPUT_BYTES2)
50087
50087
  stop(new PublicHelperError("invalid_response", "The helper response is too large."));
50088
50088
  else chunks.push(chunk);
50089
50089
  });
@@ -51181,6 +51181,199 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS, pen
51181
51181
  };
51182
51182
  }
51183
51183
 
51184
+ // src/utils/structuredText.ts
51185
+ import { isDeepStrictEqual } from "node:util";
51186
+ var STRUCTURED_TEXT_PREFIX = "structuredContent: ";
51187
+ var SHOWN_ABOVE = "[shown in full above]";
51188
+ var MIN_ELIDED_CHARS = 64;
51189
+ var MAX_MIRROR_CHARS = 16384;
51190
+ var MAX_FIELD_CHARS = 1024;
51191
+ function textOf(content) {
51192
+ return content.map(
51193
+ (item) => item && typeof item === "object" && item.type === "text" ? String(item.text ?? "") : ""
51194
+ ).join("\n");
51195
+ }
51196
+ function elide(value, text2) {
51197
+ if (typeof value === "string") {
51198
+ return value.length >= MIN_ELIDED_CHARS && text2.includes(value) ? SHOWN_ABOVE : value;
51199
+ }
51200
+ if (Array.isArray(value)) return value.map((item) => elide(item, text2));
51201
+ if (value && typeof value === "object") {
51202
+ const out = {};
51203
+ for (const [key, item] of Object.entries(value)) {
51204
+ if (item !== void 0) out[key] = elide(item, text2);
51205
+ }
51206
+ return out;
51207
+ }
51208
+ return value;
51209
+ }
51210
+ function textIsTheJson(content, structured) {
51211
+ const plain = JSON.parse(JSON.stringify(structured));
51212
+ return content.some((item) => {
51213
+ const text2 = item?.text;
51214
+ if (item?.type !== "text" || typeof text2 !== "string") return false;
51215
+ const trimmed = text2.trim();
51216
+ if (!trimmed.startsWith("{")) return false;
51217
+ try {
51218
+ return isDeepStrictEqual(JSON.parse(trimmed), plain);
51219
+ } catch {
51220
+ return false;
51221
+ }
51222
+ });
51223
+ }
51224
+ function structuredTextLine(structured, text2) {
51225
+ const elided = elide(structured, text2);
51226
+ let json2 = JSON.stringify(elided);
51227
+ if (json2.length > MAX_MIRROR_CHARS) {
51228
+ const kept = {};
51229
+ const omitted = [];
51230
+ for (const [key, value] of Object.entries(elided)) {
51231
+ if (JSON.stringify(value).length > MAX_FIELD_CHARS) omitted.push(key);
51232
+ else kept[key] = value;
51233
+ }
51234
+ json2 = JSON.stringify({ ...kept, _omitted: omitted });
51235
+ }
51236
+ return STRUCTURED_TEXT_PREFIX + json2;
51237
+ }
51238
+ function withStructuredText(result) {
51239
+ const r = result;
51240
+ if (!r || typeof r !== "object") return result;
51241
+ const structured = r.structuredContent;
51242
+ if (!structured || typeof structured !== "object" || Array.isArray(structured)) return result;
51243
+ const content = Array.isArray(r.content) ? r.content : [];
51244
+ if (textIsTheJson(content, structured)) return result;
51245
+ const line = structuredTextLine(structured, textOf(content));
51246
+ return { ...r, content: [...content, { type: "text", text: line }] };
51247
+ }
51248
+ function installStructuredText(server2) {
51249
+ const target = server2;
51250
+ const register = target.registerTool;
51251
+ if (typeof register === "function") {
51252
+ target.registerTool = function(name, config2, cb) {
51253
+ const wrapped = typeof cb === "function" ? async (...args) => withStructuredText(await cb(...args)) : cb;
51254
+ return register.call(this, name, config2, wrapped);
51255
+ };
51256
+ }
51257
+ const createError = target.createToolError;
51258
+ if (typeof createError === "function") {
51259
+ target.createToolError = (message) => withStructuredText(createError.call(target, message));
51260
+ }
51261
+ }
51262
+
51263
+ // src/services/tableResponse.ts
51264
+ var DEFAULT_TABLE_RESPONSE_BYTES = 4 * 1024 * 1024;
51265
+ var MAX_TABLE_RESPONSE_BYTES = 8 * 1024 * 1024;
51266
+ var MAX_TABLES_PER_PAGE = 500;
51267
+ var MIN_TABLE_RESPONSE_BYTES = 1024;
51268
+ function validateTablePageOptions(options) {
51269
+ const {
51270
+ offset = 0,
51271
+ limit = MAX_TABLES_PER_PAGE,
51272
+ maxBytes = DEFAULT_TABLE_RESPONSE_BYTES
51273
+ } = options;
51274
+ if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > MAX_TABLES_PER_PAGE || !Number.isSafeInteger(maxBytes) || maxBytes < MIN_TABLE_RESPONSE_BYTES || maxBytes > MAX_TABLE_RESPONSE_BYTES) {
51275
+ throw new CodedError("Invalid table page offset, limit or maxBytes.", {
51276
+ code: "validation_error"
51277
+ });
51278
+ }
51279
+ return { offset, limit, maxBytes };
51280
+ }
51281
+ function response(id2, result, tables, options, stoppedAtSizeLimit) {
51282
+ const tableCount = result.tables.length;
51283
+ const next = options.offset + tables.length;
51284
+ const hasMore = next < tableCount;
51285
+ const complete = result.tableCellsComplete && tables.every((table) => table.complete);
51286
+ const markdown = tables.map(
51287
+ (table) => table.markdown ?? `[table ${table.index} could not be ${table.contentOmitted ? "returned" : "decoded"}: ${table.reason}]`
51288
+ ).join("\n\n");
51289
+ let summary = tableCount === 0 ? "This note has no native tables." : `${tables.length === tableCount ? tableCount : `${tables.length} of ${tableCount}`} table(s)${complete ? "" : " (some content could not be returned; see tables[].reason)"}:
51290
+
51291
+ ${markdown}`;
51292
+ if (hasMore)
51293
+ summary += `
51294
+
51295
+ More tables remain: call get-note-tables with offset ${next} and the same limit and maxBytes.`;
51296
+ return {
51297
+ content: [{ type: "text", text: summary }],
51298
+ structuredContent: {
51299
+ id: id2,
51300
+ tables,
51301
+ tableCount,
51302
+ tableCellsComplete: complete,
51303
+ markdown,
51304
+ page: {
51305
+ ...options,
51306
+ totalAvailable: tableCount,
51307
+ returned: tables.length,
51308
+ ...hasMore ? { nextOffset: next } : {},
51309
+ hasMore,
51310
+ stoppedAtSizeLimit
51311
+ }
51312
+ }
51313
+ };
51314
+ }
51315
+ var size = (value) => Buffer.byteLength(JSON.stringify(withStructuredText(value)));
51316
+ function tablePageResponse(id2, result, request = {}) {
51317
+ const options = validateTablePageOptions(request);
51318
+ const available = Math.max(0, Math.min(options.limit, result.tables.length - options.offset));
51319
+ const fits = (count) => {
51320
+ const candidate = result.tables.slice(options.offset, options.offset + count);
51321
+ const couldStopLater = count < available;
51322
+ const candidateBytes = Math.max(
51323
+ size(response(id2, result, candidate, options, false)),
51324
+ couldStopLater ? size(response(id2, result, candidate, options, true)) : 0
51325
+ );
51326
+ return candidateBytes <= options.maxBytes;
51327
+ };
51328
+ let accepted = 0;
51329
+ let trial = 1;
51330
+ while (trial <= available) {
51331
+ if (fits(trial)) {
51332
+ accepted = trial;
51333
+ if (trial === available) break;
51334
+ trial = Math.min(available, trial * 2);
51335
+ continue;
51336
+ }
51337
+ let low = accepted + 1;
51338
+ let high = trial - 1;
51339
+ while (low <= high) {
51340
+ const middle = Math.floor((low + high) / 2);
51341
+ if (fits(middle)) {
51342
+ accepted = middle;
51343
+ low = middle + 1;
51344
+ } else {
51345
+ high = middle - 1;
51346
+ }
51347
+ }
51348
+ break;
51349
+ }
51350
+ let tables = result.tables.slice(options.offset, options.offset + accepted);
51351
+ const stoppedAtSizeLimit = accepted < available;
51352
+ if (stoppedAtSizeLimit && accepted === 0) {
51353
+ const table = result.tables[options.offset];
51354
+ tables = [
51355
+ {
51356
+ index: table.index,
51357
+ id: table.id,
51358
+ ...table.attachmentId !== void 0 ? { attachmentId: table.attachmentId } : {},
51359
+ ...table.rowCount !== void 0 ? { rowCount: table.rowCount } : {},
51360
+ ...table.columnCount !== void 0 ? { columnCount: table.columnCount } : {},
51361
+ complete: false,
51362
+ contentOmitted: true,
51363
+ reason: "Table content exceeds this response size limit. Increase maxBytes or export the note to a file."
51364
+ }
51365
+ ];
51366
+ }
51367
+ const output = response(id2, result, tables, options, stoppedAtSizeLimit);
51368
+ if (size(output) > options.maxBytes) {
51369
+ throw new CodedError(
51370
+ "Table metadata is too large for this response limit; increase maxBytes.",
51371
+ { code: "validation_error" }
51372
+ );
51373
+ }
51374
+ return output;
51375
+ }
51376
+
51184
51377
  // src/utils/noteParagraphs.ts
51185
51378
  import { gunzipSync as gunzipSync9 } from "node:zlib";
51186
51379
  var ENVELOPE_CODE = {
@@ -51392,10 +51585,10 @@ function pageParagraphs(paragraphs, { offset = 0, limit = 500, maxBytes = 4 * 10
51392
51585
  const out = [];
51393
51586
  let bytes = 0;
51394
51587
  for (let i = start; i < list.length && out.length < limit; i++) {
51395
- const size = Buffer.byteLength(JSON.stringify(list[i]));
51396
- if (out.length && bytes + size > maxBytes) break;
51588
+ const size2 = Buffer.byteLength(JSON.stringify(list[i]));
51589
+ if (out.length && bytes + size2 > maxBytes) break;
51397
51590
  out.push(list[i]);
51398
- bytes += size;
51591
+ bytes += size2;
51399
51592
  }
51400
51593
  const next = start + out.length;
51401
51594
  return {
@@ -51931,10 +52124,10 @@ function listNoteLinks(options = {}) {
51931
52124
  const page = [];
51932
52125
  let bytes = 0;
51933
52126
  for (let i = start; i < links.length && page.length < limit; i++) {
51934
- const size = Buffer.byteLength(JSON.stringify(links[i]));
51935
- if (page.length && bytes + size > maxBytes) break;
52127
+ const size2 = Buffer.byteLength(JSON.stringify(links[i]));
52128
+ if (page.length && bytes + size2 > maxBytes) break;
51936
52129
  page.push(links[i]);
51937
- bytes += size;
52130
+ bytes += size2;
51938
52131
  }
51939
52132
  const next = start + page.length;
51940
52133
  return {
@@ -52545,8 +52738,8 @@ var DataUrlWriter = class {
52545
52738
  if (!this.fits(data.length)) return { error: "too-large" };
52546
52739
  return this.embed(`generated:${key}`, data, mime);
52547
52740
  }
52548
- fits(size) {
52549
- return size <= this.maxBytes && this.embeddedBytes + size <= this.totalBytes;
52741
+ fits(size2) {
52742
+ return size2 <= this.maxBytes && this.embeddedBytes + size2 <= this.totalBytes;
52550
52743
  }
52551
52744
  embed(key, data, mime) {
52552
52745
  const result = { url: `data:${mime};base64,${data.toString("base64")}`, mime };
@@ -55307,85 +55500,6 @@ function exportWithTemplate(request, deps, chosen, notes, skipped, { output, ass
55307
55500
  return { ...receipt, markdown };
55308
55501
  }
55309
55502
 
55310
- // src/utils/structuredText.ts
55311
- import { isDeepStrictEqual } from "node:util";
55312
- var STRUCTURED_TEXT_PREFIX = "structuredContent: ";
55313
- var SHOWN_ABOVE = "[shown in full above]";
55314
- var MIN_ELIDED_CHARS = 64;
55315
- var MAX_MIRROR_CHARS = 16384;
55316
- var MAX_FIELD_CHARS = 1024;
55317
- function textOf(content) {
55318
- return content.map(
55319
- (item) => item && typeof item === "object" && item.type === "text" ? String(item.text ?? "") : ""
55320
- ).join("\n");
55321
- }
55322
- function elide(value, text2) {
55323
- if (typeof value === "string") {
55324
- return value.length >= MIN_ELIDED_CHARS && text2.includes(value) ? SHOWN_ABOVE : value;
55325
- }
55326
- if (Array.isArray(value)) return value.map((item) => elide(item, text2));
55327
- if (value && typeof value === "object") {
55328
- const out = {};
55329
- for (const [key, item] of Object.entries(value)) {
55330
- if (item !== void 0) out[key] = elide(item, text2);
55331
- }
55332
- return out;
55333
- }
55334
- return value;
55335
- }
55336
- function textIsTheJson(content, structured) {
55337
- const plain = JSON.parse(JSON.stringify(structured));
55338
- return content.some((item) => {
55339
- const text2 = item?.text;
55340
- if (item?.type !== "text" || typeof text2 !== "string") return false;
55341
- const trimmed = text2.trim();
55342
- if (!trimmed.startsWith("{")) return false;
55343
- try {
55344
- return isDeepStrictEqual(JSON.parse(trimmed), plain);
55345
- } catch {
55346
- return false;
55347
- }
55348
- });
55349
- }
55350
- function structuredTextLine(structured, text2) {
55351
- const elided = elide(structured, text2);
55352
- let json2 = JSON.stringify(elided);
55353
- if (json2.length > MAX_MIRROR_CHARS) {
55354
- const kept = {};
55355
- const omitted = [];
55356
- for (const [key, value] of Object.entries(elided)) {
55357
- if (JSON.stringify(value).length > MAX_FIELD_CHARS) omitted.push(key);
55358
- else kept[key] = value;
55359
- }
55360
- json2 = JSON.stringify({ ...kept, _omitted: omitted });
55361
- }
55362
- return STRUCTURED_TEXT_PREFIX + json2;
55363
- }
55364
- function withStructuredText(result) {
55365
- const r = result;
55366
- if (!r || typeof r !== "object") return result;
55367
- const structured = r.structuredContent;
55368
- if (!structured || typeof structured !== "object" || Array.isArray(structured)) return result;
55369
- const content = Array.isArray(r.content) ? r.content : [];
55370
- if (textIsTheJson(content, structured)) return result;
55371
- const line = structuredTextLine(structured, textOf(content));
55372
- return { ...r, content: [...content, { type: "text", text: line }] };
55373
- }
55374
- function installStructuredText(server2) {
55375
- const target = server2;
55376
- const register = target.registerTool;
55377
- if (typeof register === "function") {
55378
- target.registerTool = function(name, config2, cb) {
55379
- const wrapped = typeof cb === "function" ? async (...args) => withStructuredText(await cb(...args)) : cb;
55380
- return register.call(this, name, config2, wrapped);
55381
- };
55382
- }
55383
- const createError = target.createToolError;
55384
- if (typeof createError === "function") {
55385
- target.createToolError = (message) => withStructuredText(createError.call(target, message));
55386
- }
55387
- }
55388
-
55389
55503
  // src/tools/directOperations.ts
55390
55504
  import { createHash as createHash4 } from "node:crypto";
55391
55505
  import {
@@ -55677,12 +55791,12 @@ function localAttachment(path10) {
55677
55791
  }
55678
55792
  }
55679
55793
  var MAX_ADD_ATTACHMENT_BYTES = 64 * 1024 * 1024;
55680
- function fileMatches(path10, size, expected) {
55794
+ function fileMatches(path10, size2, expected) {
55681
55795
  let descriptor;
55682
55796
  try {
55683
55797
  descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
55684
55798
  const stat = fstatSync7(descriptor);
55685
- if (!stat.isFile() || stat.size !== size) return false;
55799
+ if (!stat.isFile() || stat.size !== size2) return false;
55686
55800
  const hash = createHash4("sha256");
55687
55801
  const chunk = Buffer.allocUnsafe(1024 * 1024);
55688
55802
  let total = 0;
@@ -55690,10 +55804,10 @@ function fileMatches(path10, size, expected) {
55690
55804
  const read = readSync5(descriptor, chunk, 0, chunk.length, null);
55691
55805
  if (read === 0) break;
55692
55806
  total += read;
55693
- if (total > size) return false;
55807
+ if (total > size2) return false;
55694
55808
  hash.update(chunk.subarray(0, read));
55695
55809
  }
55696
- return total === size && hash.digest("hex") === expected;
55810
+ return total === size2 && hash.digest("hex") === expected;
55697
55811
  } catch {
55698
55812
  return false;
55699
55813
  } finally {
@@ -62842,10 +62956,10 @@ async function readEditorJson(req) {
62842
62956
  if (!/^application\/json(?:\s*;|$)/i.test(type))
62843
62957
  throw new EditorHttpError(415, "unsupported-media-type", "Send JSON (application/json).");
62844
62958
  const chunks = [];
62845
- let size = 0;
62959
+ let size2 = 0;
62846
62960
  for await (const chunk of req) {
62847
- size += chunk.length;
62848
- if (size > MAX_EDITOR_BODY_BYTES)
62961
+ size2 += chunk.length;
62962
+ if (size2 > MAX_EDITOR_BODY_BYTES)
62849
62963
  throw new EditorHttpError(413, "too-large", "Request body too large.");
62850
62964
  chunks.push(chunk);
62851
62965
  }
@@ -63196,12 +63310,12 @@ var textFingerprint = (normalized2) => createHash8("sha256").update(normalized2,
63196
63310
  function textSimilarity(a, b) {
63197
63311
  if (a === b) return 1;
63198
63312
  if (!a || !b) return 0;
63199
- const size = Math.min(a.length, b.length) < 3 ? 2 : 3;
63313
+ const size2 = Math.min(a.length, b.length) < 3 ? 2 : 3;
63200
63314
  const grams = (s) => {
63201
63315
  const out = /* @__PURE__ */ new Map();
63202
63316
  const padded = ` ${s} `;
63203
- for (let i = 0; i + size <= padded.length; i++) {
63204
- const g = padded.slice(i, i + size);
63317
+ for (let i = 0; i + size2 <= padded.length; i++) {
63318
+ const g = padded.slice(i, i + size2);
63205
63319
  out.set(g, (out.get(g) ?? 0) + 1);
63206
63320
  }
63207
63321
  return out;
@@ -65041,18 +65155,27 @@ registerTool(
65041
65155
  registerTool(
65042
65156
  "get-note-tables",
65043
65157
  {
65044
- description: "Use when: reading the native tables in one exact note as data or Markdown.\nReturns: every table in body order as GitHub-flavored Markdown (first row as header) plus JSON rows with stable row/column ids, and tableCellsComplete.\nDo not use when: you need the whole note (get-note-markdown / get-note-content) or native object ranges and checklist ids (get-native-objects).\nSafety: read-only; reads the NoteStore database and requires Full Disk Access. A cell that cannot be decoded is null in rows, listed in incompleteCells, and marked [undecoded cell] in Markdown; it is never guessed. Cell text only: links and styling inside cells are not rendered.",
65045
- inputSchema: { id: noteIdInput },
65158
+ description: "Use when: reading the native tables in one exact note as data or Markdown.\nReturns: one page of whole tables in body order as GitHub-flavored Markdown (first row as header) plus JSON rows with stable row/column ids, tableCellsComplete, and page info. Default limit 500 and maxBytes 4 MiB; while page.hasMore is true, call again with offset set to page.nextOffset. The byte budget includes the full serialized tool response, including repeated Markdown.\nDo not use when: you need the whole note (get-note-markdown / get-note-content) or native object ranges and checklist ids (get-native-objects).\nSafety: read-only; reads the NoteStore database and requires Full Disk Access. A cell that cannot be decoded is null in rows, listed in incompleteCells, and marked [undecoded cell] in Markdown; it is never guessed. A table larger than an empty page returns metadata only with complete: false and contentOmitted: true; increase maxBytes (max 8 MiB) or export the note to a file. Cell text only: links and styling inside cells are not rendered.",
65159
+ inputSchema: {
65160
+ id: noteIdInput,
65161
+ offset: external_exports.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional().describe("Zero-based position of the first table (default 0); use page.nextOffset"),
65162
+ limit: external_exports.number().int().min(1).max(MAX_TABLES_PER_PAGE).optional().describe(`Maximum tables to return (default and max ${MAX_TABLES_PER_PAGE})`),
65163
+ maxBytes: external_exports.number().int().min(MIN_TABLE_RESPONSE_BYTES).max(MAX_TABLE_RESPONSE_BYTES).optional().describe(
65164
+ `Maximum UTF-8 bytes of the serialized tool response (default ${DEFAULT_TABLE_RESPONSE_BYTES}, max ${MAX_TABLE_RESPONSE_BYTES})`
65165
+ )
65166
+ },
65046
65167
  outputSchema: {
65047
65168
  id: external_exports.string().optional(),
65048
65169
  tables: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
65049
65170
  tableCount: external_exports.number().optional(),
65050
65171
  tableCellsComplete: external_exports.boolean().optional(),
65051
- markdown: external_exports.string().optional()
65172
+ markdown: external_exports.string().optional(),
65173
+ page: external_exports.record(external_exports.unknown()).optional()
65052
65174
  },
65053
65175
  annotations: { readOnlyHint: true }
65054
65176
  },
65055
- withErrorHandling(({ id: id2 }) => {
65177
+ withErrorHandling(({ id: id2, offset, limit, maxBytes }) => {
65178
+ const options = validateTablePageOptions({ offset, limit, maxBytes });
65056
65179
  const { metadata, message } = getNoteMetadata(id2);
65057
65180
  if (!metadata) return errorResponse(message || `Failed to read note "${id2}"`);
65058
65181
  if (metadata.passwordProtected) {
@@ -65061,17 +65184,7 @@ registerTool(
65061
65184
  );
65062
65185
  }
65063
65186
  const result = notesManager.getNoteTablesById(id2);
65064
- const count = result.tables.length;
65065
- const summary = count === 0 ? "This note has no native tables." : `${count} table(s)${result.tableCellsComplete ? "" : " (some content could not be decoded; see tables[].reason)"}:
65066
-
65067
- ${result.markdown}`;
65068
- return successResponse(summary, {
65069
- id: id2,
65070
- tables: result.tables,
65071
- tableCount: count,
65072
- tableCellsComplete: result.tableCellsComplete,
65073
- markdown: result.markdown
65074
- });
65187
+ return tablePageResponse(id2, result, options);
65075
65188
  }, "Error reading note tables")
65076
65189
  );
65077
65190
  registerTool(
@@ -65850,7 +65963,7 @@ registerTool(
65850
65963
  readLinks: (noteId3) => readRichNote(noteId3).links,
65851
65964
  append: (request) => {
65852
65965
  let route = "applescript";
65853
- const response = guardedAppend(
65966
+ const response2 = guardedAppend(
65854
65967
  {
65855
65968
  ...request,
65856
65969
  format: "html",
@@ -65858,11 +65971,11 @@ registerTool(
65858
65971
  },
65859
65972
  (r) => route = r
65860
65973
  );
65861
- if (response.isError) {
65862
- const envelope = response.structuredContent;
65863
- throw envelope?.code ? new CodedError(response.content[0].text, envelope) : new Error(response.content[0].text);
65974
+ if (response2.isError) {
65975
+ const envelope = response2.structuredContent;
65976
+ throw envelope?.code ? new CodedError(response2.content[0].text, envelope) : new Error(response2.content[0].text);
65864
65977
  }
65865
- return { route, contentHash: String(response.structuredContent?.contentHash ?? "") };
65978
+ return { route, contentHash: String(response2.structuredContent?.contentHash ?? "") };
65866
65979
  }
65867
65980
  }
65868
65981
  );
@@ -67003,7 +67116,7 @@ registerTool(
67003
67116
  `Too large to return whole, so oversized inline images were replaced (strippedImages) or the body was left out (contentOmitted): ${degraded.map((n) => n.id).join(", ")}. Read these with get-note-content, and their files with list-attachments and save-attachment.`
67004
67117
  );
67005
67118
  }
67006
- const response = {
67119
+ const response2 = {
67007
67120
  content: [
67008
67121
  {
67009
67122
  type: "text",
@@ -67018,13 +67131,13 @@ Full JSON export:`
67018
67131
  ],
67019
67132
  structuredContent: { ...exportData }
67020
67133
  };
67021
- const responseBytes = Buffer.byteLength(JSON.stringify(response));
67134
+ const responseBytes = Buffer.byteLength(JSON.stringify(response2));
67022
67135
  if (responseBytes > maxResponseBytes) {
67023
67136
  return errorResponse(
67024
67137
  `Error exporting notes: this page is ${formatMegabytes(responseBytes)}, over the ${formatMegabytes(maxResponseBytes)} response limit, so it was not sent. Call export-notes-json again with offset ${page.offset} and a smaller limit (for example ${Math.max(1, Math.floor(page.returned / 2))}), or raise APPLE_NOTES_MCP_EXPORT_MAX_BYTES if your MCP client accepts larger messages.`
67025
67138
  );
67026
67139
  }
67027
- return response;
67140
+ return response2;
67028
67141
  }, "Error exporting notes")
67029
67142
  );
67030
67143
  registerTool(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.15.5",
3
+ "version": "2.15.6",
4
4
  "description": "MCP server for Apple Notes - read, search, create, edit, organize, and export notes on macOS via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",