apple-notes-mcp 2.9.27 → 2.9.30
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 +8 -7
- package/build/index.js +61 -107
- package/docs/markdown-templates.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1752,11 +1752,12 @@ single note.
|
|
|
1752
1752
|
| `assetsDir` | string | No | Absolute directory for attachment copies. Existing files are never replaced; a taken name gets `-2`, `-3`, ... |
|
|
1753
1753
|
| `wrap` | number | No | Hard-wrap prose at this many columns. Code, tables and headings are never wrapped |
|
|
1754
1754
|
| `template` | string | No | Render through a template: `standard-markdown`, `obsidian`, or a saved template's name. Exclusive with `templateFile` |
|
|
1755
|
-
| `templateFile` | string | No | Render through the JSON template in this `.json` file (
|
|
1755
|
+
| `templateFile` | string | No | Render through the JSON template in this `.json` file (at most 256 KiB), read under the same rules as `create-note`'s [`contentPath`](#create-note): hidden paths (such as `~/.docker` or a project `.env`) and `~/Library` other than iCloud Drive and `~/Library/CloudStorage` are refused, checked again after resolving symbolic links and letter case, unless the server sets `APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1`. Errors name the file, never its contents |
|
|
1756
1756
|
|
|
1757
1757
|
Both paths follow the `save-attachment` rules (absolute, under the home
|
|
1758
1758
|
directory, a temp directory, or `/Volumes`, no symlink escapes) and may not
|
|
1759
|
-
point inside the Notes library container.
|
|
1759
|
+
point inside the Notes library container. `templateFile` is a read, so it
|
|
1760
|
+
also follows `contentPath`'s private-location rule. With `outputPath`, asset links are
|
|
1760
1761
|
relative to the document's directory; without it they are absolute paths.
|
|
1761
1762
|
|
|
1762
1763
|
**Returns:** without `outputPath`, the Markdown itself (refused with
|
|
@@ -1863,7 +1864,7 @@ or `templateFile`.
|
|
|
1863
1864
|
|-----------|------|----------|-------------|
|
|
1864
1865
|
| `name` | string | One of three | A saved or built-in template |
|
|
1865
1866
|
| `template` | object or string | One of three | The template as a JSON object or JSON text |
|
|
1866
|
-
| `templateFile` | string | One of three | Absolute path of a JSON template file (home, a temp directory, or `/Volumes`; at most 256 KiB) |
|
|
1867
|
+
| `templateFile` | string | One of three | Absolute path of a JSON template file (home, a temp directory, or `/Volumes`; at most 256 KiB), read under the same rules as `create-note`'s [`contentPath`](#create-note): hidden paths (such as `~/.docker` or a project `.env`) and `~/Library` other than iCloud Drive and `~/Library/CloudStorage` are refused, checked again after resolving symbolic links and letter case, unless the server sets `APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1` |
|
|
1867
1868
|
|
|
1868
1869
|
**Returns:** `valid`, and `errors` as `{path, message}` pairs such as
|
|
1869
1870
|
`$.rules["inline.bold"].after: is required when mode is "wrap"`. An invalid
|
|
@@ -1880,7 +1881,7 @@ can use it by `template` name.
|
|
|
1880
1881
|
|-----------|------|----------|-------------|
|
|
1881
1882
|
| `name` | string | Yes | Lowercase `a-z`, `0-9`, `-` and `_`, 1-64 characters, starting with a letter or digit. Built-in names are reserved |
|
|
1882
1883
|
| `template` | object or string | One of `template`/`templateFile` | The template |
|
|
1883
|
-
| `templateFile` | string | One of `template`/`templateFile` | A JSON template file |
|
|
1884
|
+
| `templateFile` | string | One of `template`/`templateFile` | A JSON template file, under the same rules as `validate-markdown-template`'s |
|
|
1884
1885
|
| `force` | boolean | No | Replace an existing template of this name (default `false`) |
|
|
1885
1886
|
|
|
1886
1887
|
Saving is create-only: an existing name is refused with `[template-exists]`
|
|
@@ -2264,7 +2265,7 @@ Analyzes one local SVG file and reports whether it can be represented as editabl
|
|
|
2264
2265
|
|
|
2265
2266
|
| Parameter | Type | Required | Description |
|
|
2266
2267
|
|-----------|------|----------|-------------|
|
|
2267
|
-
| `path` | string | Yes | Absolute path of one `.svg` file: a regular file, not a symbolic link, at most 1 MiB of UTF-8, in home, temp, or `/Volumes` |
|
|
2268
|
+
| `path` | string | Yes | Absolute path of one `.svg` file: a regular file, not a symbolic link, at most 1 MiB of UTF-8, in home, temp, or `/Volumes`, read under the same rules as `create-note`'s [`contentPath`](#create-note): hidden paths (such as `~/.docker` or a project `.env`) and `~/Library` other than iCloud Drive and `~/Library/CloudStorage` are refused, checked again after resolving symbolic links and letter case, unless the server sets `APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1`. A file whose first element is not `<svg>` is refused without quoting any of it |
|
|
2268
2269
|
| `includeDrawing` | boolean | No | Also return the normalized drawing. Off by default because it can be large |
|
|
2269
2270
|
|
|
2270
2271
|
**Returns:** `classification`, `importable`, `defaultWriteAllowed`, `requiredLosses`, `issues` (each with a `code`, the `loss` it causes or `null`, an element `location` such as `svg/g[2]/path[1]#id`, and a message), `viewport`, `counts` (elements, references, path segments, output strokes and points, and the `geometryWork`, `dashWork`, and `scanWork` budgets as `{used, max}`), `source` (`sha256`, `bytes`), `drawingBytes`, and `analysisDigest`.
|
|
@@ -2408,7 +2409,7 @@ listed under the tool itself:
|
|
|
2408
2409
|
|-----------|------|----------|-------------|
|
|
2409
2410
|
| `id` | string | Yes | Exact note ID: x-coredata id, Notes UUID, or numeric key (see [Identifier forms](#identifier-forms)) |
|
|
2410
2411
|
| `expectedContentHash` | string | Yes | `contentHash` (`sha256:…`) from a fresh read of the note |
|
|
2411
|
-
| `scopeText` | string | Yes | Distinctive phrase already in the note, 12–500 characters, that Notes search can find. Prefer plain words without punctuation, hashtags, or paths |
|
|
2412
|
+
| `scopeText` | string | Yes | Distinctive phrase already in the note below its title line, 12–500 characters, that Notes search can find. Prefer plain words without punctuation, hashtags, or paths |
|
|
2412
2413
|
|
|
2413
2414
|
When the Shortcut refuses a request (its Find Notes step did not return
|
|
2414
2415
|
exactly one note with that exact title and `scopeText`) or stops with its own
|
|
@@ -2780,7 +2781,7 @@ All configuration is optional — the server works out of the box. Override beha
|
|
|
2780
2781
|
| `APPLE_NOTES_MCP_BACKGROUND_SHORTCUT` | `Apple Notes MCP - Background Operations v5` | Name or UUID of the installed Background Operations bridge to run. `setup` still installs and checks the default name. |
|
|
2781
2782
|
| `APPLE_NOTES_MCP_MARKDOWN_SHORTCUT` | `Apple Notes MCP - Create Markdown Note` | Name or UUID of the installed Create Markdown Note bridge to run. `setup` still installs and checks the default name. |
|
|
2782
2783
|
| `APPLE_NOTES_MCP_PASTEBOARD_NAME` | unset | Testing only: makes [`add-attachment-from-pasteboard`](#add-attachment-from-pasteboard) read a private named pasteboard instead of the general clipboard. |
|
|
2783
|
-
| `APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS` | unset | Set to `1` to let `create-note` read a [`contentPath`](#create-note),
|
|
2784
|
+
| `APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS` | unset | Set to `1` to let `create-note` read a [`contentPath`](#create-note), [`add-attachment`](#add-attachment) and [`create-note-with-attachment`](#create-note-with-attachment) attach a `path`, [`analyze-svg`](#analyze-svg) read a `path`, and the Markdown template tools read a `templateFile`, inside a hidden directory or file (such as `~/.config`) or `~/Library` outside iCloud Drive and `~/Library/CloudStorage`. Off by default because those places hold keys, tokens and app data. |
|
|
2784
2785
|
| `DEBUG` / `VERBOSE` | unset | Set either to enable verbose diagnostic logging to stderr. |
|
|
2785
2786
|
|
|
2786
2787
|
### Configuration file (when the host strips `env`)
|
package/build/index.js
CHANGED
|
@@ -417,11 +417,11 @@ var require_codegen = __commonJS({
|
|
|
417
417
|
const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
|
|
418
418
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
419
419
|
}
|
|
420
|
-
optimizeNames(names,
|
|
420
|
+
optimizeNames(names, constants9) {
|
|
421
421
|
if (!names[this.name.str])
|
|
422
422
|
return;
|
|
423
423
|
if (this.rhs)
|
|
424
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
424
|
+
this.rhs = optimizeExpr(this.rhs, names, constants9);
|
|
425
425
|
return this;
|
|
426
426
|
}
|
|
427
427
|
get names() {
|
|
@@ -438,10 +438,10 @@ var require_codegen = __commonJS({
|
|
|
438
438
|
render({ _n }) {
|
|
439
439
|
return `${this.lhs} = ${this.rhs};` + _n;
|
|
440
440
|
}
|
|
441
|
-
optimizeNames(names,
|
|
441
|
+
optimizeNames(names, constants9) {
|
|
442
442
|
if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
|
|
443
443
|
return;
|
|
444
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
444
|
+
this.rhs = optimizeExpr(this.rhs, names, constants9);
|
|
445
445
|
return this;
|
|
446
446
|
}
|
|
447
447
|
get names() {
|
|
@@ -502,8 +502,8 @@ var require_codegen = __commonJS({
|
|
|
502
502
|
optimizeNodes() {
|
|
503
503
|
return `${this.code}` ? this : void 0;
|
|
504
504
|
}
|
|
505
|
-
optimizeNames(names,
|
|
506
|
-
this.code = optimizeExpr(this.code, names,
|
|
505
|
+
optimizeNames(names, constants9) {
|
|
506
|
+
this.code = optimizeExpr(this.code, names, constants9);
|
|
507
507
|
return this;
|
|
508
508
|
}
|
|
509
509
|
get names() {
|
|
@@ -532,12 +532,12 @@ var require_codegen = __commonJS({
|
|
|
532
532
|
}
|
|
533
533
|
return nodes.length > 0 ? this : void 0;
|
|
534
534
|
}
|
|
535
|
-
optimizeNames(names,
|
|
535
|
+
optimizeNames(names, constants9) {
|
|
536
536
|
const { nodes } = this;
|
|
537
537
|
let i = nodes.length;
|
|
538
538
|
while (i--) {
|
|
539
539
|
const n = nodes[i];
|
|
540
|
-
if (n.optimizeNames(names,
|
|
540
|
+
if (n.optimizeNames(names, constants9))
|
|
541
541
|
continue;
|
|
542
542
|
subtractNames(names, n.names);
|
|
543
543
|
nodes.splice(i, 1);
|
|
@@ -590,12 +590,12 @@ var require_codegen = __commonJS({
|
|
|
590
590
|
return void 0;
|
|
591
591
|
return this;
|
|
592
592
|
}
|
|
593
|
-
optimizeNames(names,
|
|
593
|
+
optimizeNames(names, constants9) {
|
|
594
594
|
var _a;
|
|
595
|
-
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
596
|
-
if (!(super.optimizeNames(names,
|
|
595
|
+
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants9);
|
|
596
|
+
if (!(super.optimizeNames(names, constants9) || this.else))
|
|
597
597
|
return;
|
|
598
|
-
this.condition = optimizeExpr(this.condition, names,
|
|
598
|
+
this.condition = optimizeExpr(this.condition, names, constants9);
|
|
599
599
|
return this;
|
|
600
600
|
}
|
|
601
601
|
get names() {
|
|
@@ -618,10 +618,10 @@ var require_codegen = __commonJS({
|
|
|
618
618
|
render(opts) {
|
|
619
619
|
return `for(${this.iteration})` + super.render(opts);
|
|
620
620
|
}
|
|
621
|
-
optimizeNames(names,
|
|
622
|
-
if (!super.optimizeNames(names,
|
|
621
|
+
optimizeNames(names, constants9) {
|
|
622
|
+
if (!super.optimizeNames(names, constants9))
|
|
623
623
|
return;
|
|
624
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
624
|
+
this.iteration = optimizeExpr(this.iteration, names, constants9);
|
|
625
625
|
return this;
|
|
626
626
|
}
|
|
627
627
|
get names() {
|
|
@@ -657,10 +657,10 @@ var require_codegen = __commonJS({
|
|
|
657
657
|
render(opts) {
|
|
658
658
|
return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
|
|
659
659
|
}
|
|
660
|
-
optimizeNames(names,
|
|
661
|
-
if (!super.optimizeNames(names,
|
|
660
|
+
optimizeNames(names, constants9) {
|
|
661
|
+
if (!super.optimizeNames(names, constants9))
|
|
662
662
|
return;
|
|
663
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
663
|
+
this.iterable = optimizeExpr(this.iterable, names, constants9);
|
|
664
664
|
return this;
|
|
665
665
|
}
|
|
666
666
|
get names() {
|
|
@@ -702,11 +702,11 @@ var require_codegen = __commonJS({
|
|
|
702
702
|
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
|
|
703
703
|
return this;
|
|
704
704
|
}
|
|
705
|
-
optimizeNames(names,
|
|
705
|
+
optimizeNames(names, constants9) {
|
|
706
706
|
var _a, _b;
|
|
707
|
-
super.optimizeNames(names,
|
|
708
|
-
(_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
709
|
-
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names,
|
|
707
|
+
super.optimizeNames(names, constants9);
|
|
708
|
+
(_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants9);
|
|
709
|
+
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants9);
|
|
710
710
|
return this;
|
|
711
711
|
}
|
|
712
712
|
get names() {
|
|
@@ -1007,7 +1007,7 @@ var require_codegen = __commonJS({
|
|
|
1007
1007
|
function addExprNames(names, from) {
|
|
1008
1008
|
return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
|
|
1009
1009
|
}
|
|
1010
|
-
function optimizeExpr(expr, names,
|
|
1010
|
+
function optimizeExpr(expr, names, constants9) {
|
|
1011
1011
|
if (expr instanceof code_1.Name)
|
|
1012
1012
|
return replaceName(expr);
|
|
1013
1013
|
if (!canOptimize(expr))
|
|
@@ -1022,14 +1022,14 @@ var require_codegen = __commonJS({
|
|
|
1022
1022
|
return items;
|
|
1023
1023
|
}, []));
|
|
1024
1024
|
function replaceName(n) {
|
|
1025
|
-
const c =
|
|
1025
|
+
const c = constants9[n.str];
|
|
1026
1026
|
if (c === void 0 || names[n.str] !== 1)
|
|
1027
1027
|
return n;
|
|
1028
1028
|
delete names[n.str];
|
|
1029
1029
|
return c;
|
|
1030
1030
|
}
|
|
1031
1031
|
function canOptimize(e) {
|
|
1032
|
-
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 &&
|
|
1032
|
+
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants9[c.str] !== void 0);
|
|
1033
1033
|
}
|
|
1034
1034
|
}
|
|
1035
1035
|
function subtractNames(names, from) {
|
|
@@ -7422,7 +7422,7 @@ var require_DOMException = __commonJS({
|
|
|
7422
7422
|
"INVALID_NODE_TYPE_ERR (24): the supplied node is invalid or has an invalid ancestor for this operation",
|
|
7423
7423
|
"DATA_CLONE_ERR (25): the object can not be cloned."
|
|
7424
7424
|
];
|
|
7425
|
-
var
|
|
7425
|
+
var constants9 = {
|
|
7426
7426
|
INDEX_SIZE_ERR,
|
|
7427
7427
|
DOMSTRING_SIZE_ERR: 2,
|
|
7428
7428
|
// historical
|
|
@@ -7461,8 +7461,8 @@ var require_DOMException = __commonJS({
|
|
|
7461
7461
|
this.name = names[code];
|
|
7462
7462
|
}
|
|
7463
7463
|
DOMException.prototype.__proto__ = Error.prototype;
|
|
7464
|
-
for (c in
|
|
7465
|
-
v = { value:
|
|
7464
|
+
for (c in constants9) {
|
|
7465
|
+
v = { value: constants9[c] };
|
|
7466
7466
|
Object.defineProperty(DOMException, c, v);
|
|
7467
7467
|
Object.defineProperty(DOMException.prototype, c, v);
|
|
7468
7468
|
}
|
|
@@ -39857,6 +39857,10 @@ var CodedError = class extends Error {
|
|
|
39857
39857
|
};
|
|
39858
39858
|
var RULES = [
|
|
39859
39859
|
{ code: "timeout_indeterminate", pattern: /timed out|\bETIMEDOUT\b/i },
|
|
39860
|
+
// A scope guard is checked before the write runs, so nothing was written:
|
|
39861
|
+
// the note is not where it was reviewed. Its advice to read the note before
|
|
39862
|
+
// retrying must not make it look like an unverified write.
|
|
39863
|
+
{ code: "revision_conflict", pattern: /\bScope guard failed:/ },
|
|
39860
39864
|
{
|
|
39861
39865
|
code: "verification_failed",
|
|
39862
39866
|
pattern: /\buncertain\b|may have (?:been|succeeded)|\baccepted (?:the|an) \w+, but|\bnot verified\b|Do not retry automatically|before any retry|read (?:the )?(?:exact )?(?:note|ID)\b[^.]*before retr/i
|
|
@@ -39885,7 +39889,7 @@ var RULES = [
|
|
|
39885
39889
|
},
|
|
39886
39890
|
{
|
|
39887
39891
|
code: "validation_error",
|
|
39888
|
-
pattern: /\bis required\b|required\b|\bInvalid\b|\bmust\b|\bProvide\b|No (?:note IDs|reviewed notes) provided|response limit|Refusing to write|cannot be resolved|at most \d|between \d+ and \d+|supports the (?:end|default)|Use a distinctive|Use create-table|too large|equal cell counts|Folder path is empty/i
|
|
39892
|
+
pattern: /\bis required\b|required\b|\bInvalid\b|\bmust\b|\bProvide\b|No (?:note IDs|reviewed notes) provided|response limit|Refusing to (?:read|write)|cannot be resolved|at most \d|between \d+ and \d+|supports the (?:end|default)|Use a distinctive|Use create-table|too large|equal cell counts|Folder path is empty/i
|
|
39889
39893
|
}
|
|
39890
39894
|
];
|
|
39891
39895
|
var NOTHING_WRITTEN = /nothing (?:was )?(?:changed|created|written)|no replacement started|No content was (?:replaced|appended)|Nothing was created/i;
|
|
@@ -48719,6 +48723,12 @@ function describeTransportFailure(error2) {
|
|
|
48719
48723
|
if (detail?.refused) return `${named} ${detail.message}`;
|
|
48720
48724
|
return detail?.code === "ETIMEDOUT" ? `Shortcuts timed out waiting for ${named}. ${shortcutConsentHint(detail.shortcut)}` : `${named} failed: ${String(detail?.stderr || detail?.message || "no output").trim().slice(0, 400)}`;
|
|
48721
48725
|
}
|
|
48726
|
+
function textBelowTitle(text2, title) {
|
|
48727
|
+
const lineEnd = text2.indexOf("\n");
|
|
48728
|
+
const firstLine = lineEnd < 0 ? text2 : text2.slice(0, lineEnd);
|
|
48729
|
+
if (firstLine.trim() !== title.trim()) return text2;
|
|
48730
|
+
return lineEnd < 0 ? "" : text2.slice(lineEnd + 1);
|
|
48731
|
+
}
|
|
48722
48732
|
function mutateBackground(request, operation, data, verify, deps) {
|
|
48723
48733
|
if (request.scopeText.length < 12 || request.scopeText.length > 500 || /[\r\n\0]/u.test(request.scopeText))
|
|
48724
48734
|
throw new Error("Use a distinctive existing single-line scope of 12\u2013500 characters");
|
|
@@ -48727,6 +48737,11 @@ function mutateBackground(request, operation, data, verify, deps) {
|
|
|
48727
48737
|
throw new Error("Note revision changed; read it again");
|
|
48728
48738
|
if (!before.rich.text.includes(request.scopeText))
|
|
48729
48739
|
throw new Error("Scope is absent from exact note");
|
|
48740
|
+
if (!textBelowTitle(before.rich.text, before.title).includes(request.scopeText))
|
|
48741
|
+
throw new CodedError(
|
|
48742
|
+
"scopeText appears only in the note's title line. The bridge's Find Notes step matches it against the body below the title, so it would refuse; pass a phrase from below the title. Nothing changed",
|
|
48743
|
+
{ code: "validation_error", committed: false, indeterminate: false }
|
|
48744
|
+
);
|
|
48730
48745
|
const candidates = deps.candidates(before.title, request.scopeText);
|
|
48731
48746
|
if (candidates.length !== 1 || candidates[0] !== request.id)
|
|
48732
48747
|
throw new Error("Ambiguous note selection; nothing changed");
|
|
@@ -53141,33 +53156,9 @@ function templateAssetsDir(directory, outputDir, values) {
|
|
|
53141
53156
|
return dir;
|
|
53142
53157
|
}
|
|
53143
53158
|
function readTemplateFile(path10) {
|
|
53144
|
-
|
|
53145
|
-
|
|
53146
|
-
|
|
53147
|
-
let fd;
|
|
53148
|
-
try {
|
|
53149
|
-
fd = openRegular(abs);
|
|
53150
|
-
} catch (error2) {
|
|
53151
|
-
const code = error2.code;
|
|
53152
|
-
throw new Error(
|
|
53153
|
-
code === "ENOENT" ? `Template file not found: ${abs}` : code === "ELOOP" ? `Refusing to read the symbolic link ${abs}` : `Template file is not a readable regular file: ${abs}`
|
|
53154
|
-
);
|
|
53155
|
-
}
|
|
53156
|
-
try {
|
|
53157
|
-
const size = fstatSync5(fd).size;
|
|
53158
|
-
if (size > MAX_TEMPLATE_BYTES)
|
|
53159
|
-
throw new Error(`Template file is ${size} bytes; the limit is ${MAX_TEMPLATE_BYTES}`);
|
|
53160
|
-
const data = Buffer.alloc(size);
|
|
53161
|
-
let read = 0;
|
|
53162
|
-
while (read < size) {
|
|
53163
|
-
const n = readSync4(fd, data, read, size - read, read);
|
|
53164
|
-
if (n <= 0) break;
|
|
53165
|
-
read += n;
|
|
53166
|
-
}
|
|
53167
|
-
return data.subarray(0, read).toString("utf8");
|
|
53168
|
-
} finally {
|
|
53169
|
-
closeSync5(fd);
|
|
53170
|
-
}
|
|
53159
|
+
if (extname4(path10).toLowerCase() !== ".json")
|
|
53160
|
+
throw new Error(`Template file must have a .json extension: ${path10}`);
|
|
53161
|
+
return readAllowedFile(path10, MAX_TEMPLATE_BYTES, { label: "Template file" }).toString("utf8");
|
|
53171
53162
|
}
|
|
53172
53163
|
|
|
53173
53164
|
// src/services/notesExport.ts
|
|
@@ -54759,7 +54750,7 @@ function failure(action, error2) {
|
|
|
54759
54750
|
var nameInput = external_exports.string().min(1).max(64).describe("Template name: a built-in (standard-markdown, obsidian) or a saved template's name");
|
|
54760
54751
|
var templateInput = external_exports.union([external_exports.record(external_exports.unknown()), external_exports.string().max(MAX_TEMPLATE_BYTES)]).optional().describe("The template itself, as a JSON object or JSON text (schemaVersion 1)");
|
|
54761
54752
|
var templateFileInput = external_exports.string().min(1).max(4096).optional().describe(
|
|
54762
|
-
"Absolute path of a JSON template file ending in .json (home, a temp dir, or /Volumes; symlinks refused; at most 256 KiB)"
|
|
54753
|
+
"Absolute path of a JSON template file ending in .json (home, a temp dir, or /Volumes; hidden paths and ~/Library outside iCloud Drive and CloudStorage refused unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; symlinks refused; at most 256 KiB)"
|
|
54763
54754
|
);
|
|
54764
54755
|
function sourceText(args) {
|
|
54765
54756
|
if (args.template === void 0 === (args.templateFile === void 0))
|
|
@@ -54829,7 +54820,7 @@ function registerMarkdownTemplates(server2, store = () => new TemplateStore()) {
|
|
|
54829
54820
|
server2.registerTool(
|
|
54830
54821
|
"validate-markdown-template",
|
|
54831
54822
|
{
|
|
54832
|
-
description: 'Use when: checking a template before saving it or exporting with it.\nReturns: valid true, or valid false with every problem as {path, message}, where path is a JSON path such as $.rules["inline.bold"].after.\nDo not use when: you want to store it (save-markdown-template validates too).\nSafety: read-only. Pass exactly one of name (a saved or built-in template), template (JSON object or text) or templateFile.',
|
|
54823
|
+
description: 'Use when: checking a template before saving it or exporting with it.\nReturns: valid true, or valid false with every problem as {path, message}, where path is a JSON path such as $.rules["inline.bold"].after.\nDo not use when: you want to store it (save-markdown-template validates too).\nSafety: read-only. Pass exactly one of name (a saved or built-in template), template (JSON object or text) or templateFile. templateFile reads only a regular .json file in home, temp or /Volumes, refusing hidden paths (~/.docker, .env) and ~/Library outside iCloud Drive and CloudStorage unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; errors never quote the file.',
|
|
54833
54824
|
inputSchema: validateInput,
|
|
54834
54825
|
outputSchema: loose,
|
|
54835
54826
|
annotations: { readOnlyHint: true }
|
|
@@ -54870,7 +54861,7 @@ function registerMarkdownTemplates(server2, store = () => new TemplateStore()) {
|
|
|
54870
54861
|
server2.registerTool(
|
|
54871
54862
|
"save-markdown-template",
|
|
54872
54863
|
{
|
|
54873
|
-
description: "Use when: storing a template so export-notes-markdown can use it by name.\nReturns: name, path, bytes and whether an existing template was replaced.\nDo not use when: exporting once (pass templateFile to export-notes-markdown instead).\nSafety: writes one file in the template library only (APPLE_NOTES_MCP_TEMPLATE_DIR, default ~/Library/Application Support/apple-notes-mcp/templates, mode 0600). Validates first; an invalid template is refused with every JSON path. Create-only: an existing name is refused with [template-exists] unless force is true. Built-in names are reserved.",
|
|
54864
|
+
description: "Use when: storing a template so export-notes-markdown can use it by name.\nReturns: name, path, bytes and whether an existing template was replaced.\nDo not use when: exporting once (pass templateFile to export-notes-markdown instead).\nSafety: writes one file in the template library only (APPLE_NOTES_MCP_TEMPLATE_DIR, default ~/Library/Application Support/apple-notes-mcp/templates, mode 0600). Validates first; an invalid template is refused with every JSON path. Create-only: an existing name is refused with [template-exists] unless force is true. Built-in names are reserved. templateFile follows the same read scope as validate-markdown-template.",
|
|
54874
54865
|
inputSchema: saveInput,
|
|
54875
54866
|
outputSchema: loose,
|
|
54876
54867
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
@@ -54913,7 +54904,6 @@ function registerMarkdownTemplates(server2, store = () => new TemplateStore()) {
|
|
|
54913
54904
|
|
|
54914
54905
|
// src/utils/svgAnalyzer.ts
|
|
54915
54906
|
import { createHash as createHash5 } from "node:crypto";
|
|
54916
|
-
import { closeSync as closeSync9, constants as constants9, fstatSync as fstatSync9, openSync as openSync9, readSync as readSync7 } from "node:fs";
|
|
54917
54907
|
|
|
54918
54908
|
// src/utils/svgColor.ts
|
|
54919
54909
|
var NAMED = {
|
|
@@ -55756,7 +55746,7 @@ function decodeEntities(raw, line) {
|
|
|
55756
55746
|
if (body in PREDEFINED) return PREDEFINED[body];
|
|
55757
55747
|
throw new SvgError(
|
|
55758
55748
|
"svg_unsafe",
|
|
55759
|
-
`
|
|
55749
|
+
`A named entity on line ${line} is not allowed: only the five XML entities are`
|
|
55760
55750
|
);
|
|
55761
55751
|
});
|
|
55762
55752
|
}
|
|
@@ -55827,6 +55817,8 @@ function parseXml(source, limits) {
|
|
|
55827
55817
|
if (!NAME_START.test(source[i] ?? "")) fail("Invalid tag");
|
|
55828
55818
|
while (i < source.length && NAME_CHAR.test(source[i])) i++;
|
|
55829
55819
|
const name = source.slice(pos + 1, i);
|
|
55820
|
+
if (!root && !stack.length && name.slice(name.indexOf(":") + 1) !== "svg")
|
|
55821
|
+
fail("The root element is not <svg>");
|
|
55830
55822
|
const rawAttributes = [];
|
|
55831
55823
|
const attributeNames = /* @__PURE__ */ new Set();
|
|
55832
55824
|
let selfClosing = false;
|
|
@@ -56944,50 +56936,12 @@ function analyzeSvgBuffer(source) {
|
|
|
56944
56936
|
const viewport = analyzer.run();
|
|
56945
56937
|
return analysisFor(source, analyzer, viewport);
|
|
56946
56938
|
}
|
|
56947
|
-
function readSvgSource(path10) {
|
|
56948
|
-
let fd;
|
|
56949
|
-
try {
|
|
56950
|
-
fd = openSync9(path10, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
|
|
56951
|
-
} catch (error2) {
|
|
56952
|
-
if (error2.code === "ELOOP")
|
|
56953
|
-
throw new SvgError("svg_file_invalid", "The SVG path is a symbolic link");
|
|
56954
|
-
throw new SvgError("svg_file_invalid", "The SVG file does not exist or cannot be read");
|
|
56955
|
-
}
|
|
56956
|
-
try {
|
|
56957
|
-
const info = fstatSync9(fd);
|
|
56958
|
-
if (!info.isFile())
|
|
56959
|
-
throw new SvgError("svg_file_invalid", "The SVG path is not a regular file");
|
|
56960
|
-
if (info.size > SVG_LIMITS.maxSourceBytes)
|
|
56961
|
-
throw new SvgError(
|
|
56962
|
-
"svg_file_invalid",
|
|
56963
|
-
`The SVG is larger than ${SVG_LIMITS.maxSourceBytes} bytes`
|
|
56964
|
-
);
|
|
56965
|
-
const buffer = Buffer.alloc(SVG_LIMITS.maxSourceBytes + 1);
|
|
56966
|
-
let total = 0;
|
|
56967
|
-
for (; ; ) {
|
|
56968
|
-
const n = readSync7(fd, buffer, total, buffer.length - total, null);
|
|
56969
|
-
if (n === 0) break;
|
|
56970
|
-
total += n;
|
|
56971
|
-
if (total > SVG_LIMITS.maxSourceBytes)
|
|
56972
|
-
throw new SvgError(
|
|
56973
|
-
"svg_file_invalid",
|
|
56974
|
-
`The SVG is larger than ${SVG_LIMITS.maxSourceBytes} bytes`
|
|
56975
|
-
);
|
|
56976
|
-
}
|
|
56977
|
-
return buffer.subarray(0, total);
|
|
56978
|
-
} finally {
|
|
56979
|
-
closeSync9(fd);
|
|
56980
|
-
}
|
|
56981
|
-
}
|
|
56982
|
-
function analyzeSvgFile(path10) {
|
|
56983
|
-
return analyzeSvgBuffer(readSvgSource(path10));
|
|
56984
|
-
}
|
|
56985
56939
|
|
|
56986
56940
|
// src/tools/svgAnalysis.ts
|
|
56987
56941
|
function runSvgAnalysis(args, roots = allowedSaveRoots()) {
|
|
56988
|
-
let
|
|
56942
|
+
let source;
|
|
56989
56943
|
try {
|
|
56990
|
-
|
|
56944
|
+
source = readAllowedFile(args.path, SVG_LIMITS.maxSourceBytes, { roots, label: "SVG file" });
|
|
56991
56945
|
} catch (error2) {
|
|
56992
56946
|
throw new CodedError(error2.message, {
|
|
56993
56947
|
code: "validation_error",
|
|
@@ -56995,7 +56949,7 @@ function runSvgAnalysis(args, roots = allowedSaveRoots()) {
|
|
|
56995
56949
|
});
|
|
56996
56950
|
}
|
|
56997
56951
|
try {
|
|
56998
|
-
const result =
|
|
56952
|
+
const result = analyzeSvgBuffer(source);
|
|
56999
56953
|
return {
|
|
57000
56954
|
...result.analysis,
|
|
57001
56955
|
...args.includeDrawing ? { drawing: result.drawing } : {}
|
|
@@ -57013,7 +56967,7 @@ function runSvgAnalysis(args, roots = allowedSaveRoots()) {
|
|
|
57013
56967
|
function registerSvgAnalysis(server2) {
|
|
57014
56968
|
const inputSchema = {
|
|
57015
56969
|
path: external_exports.string().min(1).max(4096).describe(
|
|
57016
|
-
`Absolute path of one .svg file (home, temp, or /Volumes; not a symbolic link; at most ${SVG_LIMITS.maxSourceBytes} bytes of UTF-8)`
|
|
56970
|
+
`Absolute path of one .svg file (home, temp, or /Volumes; not a hidden path or ~/Library outside iCloud Drive and CloudStorage; not a symbolic link; at most ${SVG_LIMITS.maxSourceBytes} bytes of UTF-8)`
|
|
57017
56971
|
),
|
|
57018
56972
|
includeDrawing: external_exports.boolean().optional().describe(
|
|
57019
56973
|
"Also return the normalized drawing (strokes with sRGB color, width and points). Off by default: it can be large."
|
|
@@ -57022,7 +56976,7 @@ function registerSvgAnalysis(server2) {
|
|
|
57022
56976
|
server2.registerTool(
|
|
57023
56977
|
"analyze-svg",
|
|
57024
56978
|
{
|
|
57025
|
-
description: "Use when: checking whether an SVG file can be represented as editable monoline strokes, and what that conversion would approximate or drop.\nReturns: classification (safe, lossy, unsupported), importable, defaultWriteAllowed, requiredLosses (geometry-approximation, paint-approximation, drop-content), issues with element locations, counts and work budgets, the source SHA-256, and analysisDigest, a SHA-256 over the canonical analysis and normalized drawing. includeDrawing adds the drawing.\nDo not use when: you want to attach the SVG file itself to a note (add-attachment).\nSafety: read-only and local. Opens no Notes data and makes no network request. Scripts, event handlers, style elements, animation, DOCTYPE/entities and external resources are refused with svgCode svg_unsafe; malformed files with svg_invalid; oversized work with svg_complexity_limit.",
|
|
56979
|
+
description: "Use when: checking whether an SVG file can be represented as editable monoline strokes, and what that conversion would approximate or drop.\nReturns: classification (safe, lossy, unsupported), importable, defaultWriteAllowed, requiredLosses (geometry-approximation, paint-approximation, drop-content), issues with element locations, counts and work budgets, the source SHA-256, and analysisDigest, a SHA-256 over the canonical analysis and normalized drawing. includeDrawing adds the drawing.\nDo not use when: you want to attach the SVG file itself to a note (add-attachment).\nSafety: read-only and local. Opens no Notes data and makes no network request. Reads only a regular file in home, temp or /Volumes, refusing hidden paths (~/.ssh, .env) and ~/Library outside iCloud Drive and CloudStorage unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; a file whose root element is not <svg> is refused without quoting it. Scripts, event handlers, style elements, animation, DOCTYPE/entities and external resources are refused with svgCode svg_unsafe; malformed files with svg_invalid; oversized work with svg_complexity_limit.",
|
|
57026
56980
|
inputSchema,
|
|
57027
56981
|
outputSchema: external_exports.object({ classification: external_exports.string().optional() }).passthrough(),
|
|
57028
56982
|
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
@@ -57165,7 +57119,7 @@ var common = {
|
|
|
57165
57119
|
id,
|
|
57166
57120
|
expectedContentHash: revision3,
|
|
57167
57121
|
scopeText: external_exports.string().min(12).max(500).describe(
|
|
57168
|
-
"Distinctive existing phrase used by Notes search. Prefer plain words without punctuation, hashtags, or paths."
|
|
57122
|
+
"Distinctive existing phrase from below the title line, used by Notes search. Prefer plain words without punctuation, hashtags, or paths."
|
|
57169
57123
|
)
|
|
57170
57124
|
};
|
|
57171
57125
|
var EMPTY_TABLE_ROWS = [
|
|
@@ -62209,7 +62163,7 @@ var exportStatsSchema = external_exports.object({
|
|
|
62209
62163
|
registerTool(
|
|
62210
62164
|
"export-notes-markdown",
|
|
62211
62165
|
{
|
|
62212
|
-
description: "Use when: exporting one note (by exact id) or a folder's notes as one Markdown document rendered from the decoded note body: headings, bulleted/dashed/numbered lists with indent, checklists with state, block quotes, monospaced blocks, bold/italic/strikethrough/underline/highlight, links, tables, and attachments in body order.\nReturns: the Markdown inline (capped by APPLE_NOTES_MCP_EXPORT_MAX_BYTES), or with outputPath a receipt {format, count, bytes, output}; plus attachment counts and skipped notes (for example password-protected ones).\nDo not use when: you need the legacy HTML-converted Markdown of one note (get-note-markdown) or a restorable backup (export-notes-json). A folder document separates notes with '---' and is a presentation format, not something to import back.\nSafety: read-only against Notes; requires Full Disk Access. outputPath is create-only (an existing file is refused with [output_exists]); assetsDir copies attachment files without replacing existing ones (collisions get -2, -3 suffixes). Without assetsDir, attachments render as labeled placeholders.\nTemplates: pass template ('standard-markdown' reproduces the default output; 'obsidian' adds YAML front matter and copies attachments beside outputPath) or templateFile (a JSON template) to control how every block, inline style, attachment, per-note header/footer and separator renders. Templated receipts add template, warnings (e.g. missing_asset) and assetFiles; an invalid template is refused with [invalid-template] and every problem's JSON path.",
|
|
62166
|
+
description: "Use when: exporting one note (by exact id) or a folder's notes as one Markdown document rendered from the decoded note body: headings, bulleted/dashed/numbered lists with indent, checklists with state, block quotes, monospaced blocks, bold/italic/strikethrough/underline/highlight, links, tables, and attachments in body order.\nReturns: the Markdown inline (capped by APPLE_NOTES_MCP_EXPORT_MAX_BYTES), or with outputPath a receipt {format, count, bytes, output}; plus attachment counts and skipped notes (for example password-protected ones).\nDo not use when: you need the legacy HTML-converted Markdown of one note (get-note-markdown) or a restorable backup (export-notes-json). A folder document separates notes with '---' and is a presentation format, not something to import back.\nSafety: read-only against Notes; requires Full Disk Access. outputPath is create-only (an existing file is refused with [output_exists]); assetsDir copies attachment files without replacing existing ones (collisions get -2, -3 suffixes). Without assetsDir, attachments render as labeled placeholders.\nTemplates: pass template ('standard-markdown' reproduces the default output; 'obsidian' adds YAML front matter and copies attachments beside outputPath) or templateFile (a JSON template; hidden paths and ~/Library outside iCloud Drive and CloudStorage are refused unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1) to control how every block, inline style, attachment, per-note header/footer and separator renders. Templated receipts add template, warnings (e.g. missing_asset) and assetFiles; an invalid template is refused with [invalid-template] and every problem's JSON path.",
|
|
62213
62167
|
inputSchema: {
|
|
62214
62168
|
id: noteIdInput.optional(),
|
|
62215
62169
|
folder: external_exports.string().min(1).max(MAX.FOLDER).optional().describe("Folder path to export instead of one note (nested paths use '/')"),
|
|
@@ -62224,7 +62178,7 @@ registerTool(
|
|
|
62224
62178
|
`Render through this template: built-in ${BUILTIN_TEMPLATE_NAMES.map((n) => `'${n}'`).join(" or ")}, or a saved template's name (list-markdown-templates). Exclusive with templateFile`
|
|
62225
62179
|
),
|
|
62226
62180
|
templateFile: exportPathInput(
|
|
62227
|
-
"JSON template file (.json) to render through (exclusive with template; at most 256 KiB)"
|
|
62181
|
+
"JSON template file (.json) to render through (exclusive with template; at most 256 KiB; hidden paths and ~/Library outside iCloud Drive and CloudStorage refused unless APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1)"
|
|
62228
62182
|
)
|
|
62229
62183
|
},
|
|
62230
62184
|
outputSchema: {
|
|
@@ -25,7 +25,7 @@ Pass exactly one of these to `export-notes-markdown`:
|
|
|
25
25
|
| Parameter | Meaning |
|
|
26
26
|
| --- | --- |
|
|
27
27
|
| `template` | A built-in name (`standard-markdown` or `obsidian`) or a saved template's name |
|
|
28
|
-
| `templateFile` | An absolute path to a JSON template file ending in `.json` (home, a temp directory, or `/Volumes`; at most 256 KiB; symlinks refused) |
|
|
28
|
+
| `templateFile` | An absolute path to a JSON template file ending in `.json` (home, a temp directory, or `/Volumes`; at most 256 KiB; symlinks refused). Hidden paths and `~/Library` other than iCloud Drive and `~/Library/CloudStorage` are refused unless the server sets `APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1`. Errors name the file, never its contents |
|
|
29
29
|
|
|
30
30
|
The template is read and validated before any note is opened. An invalid
|
|
31
31
|
template is refused with `[invalid-template]` and one line per problem, each
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apple-notes-mcp",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.30",
|
|
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",
|