apple-notes-mcp 2.9.14 → 2.9.16

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.
@@ -0,0 +1,248 @@
1
+ # Markdown export templates
2
+
3
+ `export-notes-markdown` can render notes through a template instead of its
4
+ fixed Markdown renderer. A template is portable JSON that says how each block
5
+ style, inline format, attachment, per-note header and footer, and the
6
+ separator between notes are written. It is data only: there are no
7
+ expressions, conditions or scripts. Each rule picks one of five modes and may
8
+ use `{{placeholder}}` tokens, which are replaced literally.
9
+
10
+ Templates change only the exported Markdown. They never change a note.
11
+
12
+ ## Choosing a template
13
+
14
+ Built-in and saved templates are managed with `list-markdown-templates`,
15
+ `show-markdown-template`, `validate-markdown-template`,
16
+ `save-markdown-template` and `delete-markdown-template`. Saved templates live
17
+ in `~/Library/Application Support/apple-notes-mcp/templates` (or the absolute
18
+ directory in `APPLE_NOTES_MCP_TEMPLATE_DIR`) as `<name>.json`, where the name
19
+ is a lowercase slug of `a-z`, `0-9`, `-` and `_`. Saving is create-only unless
20
+ `force` is true, and the built-in names are reserved. The library skips files
21
+ it cannot read or validate instead of trusting them.
22
+
23
+ Pass exactly one of these to `export-notes-markdown`:
24
+
25
+ | Parameter | Meaning |
26
+ | --- | --- |
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) |
29
+
30
+ The template is read and validated before any note is opened. An invalid
31
+ template is refused with `[invalid-template]` and one line per problem, each
32
+ with a JSON path:
33
+
34
+ ```text
35
+ Error exporting Markdown [invalid-template]: the template is invalid:
36
+ $.rules["inline.bold"].after: is required when mode is "wrap"
37
+ $.inlineOrder: must list every inline format; missing "link"
38
+ ```
39
+
40
+ Errors never quote the file's contents. A JSON syntax error reports only its
41
+ line and column, a wrong value reports the allowed values but not the one
42
+ given, and a file without `"schemaVersion": 1` is reported only as missing
43
+ it, with nothing else about the file.
44
+
45
+ ### Built-in templates
46
+
47
+ - **`standard-markdown`** reproduces the default export exactly. Use it as the
48
+ base for your own templates.
49
+ - **`obsidian`** extends `standard-markdown` with YAML front matter for every
50
+ note (title, created, modified, folder, tags, note id), renders link cards
51
+ whose URL is an image file as images (taking a following all-italic
52
+ paragraph as the alt text), and copies attachments into `<file>.assets`
53
+ beside `outputPath`.
54
+
55
+ ## A first template
56
+
57
+ This template keeps every standard rule and wraps blue highlights in a
58
+ callout. Listing `highlight` last in `inlineOrder` makes it the outermost
59
+ wrapper, so a bold blue run renders as `[callout]**text**[/callout]`.
60
+
61
+ ```json
62
+ {
63
+ "schemaVersion": 1,
64
+ "name": "editorial",
65
+ "inlineOrder": [
66
+ "subscript",
67
+ "superscript",
68
+ "underline",
69
+ "strikethrough",
70
+ "bold",
71
+ "italic",
72
+ "color",
73
+ "link",
74
+ "highlight"
75
+ ],
76
+ "rules": {
77
+ "inline.highlight.blue": { "mode": "wrap", "before": "[callout]", "after": "[/callout]" }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## Schema version 1
83
+
84
+ | Key | Required | Meaning |
85
+ | --- | --- | --- |
86
+ | `schemaVersion` | yes | The integer `1` |
87
+ | `name` | no | Display name, at most 512 characters |
88
+ | `description` | no | At most 512 characters |
89
+ | `extends` | no | `standard-markdown` (the default) or `obsidian` |
90
+ | `assets` | no | Merged field by field with the base |
91
+ | `inlineOrder` | no | Every inline format exactly once, innermost first |
92
+ | `rules` | no | Rule overrides keyed by rule id; each replaces the whole base rule |
93
+ | `options` | no | Merged field by field with the base |
94
+
95
+ Unknown keys, rule ids, rule fields, placeholders and modifiers are errors.
96
+
97
+ ### Rule modes
98
+
99
+ | Mode | Fields | Output |
100
+ | --- | --- | --- |
101
+ | `wrap` | `before`, `after` | `before` + content + `after` |
102
+ | `linePrefix` | `value` | `value` before every line of the content (a blank line gets the trimmed prefix) |
103
+ | `pattern` | `value` | `value`, with `{{content}}` where the content goes |
104
+ | `plain` | none | The content unchanged |
105
+ | `omit` | none | Nothing |
106
+
107
+ A field that the mode does not use is an error. Any rule may add
108
+ `"join": "line"` or `"join": "paragraph"`. Adjacent blocks whose rule joins by
109
+ `line` and that share a group stay on consecutive lines; everything else is
110
+ separated by a blank line. All list styles share one group, so a bulleted item
111
+ followed by a numbered item stays tight. For `attachment.gallery`, `join` sets
112
+ the spacing between gallery items.
113
+
114
+ Inline rules apply to a run's text without its leading and trailing
115
+ whitespace, which stays outside the wrapper. An inline rule set to `plain`
116
+ does not split runs, and one set to `omit` drops the text it covers.
117
+
118
+ ### Rules and their standard values
119
+
120
+ | Rule | Standard |
121
+ | --- | --- |
122
+ | `document.header` | `pattern` `""` (runs once per note, before it) |
123
+ | `document.footer` | `pattern` `""` (once per note, after it) |
124
+ | `document.separator` | `pattern` `"\n\n---\n\n"` (between notes; placeholders describe the next note) |
125
+ | `block.title` | `wrap` `"# "`, paragraph join |
126
+ | `block.heading` | `wrap` `"## "`, paragraph join |
127
+ | `block.subheading` | `wrap` `"### "`, paragraph join |
128
+ | `block.body` | `plain`, paragraph join |
129
+ | `block.bulleted`, `block.dashed` | `wrap` `"- "`, line join |
130
+ | `block.numbered` | `pattern` `"{{index}}. {{content}}"`, line join |
131
+ | `block.checklist.checked` | `wrap` `"- [x] "`, line join |
132
+ | `block.checklist.unchecked` | `wrap` `"- [ ] "`, line join |
133
+ | `block.code` | `wrap` `"{{fence}}\n"` / `"\n{{fence}}"`, paragraph join |
134
+ | `paragraph.quote` | `linePrefix` `"> "` (applied after the block's own rule) |
135
+ | `inline.bold` / `inline.italic` | `wrap` `**` / `*` |
136
+ | `inline.underline` / `inline.strikethrough` | `wrap` `<u>`…`</u>` / `~~` |
137
+ | `inline.superscript` / `inline.subscript` | `wrap` `<sup>`…`</sup>` / `<sub>`…`</sub>` |
138
+ | `inline.highlight.purple`, `.pink`, `.orange`, `.mint`, `.blue`, `.other` | `wrap` `==` |
139
+ | `inline.color` | `plain` |
140
+ | `inline.link` | `pattern` `"[{{content}}]({{url}})"` |
141
+ | `attachment.image`, `attachment.drawing.classic`, `attachment.drawing.paper` | `pattern` `"![{{alt}}]({{path}})"` |
142
+ | `attachment.scan`, `.pdf`, `.audio`, `.video`, `.other` | `pattern` `"[{{linkText}}]({{path}})"` |
143
+ | `attachment.table` | `plain` (content is a GitHub table) |
144
+ | `attachment.divider` | `pattern` `"---"` |
145
+ | `attachment.gallery` | `plain`, paragraph join (content is the gallery's items) |
146
+ | `attachment.url` / `attachment.map` | `pattern` `"[{{alt}}]({{url}})"` |
147
+ | `attachment.url.image` | `pattern` `"![{{alt}}]({{url}})"` |
148
+ | `attachment.placeholder` | `pattern` `"\\[{{content}}\\]"` (an attachment with no file, or a missing one) |
149
+
150
+ Drawings are exported through the image Notes stores for them. A link card to
151
+ `maps.apple.com` uses `attachment.map`. A link card whose URL is not http(s),
152
+ `notes:`, `applenotes:` or `mailto:` is always written as plain text. Hashtags
153
+ and mentions keep their text; inline note links use `inline.link`.
154
+
155
+ The standard `inlineOrder`, innermost first, is:
156
+
157
+ ```json
158
+ ["subscript", "superscript", "highlight", "underline", "strikethrough", "bold", "italic", "color", "link"]
159
+ ```
160
+
161
+ ### Placeholders
162
+
163
+ | Placeholder | Value |
164
+ | --- | --- |
165
+ | `{{content}}` | The rule's input |
166
+ | `{{title}}`, `{{id}}`, `{{uuid}}` | Note title, `x-coredata` id, and Notes UUID |
167
+ | `{{folder}}`, `{{account}}` | Folder and account names |
168
+ | `{{created}}`, `{{modified}}` | ISO 8601 UTC dates |
169
+ | `{{tags}}` | The note's hashtags without `#`, comma-separated |
170
+ | `{{exportStem}}` | `outputPath`'s file name without its extension (`export` without one) |
171
+ | `{{index}}`, `{{checked}}` | Numbered-list position; `true`/`false` for checklists |
172
+ | `{{url}}`, `{{path}}` | Link target; asset path (both escaped for a Markdown link) |
173
+ | `{{alt}}`, `{{linkText}}`, `{{caption}}` | Attachment name; its name or preview image; image-link caption |
174
+ | `{{filename}}`, `{{kind}}`, `{{uti}}` | Attachment file name, kind, and type identifier |
175
+ | `{{color}}`, `{{highlight}}` | Run color (`#RRGGBB`) and highlight name |
176
+ | `{{fence}}` | A backtick fence longer than any backtick run in the code |
177
+
178
+ Text values are escaped for Markdown. Add a modifier for other contexts:
179
+ `{{title:raw}}` inserts the value as stored, and `{{title:yaml}}` inserts a
180
+ double-quoted YAML string (tags become a YAML list, and a missing value becomes
181
+ `null`). A missing value is otherwise empty. This header writes YAML front
182
+ matter that stays valid whatever the title contains:
183
+
184
+ ```json
185
+ {
186
+ "schemaVersion": 1,
187
+ "rules": {
188
+ "document.header": {
189
+ "mode": "pattern",
190
+ "value": "---\ntitle: {{title:yaml}}\ncreated: {{created:yaml}}\ntags: {{tags:yaml}}\n---\n\n"
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ ### Options
197
+
198
+ | Option | Standard | Meaning |
199
+ | --- | --- | --- |
200
+ | `titleFallback` | `true` | Add a `block.title` line when the body does not start with the note's title |
201
+ | `richLinkImages` | `false` | Render http(s) link cards whose URL path ends in an image extension through `attachment.url.image` |
202
+ | `richLinkImageCaption` | `"none"` | `"followingItalicParagraph"`: use the next paragraph as the image's alt text (and `{{caption}}`) when the card stands alone in its paragraph and every character of the next body paragraph is italic; that paragraph is then not repeated |
203
+ | `listIndent` | four spaces | Added once per indent level before list items (at most 16 spaces or tabs) |
204
+
205
+ ## Assets
206
+
207
+ | Field | Standard | Values |
208
+ | --- | --- | --- |
209
+ | `mode` | `"copy"` | `copy` attachment files, link to the originals in place (`reference`), or `omit` file attachments entirely |
210
+ | `pathStyle` | `"relative"` | Links relative to `outputPath`'s directory, or `absolute` |
211
+ | `directory` | `null` | A relative directory beneath `outputPath`'s directory, with note placeholders such as `{{exportStem}}`, `{{folder}}` or `{{title}}` |
212
+
213
+ - In `copy` mode, files go to `assetsDir` when it is passed; otherwise to
214
+ `directory` when it is set and there is an `outputPath`. With neither,
215
+ attachments render through `attachment.placeholder`, and a template that
216
+ sets `directory` reports an `assets_dir_required` warning for each one.
217
+ - Copies are named with a sanitized stem and the first eight hex digits of
218
+ the file's SHA-256 (`photo-1a2b3c4d.jpg`), so a repeated export reuses the
219
+ same file. An existing file is never replaced: a different file under the
220
+ same name is reported as `asset_copy_failed`.
221
+ - `directory` must be relative with no `..` component. Placeholder values are
222
+ made safe as single path components, and the result must stay inside
223
+ `outputPath`'s directory, or the export is refused before anything is
224
+ written.
225
+ - `reference` writes links to the files inside the Notes library. Those links
226
+ work only on this Mac. `assetsDir` is refused in `reference` and `omit`
227
+ modes.
228
+ - Relative links need `outputPath`; without it, links are absolute.
229
+
230
+ ## Results and warnings
231
+
232
+ A templated export returns the usual receipt plus `template` (`name` and
233
+ `source`: `builtin`, `saved` or `file`), `warnings`, and `assetFiles` (the
234
+ absolute paths of the files copied or reused). Warnings do not stop the
235
+ export:
236
+
237
+ | Code | Meaning |
238
+ | --- | --- |
239
+ | `attachment_not_found` | The body names an attachment the note does not have |
240
+ | `inline_token_metadata_missing` | A hashtag, mention or note link has no text |
241
+ | `table_decode_failed` | A table could not be decoded |
242
+ | `missing_asset` | An attachment's file was not found |
243
+ | `asset_copy_failed` | A file could not be read or copied safely |
244
+ | `assets_dir_required` | The template wants copies but there is nowhere to put them |
245
+ | `gallery_children_missing` | A gallery has no items |
246
+
247
+ Each warning carries `noteId` and, when known, `attachmentId`. At most 200
248
+ are listed; `warningsOmitted` counts the rest.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.9.14",
3
+ "version": "2.9.16",
4
4
  "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",