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.
- package/README.md +21 -3
- package/build/index.js +268 -155
- 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
|
|
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
|
-
`
|
|
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(
|
|
27889
|
-
return this.min(
|
|
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
|
|
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:
|
|
36562
|
+
message: response2,
|
|
36563
36563
|
timestamp: Date.now()
|
|
36564
36564
|
}, capturedTransport?.sessionId);
|
|
36565
36565
|
} else {
|
|
36566
|
-
await capturedTransport?.send(
|
|
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(
|
|
36620
|
-
const messageId = Number(
|
|
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(
|
|
36625
|
-
resolver(
|
|
36624
|
+
if (isJSONRPCResultResponse(response2)) {
|
|
36625
|
+
resolver(response2);
|
|
36626
36626
|
} else {
|
|
36627
|
-
const error2 = new McpError(
|
|
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(
|
|
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(
|
|
36641
|
-
const 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(
|
|
36654
|
-
handler(
|
|
36653
|
+
if (isJSONRPCResultResponse(response2)) {
|
|
36654
|
+
handler(response2);
|
|
36655
36655
|
} else {
|
|
36656
|
-
const error2 = McpError.fromError(
|
|
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, (
|
|
36828
|
+
this._responseHandlers.set(messageId, (response2) => {
|
|
36829
36829
|
if (options?.signal?.aborted) {
|
|
36830
36830
|
return;
|
|
36831
36831
|
}
|
|
36832
|
-
if (
|
|
36833
|
-
return reject(
|
|
36832
|
+
if (response2 instanceof Error) {
|
|
36833
|
+
return reject(response2);
|
|
36834
36834
|
}
|
|
36835
36835
|
try {
|
|
36836
|
-
const parseResult = safeParse2(resultSchema,
|
|
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 = (
|
|
36854
|
+
const responseResolver = (response2) => {
|
|
36855
36855
|
const handler = this._responseHandlers.get(messageId);
|
|
36856
36856
|
if (handler) {
|
|
36857
|
-
handler(
|
|
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
|
|
41981
|
-
if (
|
|
41980
|
+
const size2 = fileSize(p);
|
|
41981
|
+
if (size2 > maxBytes) {
|
|
41982
41982
|
throw new Error(
|
|
41983
|
-
`Attachment is ${
|
|
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
|
|
47043
|
-
if (name ||
|
|
47042
|
+
const size2 = fixed32Float(first(font, 2));
|
|
47043
|
+
if (name || size2 !== void 0)
|
|
47044
47044
|
inline.font = {
|
|
47045
47045
|
...name ? { name } : {},
|
|
47046
|
-
...
|
|
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
|
|
47240
|
-
if (bytes +
|
|
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
|
-
|
|
47248
|
+
size2 = Buffer.byteLength(JSON.stringify(block));
|
|
47249
47249
|
}
|
|
47250
47250
|
blocks.push(block);
|
|
47251
|
-
bytes +=
|
|
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
|
|
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
|
-
|
|
50086
|
-
if (
|
|
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
|
|
51396
|
-
if (out.length && bytes +
|
|
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 +=
|
|
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
|
|
51935
|
-
if (page.length && bytes +
|
|
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 +=
|
|
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(
|
|
52549
|
-
return
|
|
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,
|
|
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 !==
|
|
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 >
|
|
55807
|
+
if (total > size2) return false;
|
|
55694
55808
|
hash.update(chunk.subarray(0, read));
|
|
55695
55809
|
}
|
|
55696
|
-
return total ===
|
|
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
|
|
62959
|
+
let size2 = 0;
|
|
62846
62960
|
for await (const chunk of req) {
|
|
62847
|
-
|
|
62848
|
-
if (
|
|
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
|
|
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 +
|
|
63204
|
-
const g = padded.slice(i, i +
|
|
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:
|
|
65045
|
-
inputSchema: {
|
|
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
|
-
|
|
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
|
|
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 (
|
|
65862
|
-
const envelope =
|
|
65863
|
-
throw envelope?.code ? new CodedError(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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.
|
|
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",
|