apple-notes-mcp 2.8.15 → 2.8.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.
package/README.md
CHANGED
|
@@ -59,22 +59,30 @@ This method also installs a **skill** that teaches Claude when and how to use Ap
|
|
|
59
59
|
On the first tool call, macOS shows an Automation permission prompt ("Claude" wants access to control "Notes") — click **OK**. Optionally, grant **Full Disk Access** to the app that launches the server to enable the database-backed tools (`get-checklist-state`, `get-note-metadata`, `get-note-link`, checklist annotations in `get-note-markdown`, and full `get-sync-status` detail); see the [Full Disk Access Setup Guide](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md). The rest of the server is pure AppleScript and works without it.
|
|
60
60
|
|
|
61
61
|
Native tag, checklist, table, pin, and rich append operations use two packaged
|
|
62
|
-
Apple Shortcuts.
|
|
62
|
+
Apple Shortcuts. A third, `Apple Notes MCP - Create Markdown Note`, is optional:
|
|
63
|
+
only `create-note`'s `format: "markdown"` needs it, and only on macOS 26 or
|
|
64
|
+
later. Run the explicit setup once:
|
|
63
65
|
|
|
64
66
|
```bash
|
|
65
67
|
npx -y apple-notes-mcp setup
|
|
66
68
|
```
|
|
67
69
|
|
|
68
70
|
The command checks existing installations and opens only missing signed
|
|
69
|
-
workflows
|
|
70
|
-
|
|
71
|
+
workflows; it opens the optional Create Markdown Note bridge only on macOS 26 or
|
|
72
|
+
later. Confirm **Add Shortcut** in each macOS window, then verify with
|
|
73
|
+
`npx -y apple-notes-mcp setup --check` or the MCP `doctor` tool. `setup --check`
|
|
74
|
+
reports ready and `doctor` reports ok once the two required bridges are
|
|
75
|
+
installed; both list the optional bridge's status separately. macOS does not
|
|
71
76
|
support silent Shortcut import, so merely connecting an MCP client never opens
|
|
72
77
|
setup windows or bypasses these confirmations.
|
|
73
78
|
|
|
74
79
|
After install **and after every upgrade**, open Shortcuts.app and run each
|
|
75
|
-
bridge — `Apple Notes MCP - Native Tags
|
|
76
|
-
`Apple Notes MCP - Background Operations v5`
|
|
77
|
-
|
|
80
|
+
installed bridge — `Apple Notes MCP - Native Tags`,
|
|
81
|
+
`Apple Notes MCP - Background Operations v5` and, if you installed it, the
|
|
82
|
+
optional `Apple Notes MCP - Create Markdown Note` — once in the foreground, choosing
|
|
83
|
+
**Always Allow** when Shortcuts asks for permission. The Create Markdown Note
|
|
84
|
+
bridge stops before reaching Notes when run with no input, so start its run
|
|
85
|
+
with the request in [`shortcuts/README.md`](shortcuts/README.md). The server runs these
|
|
78
86
|
Shortcuts in the background, where Shortcuts cannot display a first-run consent
|
|
79
87
|
prompt: an unanswered one stalls every native write on that bridge until it
|
|
80
88
|
times out, while `doctor` still reports the bridge installed. Quitting or
|
|
@@ -202,10 +210,10 @@ Creates a new note in Apple Notes.
|
|
|
202
210
|
|-----------|------|----------|-------------|
|
|
203
211
|
| `title` | string | Yes | The title of the note. Automatically prepended as `<h1>` — do NOT include the title in `content` |
|
|
204
212
|
| `content` | string | Yes | The body content of the note (do not repeat the title here) |
|
|
205
|
-
| `tags` | string[] | No | Returned-only metadata — **NOT written to Notes.app**. Apple Notes tags can't be set via AppleScript, so values passed here are echoed back in the response but do not appear on the created note. Use inline `#hashtags` in `content` instead (Notes.app turns those into real tags) |
|
|
213
|
+
| `tags` | string[] | No | Returned-only metadata — **NOT written to Notes.app**. Apple Notes tags can't be set via AppleScript, so values passed here are echoed back in the response but do not appear on the created note. Use inline `#hashtags` in `content` instead (Notes.app turns those into real tags). Refused with `format: "markdown"` |
|
|
206
214
|
| `folder` | string | No | Folder to create the note in. Supports nested paths like `"Work/Clients"`. **The folder must already exist** — create it first with [`create-folder`](#create-folder). Defaults to account root |
|
|
207
215
|
| `account` | string | No | Account name (defaults to Notes.app's default account; matched exactly or by a *unique* prefix — an ambiguous prefix is refused). Must be an account Notes.app already has configured — see [`list-accounts`](#list-accounts) |
|
|
208
|
-
| `format` | string | No | Content format: `"plaintext"` (default) or `"
|
|
216
|
+
| `format` | string | No | Content format: `"plaintext"` (default), `"html"`, or `"markdown"`. In all formats, the title is automatically prepended as the note's title line. In plaintext mode, newlines become `<br>`, tabs become `<br>`, and backslashes are preserved as HTML entities. `"markdown"` produces real Title/Heading/Subheading styles through a Shortcut; see [Markdown notes](#markdown-notes) |
|
|
209
217
|
|
|
210
218
|
**Example (tagged with inline hashtags):**
|
|
211
219
|
```json
|
|
@@ -235,13 +243,63 @@ Creates a new note in Apple Notes.
|
|
|
235
243
|
|
|
236
244
|
> **Note:** The title is automatically prepended as `<h1>` in both plaintext and HTML formats. Do not include a `<h1>` title tag in the `content` parameter, or the title will appear twice.
|
|
237
245
|
|
|
238
|
-
> **Known limitation:** `create-note` sets the note
|
|
239
|
-
> `body` property, which does not apply real Notes
|
|
240
|
-
> content — an `<h2>`/`<h3>` tag or a
|
|
241
|
-
> in `content` renders as plain bold,
|
|
242
|
-
>
|
|
243
|
-
>
|
|
244
|
-
>
|
|
246
|
+
> **Known limitation:** with `"plaintext"` or `"html"`, `create-note` sets the note
|
|
247
|
+
> body directly via AppleScript's `body` property, which does not apply real Notes
|
|
248
|
+
> paragraph styles for interior content — an `<h2>`/`<h3>` tag or a
|
|
249
|
+
> `<span style="font-size: …px">` heading span in `content` renders as plain bold,
|
|
250
|
+
> styled text, not an actual Heading or Subheading
|
|
251
|
+
> ([#172](https://github.com/sweetrb/apple-notes-mcp/issues/172)). Use
|
|
252
|
+
> `format: "markdown"` for real headings in a new note, or
|
|
253
|
+
> [`append-native`](#append-native)'s `format: "markdown"` on a note that already
|
|
254
|
+
> exists.
|
|
255
|
+
|
|
256
|
+
##### Markdown notes
|
|
257
|
+
|
|
258
|
+
`format: "markdown"` creates the note with Notes' own Markdown importer (the
|
|
259
|
+
Create Note action's "Interpret as Markdown" option, macOS 26+), run through the
|
|
260
|
+
packaged `Apple Notes MCP - Create Markdown Note` Shortcut. `#`, `##` and `###`
|
|
261
|
+
become real Title, Heading and Subheading styles, and the note starts with the
|
|
262
|
+
title line, with no seed line.
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"title": "Project Plan",
|
|
267
|
+
"content": "## Goals\n\n- Ship the beta\n- Collect feedback\n\n### Links\n\n[Tracker](https://example.com/tracker)",
|
|
268
|
+
"format": "markdown",
|
|
269
|
+
"folder": "Work"
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
- Notes interprets Markdown only in an iCloud account. The note is created in the
|
|
274
|
+
iCloud account's default folder, then moved to `folder` in that account.
|
|
275
|
+
`folder` must already exist and is checked before anything is created. If it
|
|
276
|
+
exists only in another account, the note is still created and verified in the
|
|
277
|
+
iCloud default folder, and the error names that account and the note's id so
|
|
278
|
+
you can create the folder there and `move-note` it instead of creating the
|
|
279
|
+
note again. `account` is refused with this format.
|
|
280
|
+
- `tags` are refused with this format. Create the note without them, then add
|
|
281
|
+
native tags to the returned id with [`add-native-tags`](#add-native-tags).
|
|
282
|
+
- `content` accepts the same bounded subset as `append-native`'s Markdown:
|
|
283
|
+
`#`/`##`/`###` headings, flat lists, `**bold**`, `*italic*` and inline links.
|
|
284
|
+
It also refuses Markdown that Notes would rewrite and the server could not
|
|
285
|
+
verify: `_` emphasis (underscores inside a word, as in `snake_case`, and
|
|
286
|
+
inside a link destination, as in `[docs](https://example.com/_next/static)`,
|
|
287
|
+
are fine), backslash escapes, character references such as `&`, `---` or
|
|
288
|
+
`===` lines, indented headings or list items, `1)` lists, closing `#`s, and
|
|
289
|
+
formatting inside link labels. Content that needs one of these literally, such
|
|
290
|
+
as a `/_next` path or a literal `\*`, has no Markdown form here: use
|
|
291
|
+
`format: "html"` for that note (or `append-native` with `format: "html"` on an
|
|
292
|
+
existing one), which keeps the characters but not the Heading and Subheading
|
|
293
|
+
styles. Markdown punctuation in `title` is escaped, so the title stays literal.
|
|
294
|
+
- The server finds the new note among the notes added to the default folder
|
|
295
|
+
during the run by verifying each one's visible text, heading levels and links
|
|
296
|
+
by exact-ID readback, and moves it only after exactly one verifies. On any
|
|
297
|
+
uncertain result it names the note (or says to search for the title) and
|
|
298
|
+
never retries.
|
|
299
|
+
- It is gated like the other native operations; `get-capabilities` reports it
|
|
300
|
+
as `create-note-markdown`. The Create Markdown Note Shortcut is optional and
|
|
301
|
+
needed only for this format (macOS 26+); install and approve it as described in
|
|
302
|
+
[`shortcuts/README.md`](shortcuts/README.md).
|
|
245
303
|
|
|
246
304
|
**Returns:** Confirmation message with note title and ID. Save the ID for subsequent operations like `update-note`, `delete-note`, etc.
|
|
247
305
|
|
|
@@ -1073,9 +1131,11 @@ Install the signed workflows once as described in
|
|
|
1073
1131
|
foreground in Shortcuts.app and choose **Always Allow** — again after every
|
|
1074
1132
|
upgrade — so no first-run consent prompt is left for a background run that
|
|
1075
1133
|
cannot display it. Native writes never use UI
|
|
1076
|
-
automation or write directly to the Notes database. Every operation
|
|
1077
|
-
exact note ID, a fresh `expectedContentHash`, and a
|
|
1078
|
-
`scopeText` so the Shortcut and server can independently
|
|
1134
|
+
automation or write directly to the Notes database. Every operation on an
|
|
1135
|
+
existing note requires an exact note ID, a fresh `expectedContentHash`, and a
|
|
1136
|
+
distinctive existing `scopeText` so the Shortcut and server can independently
|
|
1137
|
+
resolve the same note. Creating a note from Markdown is
|
|
1138
|
+
[`create-note`](#create-note) with `format: "markdown"`.
|
|
1079
1139
|
|
|
1080
1140
|
#### `get-capabilities`
|
|
1081
1141
|
|
|
@@ -1095,7 +1155,10 @@ subset as `format: "html"`'s Markdown-shaped content (`#`/`##`/`###` headings,
|
|
|
1095
1155
|
flat lists, emphasis, and inline links) but is sent through Notes' own native
|
|
1096
1156
|
Markdown importer rather than converted to HTML first, so `#`/`##`/`###`
|
|
1097
1157
|
produce real Title/Heading/Subheading — Notes' HTML importer only
|
|
1098
|
-
distinguishes two heading levels and renders `###` the same as `##`.
|
|
1158
|
+
distinguishes two heading levels and renders `###` the same as `##`. Markdown
|
|
1159
|
+
that the importer would rewrite, so the appended text could not be verified, is
|
|
1160
|
+
refused before anything is written; the list is under
|
|
1161
|
+
[Markdown notes](#markdown-notes). A
|
|
1099
1162
|
transport failure names the Shortcut it was waiting on, so a missing or
|
|
1100
1163
|
unapproved bridge is identified rather than guessed at; a timeout also says it
|
|
1101
1164
|
may be an unanswered first-run consent prompt and names the Shortcut to run once
|
|
@@ -1417,7 +1480,7 @@ In a JSON string literal the two characters `\\` denote **one** literal backslas
|
|
|
1417
1480
|
### Native writes time out or report an uncertain outcome
|
|
1418
1481
|
- Symptom: `add-native-tags`, `set-note-pinned`, `append-native` or another native write fails with "Shortcuts timed out waiting for …", "Operation outcome uncertain" or "readback was not verified", while `doctor` and `get-capabilities` report the bridges installed
|
|
1419
1482
|
- Common cause, especially right after install or upgrade: the bridge Shortcut is waiting on a first-run consent prompt. The server runs it in the background, where Shortcuts cannot display that prompt, so the run stalls until it times out
|
|
1420
|
-
- Fix: open Shortcuts.app, run the Shortcut the error names (`Apple Notes MCP - Native Tags
|
|
1483
|
+
- Fix: open Shortcuts.app, run the Shortcut the error names (`Apple Notes MCP - Native Tags`, `Apple Notes MCP - Background Operations v5` or `Apple Notes MCP - Create Markdown Note`) once in the foreground and choose **Always Allow**. Each bridge needs this once; quitting or relaunching Shortcuts.app or Notes.app does not clear it
|
|
1421
1484
|
- Read the exact note before retrying — the write may have landed
|
|
1422
1485
|
|
|
1423
1486
|
### "Note not found"
|
package/build/index.js
CHANGED
|
@@ -42923,6 +42923,8 @@ function comparableVisibleText(html) {
|
|
|
42923
42923
|
var BACKGROUND_SHORTCUT = "Apple Notes MCP - Background Operations v5";
|
|
42924
42924
|
var backgroundStatus = () => nativeTagsStatus(process.env.APPLE_NOTES_MCP_BACKGROUND_SHORTCUT || BACKGROUND_SHORTCUT);
|
|
42925
42925
|
var nativeTagBridgeStatus = () => nativeTagsStatus();
|
|
42926
|
+
var MARKDOWN_NOTE_SHORTCUT = "Apple Notes MCP - Create Markdown Note";
|
|
42927
|
+
var markdownNoteStatus = () => nativeTagsStatus(process.env.APPLE_NOTES_MCP_MARKDOWN_SHORTCUT || MARKDOWN_NOTE_SHORTCUT);
|
|
42926
42928
|
function readBackgroundSnapshot(manager, id2) {
|
|
42927
42929
|
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(id2))
|
|
42928
42930
|
throw new Error("Exact note ID required");
|
|
@@ -42974,6 +42976,17 @@ var NATIVE_APPEND_ELEMENTS = [
|
|
|
42974
42976
|
];
|
|
42975
42977
|
var NATIVE_APPEND_SPAN_STYLE = /^font-size\s*:\s*\d{1,3}(?:\.\d+)?(?:px|pt)\s*;?$/i;
|
|
42976
42978
|
var NATIVE_APPEND_HTML_SUBSET = `Native append accepts ${NATIVE_APPEND_ELEMENTS.map((e) => `<${e}>`).join(" ")}, with href on <a> and a font-size style on <span> as the only attributes; everything else needs update-note.`;
|
|
42979
|
+
var UNDERSCORES_OUTSIDE_A_WORD = "underscores outside a word";
|
|
42980
|
+
var UNMODELED_MARKDOWN = [
|
|
42981
|
+
[/(?<![\p{L}\p{N}])_|_(?![\p{L}\p{N}])/mu, UNDERSCORES_OUTSIDE_A_WORD],
|
|
42982
|
+
[/\\[!-/:-@[-`{-~]/m, "backslash escapes"],
|
|
42983
|
+
[/&(?:#\d+|#x[0-9a-f]+|[a-z][a-z0-9]*);/im, "character references"],
|
|
42984
|
+
[/^ {0,3}(?:=+|-+|(?:\*[ \t]*){3,})[ \t]*$/m, "underline or rule lines"],
|
|
42985
|
+
[/^ {1,3}(?:#|[-+*][ \t]|\d+[.)][ \t])/m, "indented headings or list items"],
|
|
42986
|
+
[/^\d+\)[ \t]/m, "`1)` lists"],
|
|
42987
|
+
[/^#{1,3}[ \t].*[ \t]#+[ \t]*$/m, "closing # sequences"],
|
|
42988
|
+
[/\[[^\]\n]*[*_][^\]\n]*\]\(/m, "formatting inside link labels"]
|
|
42989
|
+
];
|
|
42977
42990
|
function validateAppendContent(content, format) {
|
|
42978
42991
|
if (!content || content.length > 1024 * 1024 || content.includes("\0"))
|
|
42979
42992
|
throw new Error("Invalid append content (limit 1 MiB)");
|
|
@@ -43003,11 +43016,18 @@ function validateAppendContent(content, format) {
|
|
|
43003
43016
|
}
|
|
43004
43017
|
if (/<!--|<!|<\?|<[^>]*$/u.test(content)) throw new Error("Unsupported HTML markup");
|
|
43005
43018
|
}
|
|
43006
|
-
if (format === "markdown"
|
|
43007
|
-
|
|
43019
|
+
if (format === "markdown") {
|
|
43020
|
+
if (/!\[|<\/?[a-z]|\]\(\s*(?:javascript|data|file):/i.test(content))
|
|
43021
|
+
throw new Error("Markdown images, raw HTML and local/executable links are unsupported");
|
|
43022
|
+
const withoutLinkDestinations = content.replace(/(\[[^\]\n]+\])\([^()\s]+\)/g, "$1()");
|
|
43023
|
+
for (const [pattern, name] of UNMODELED_MARKDOWN)
|
|
43024
|
+
if (pattern.test(name === UNDERSCORES_OUTSIDE_A_WORD ? withoutLinkDestinations : content))
|
|
43025
|
+
throw new Error(
|
|
43026
|
+
`Markdown cannot use ${name}; Notes would change that text, so the result could not be verified`
|
|
43027
|
+
);
|
|
43028
|
+
}
|
|
43008
43029
|
}
|
|
43009
|
-
function runBackgroundShortcut(input) {
|
|
43010
|
-
const status = backgroundStatus();
|
|
43030
|
+
function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
43011
43031
|
if (!status.installed)
|
|
43012
43032
|
throw new Error(
|
|
43013
43033
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
@@ -43109,6 +43129,11 @@ function assertPreserved(before, after, options = {}) {
|
|
|
43109
43129
|
}
|
|
43110
43130
|
}
|
|
43111
43131
|
}
|
|
43132
|
+
function describeTransportFailure(error2) {
|
|
43133
|
+
const detail = error2;
|
|
43134
|
+
const named = detail?.shortcut ? `the "${detail.shortcut}" Shortcut` : "the background Shortcut";
|
|
43135
|
+
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)}`;
|
|
43136
|
+
}
|
|
43112
43137
|
function mutateBackground(request, operation, data, verify, deps) {
|
|
43113
43138
|
if (request.scopeText.length < 12 || request.scopeText.length > 500 || /[\r\n\0]/u.test(request.scopeText))
|
|
43114
43139
|
throw new Error("Use a distinctive existing single-line scope of 12\u2013500 characters");
|
|
@@ -43128,9 +43153,7 @@ function mutateBackground(request, operation, data, verify, deps) {
|
|
|
43128
43153
|
deps.run({ ...data, operation, title: before.title, scopeText: request.scopeText });
|
|
43129
43154
|
} catch (error2) {
|
|
43130
43155
|
transportUncertain = true;
|
|
43131
|
-
|
|
43132
|
-
const named = detail?.shortcut ? `the "${detail.shortcut}" Shortcut` : "the background Shortcut";
|
|
43133
|
-
transportMessage = 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)}`;
|
|
43156
|
+
transportMessage = describeTransportFailure(error2);
|
|
43134
43157
|
}
|
|
43135
43158
|
const after = deps.read(request.id);
|
|
43136
43159
|
try {
|
|
@@ -43218,6 +43241,109 @@ function setNativeTag(manager, request) {
|
|
|
43218
43241
|
deps
|
|
43219
43242
|
);
|
|
43220
43243
|
}
|
|
43244
|
+
var literalMarkdown = (text) => text.replace(/[!-/:-@[-`{-~]/g, "\\$&");
|
|
43245
|
+
function headingLevels(html) {
|
|
43246
|
+
return [...html.matchAll(/<h([1-3])\b[^>]*>([\s\S]*?)<\/h\1>/gi)].filter((match) => comparableVisibleText(match[2])).map((match) => Number(match[1]));
|
|
43247
|
+
}
|
|
43248
|
+
function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
|
|
43249
|
+
if (/[\r\n\0]/u.test(request.title)) throw new Error("A Markdown note title must be one line");
|
|
43250
|
+
validateAppendContent(request.content, "markdown");
|
|
43251
|
+
const bodyHtml = appendMarkdownHtml(request.content);
|
|
43252
|
+
if (request.folder) buildFolderReference(request.folder);
|
|
43253
|
+
const expectedText = `${request.title} ${comparableVisibleText(bodyHtml)}`.replace(/\s+/gu, " ").trim();
|
|
43254
|
+
const expectedLevels = [1, ...headingLevels(bodyHtml)].join();
|
|
43255
|
+
const status = markdownNoteStatus();
|
|
43256
|
+
if (!status.installed)
|
|
43257
|
+
throw new Error(
|
|
43258
|
+
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
43259
|
+
);
|
|
43260
|
+
const segments = (path4) => JSON.stringify(splitFolderPath(path4).map((part) => part.toLocaleLowerCase()));
|
|
43261
|
+
if (request.folder) {
|
|
43262
|
+
const wanted = segments(request.folder);
|
|
43263
|
+
if (!manager.listAccounts().some(
|
|
43264
|
+
(account) => account.defaultFolder && manager.listFolders(account.name).some((folder) => segments(folder.name) === wanted)
|
|
43265
|
+
))
|
|
43266
|
+
throw new Error(
|
|
43267
|
+
`Folder "${request.folder}" does not exist; create it with create-folder first. Nothing was created`
|
|
43268
|
+
);
|
|
43269
|
+
}
|
|
43270
|
+
const defaultFolderNotes = () => new Map(
|
|
43271
|
+
manager.listAccounts().flatMap(
|
|
43272
|
+
(account) => account.defaultFolder ? manager.listNoteRefs(account.name, account.defaultFolder).map((note) => [note.id, account.name]) : []
|
|
43273
|
+
)
|
|
43274
|
+
);
|
|
43275
|
+
const before = defaultFolderNotes();
|
|
43276
|
+
let transportMessage = "";
|
|
43277
|
+
try {
|
|
43278
|
+
run(
|
|
43279
|
+
{
|
|
43280
|
+
operation: "create-markdown",
|
|
43281
|
+
text: `# ${literalMarkdown(request.title)}
|
|
43282
|
+
|
|
43283
|
+
${request.content}`
|
|
43284
|
+
},
|
|
43285
|
+
status
|
|
43286
|
+
);
|
|
43287
|
+
} catch (error2) {
|
|
43288
|
+
transportMessage = describeTransportFailure(error2);
|
|
43289
|
+
}
|
|
43290
|
+
const reason = (error2) => error2 instanceof Error ? error2.message : String(error2);
|
|
43291
|
+
let candidates = [];
|
|
43292
|
+
try {
|
|
43293
|
+
const created = [...defaultFolderNotes()].filter(([noteId3]) => !before.has(noteId3));
|
|
43294
|
+
candidates = created.map(([noteId3]) => noteId3);
|
|
43295
|
+
const failures = [];
|
|
43296
|
+
const verified = created.flatMap(([noteId3, account2]) => {
|
|
43297
|
+
try {
|
|
43298
|
+
const note2 = readBackgroundSnapshot(manager, noteId3);
|
|
43299
|
+
if (note2.rich.text.replace(/[\s\ufffc]+/gu, " ").trim() !== expectedText)
|
|
43300
|
+
throw new Error("Note text not verified");
|
|
43301
|
+
if (headingLevels(note2.html).join() !== expectedLevels)
|
|
43302
|
+
throw new Error("Heading styles not verified");
|
|
43303
|
+
assertAppendedHtmlLinks(0, note2.rich.links, bodyHtml);
|
|
43304
|
+
return [{ id: noteId3, account: account2, note: note2 }];
|
|
43305
|
+
} catch (error2) {
|
|
43306
|
+
failures.push(`${noteId3}: ${reason(error2)}`);
|
|
43307
|
+
return [];
|
|
43308
|
+
}
|
|
43309
|
+
});
|
|
43310
|
+
if (verified.length !== 1)
|
|
43311
|
+
throw new Error(
|
|
43312
|
+
verified.length ? `${verified.length} matching notes appeared (${verified.map((v) => v.id).join(", ")})` : created.length ? `no new note verified (${failures.join("; ")})` : "no new note was found in the default folder"
|
|
43313
|
+
);
|
|
43314
|
+
const { id: id2, account } = verified[0];
|
|
43315
|
+
candidates = [id2];
|
|
43316
|
+
let { note } = verified[0];
|
|
43317
|
+
if (request.folder) {
|
|
43318
|
+
const wanted = segments(request.folder);
|
|
43319
|
+
if (!manager.listFolders(account).some((folder) => segments(folder.name) === wanted))
|
|
43320
|
+
throw new Error(
|
|
43321
|
+
`created and verified in the ${account} default folder, but folder "${request.folder}" does not exist in ${account}; create it there, then use move-note with id ${id2} instead of creating the note again`
|
|
43322
|
+
);
|
|
43323
|
+
if (!manager.moveNoteById(id2, request.folder, account))
|
|
43324
|
+
throw new Error(
|
|
43325
|
+
`created and verified in the ${account} default folder, but not moved to "${request.folder}"; use move-note instead of creating it again`
|
|
43326
|
+
);
|
|
43327
|
+
note = readBackgroundSnapshot(manager, id2);
|
|
43328
|
+
}
|
|
43329
|
+
return {
|
|
43330
|
+
ok: true,
|
|
43331
|
+
id: id2,
|
|
43332
|
+
title: request.title,
|
|
43333
|
+
folder: request.folder,
|
|
43334
|
+
account,
|
|
43335
|
+
contentHash: note.hash,
|
|
43336
|
+
verified: true,
|
|
43337
|
+
...transportMessage ? {
|
|
43338
|
+
transportWarning: "Transport was uncertain; exact-ID readback verified the requested result"
|
|
43339
|
+
} : {}
|
|
43340
|
+
};
|
|
43341
|
+
} catch (error2) {
|
|
43342
|
+
throw new Error(
|
|
43343
|
+
`Operation outcome uncertain; ${candidates.length ? `read ${candidates.length === 1 ? "note" : "notes"} ${candidates.join(", ")}` : "search for the title"} before any retry: ${reason(error2)}${transportMessage ? `; ${transportMessage}` : ""}`
|
|
43344
|
+
);
|
|
43345
|
+
}
|
|
43346
|
+
}
|
|
43221
43347
|
|
|
43222
43348
|
// src/tools/doctor.ts
|
|
43223
43349
|
function runDoctor(manager) {
|
|
@@ -43256,10 +43382,11 @@ function runDoctor(manager) {
|
|
|
43256
43382
|
(name) => nativeTagsStatus(name)
|
|
43257
43383
|
);
|
|
43258
43384
|
const missing = bridgeStatuses.filter((status) => !status.installed);
|
|
43385
|
+
const markdown = markdownBridgeDetail();
|
|
43259
43386
|
checks.push({
|
|
43260
43387
|
name: "Native write Shortcuts",
|
|
43261
43388
|
status: missing.length ? "warn" : "ok",
|
|
43262
|
-
detail: missing.length ? `missing: ${missing.map((status) => status.shortcut).join(", ")}. Run apple-notes-mcp setup and approve Add Shortcut in macOS. ${consentReminder}` : `both native-write bridges are installed. ${consentReminder}`
|
|
43389
|
+
detail: missing.length ? `missing: ${missing.map((status) => status.shortcut).join(", ")}. Run apple-notes-mcp setup and approve Add Shortcut in macOS. ${markdown} ${consentReminder}` : `both native-write bridges are installed. ${markdown} ${consentReminder}`
|
|
43263
43390
|
});
|
|
43264
43391
|
} catch (error2) {
|
|
43265
43392
|
checks.push({
|
|
@@ -43272,6 +43399,14 @@ function runDoctor(manager) {
|
|
|
43272
43399
|
const healthy = !checks.some((c) => c.status === "fail");
|
|
43273
43400
|
return { healthy, checks };
|
|
43274
43401
|
}
|
|
43402
|
+
function markdownBridgeDetail() {
|
|
43403
|
+
const purpose = 'needed only for create-note format: "markdown" on macOS 26+';
|
|
43404
|
+
try {
|
|
43405
|
+
return nativeTagsStatus(MARKDOWN_NOTE_SHORTCUT).installed ? `Optional ${MARKDOWN_NOTE_SHORTCUT} bridge: installed (${purpose}).` : `Optional ${MARKDOWN_NOTE_SHORTCUT} bridge: not installed (${purpose}; apple-notes-mcp setup offers it).`;
|
|
43406
|
+
} catch (error2) {
|
|
43407
|
+
return `Optional ${MARKDOWN_NOTE_SHORTCUT} bridge: could not inspect (${purpose}): ${String(error2)}.`;
|
|
43408
|
+
}
|
|
43409
|
+
}
|
|
43275
43410
|
function checkNodeRuntimeSignature() {
|
|
43276
43411
|
const name = "Node runtime signature";
|
|
43277
43412
|
try {
|
|
@@ -43894,7 +44029,8 @@ var VERIFIED_BACKGROUND = /* @__PURE__ */ new Set([
|
|
|
43894
44029
|
"insert-note-link",
|
|
43895
44030
|
"set-note-pinned",
|
|
43896
44031
|
"remove-native-tags",
|
|
43897
|
-
"replace-native-tag"
|
|
44032
|
+
"replace-native-tag",
|
|
44033
|
+
"create-note-markdown"
|
|
43898
44034
|
]);
|
|
43899
44035
|
var LIVE_VALIDATION_BLOCKERS = {};
|
|
43900
44036
|
var signingRefusal = "Installed Shortcuts refuses to sign this Notes action (unsupported features); no background fallback is enabled";
|
|
@@ -43982,16 +44118,24 @@ function registerNativeOperations(server2, manager) {
|
|
|
43982
44118
|
"set-note-pinned",
|
|
43983
44119
|
"remove-native-tags",
|
|
43984
44120
|
"replace-native-tag",
|
|
43985
|
-
"insert-note-link"
|
|
44121
|
+
"insert-note-link",
|
|
44122
|
+
"create-note-markdown"
|
|
43986
44123
|
];
|
|
43987
44124
|
let tagBridgeInstalled = false;
|
|
43988
44125
|
try {
|
|
43989
44126
|
tagBridgeInstalled = nativeTagBridgeStatus().installed;
|
|
43990
44127
|
} catch {
|
|
43991
44128
|
}
|
|
44129
|
+
let markdownBridgeInstalled = false;
|
|
44130
|
+
try {
|
|
44131
|
+
markdownBridgeInstalled = markdownNoteStatus().installed;
|
|
44132
|
+
} catch {
|
|
44133
|
+
}
|
|
44134
|
+
const installed = (name) => name === "create-note-markdown" ? markdownBridgeInstalled : bridge.installed;
|
|
43992
44135
|
return {
|
|
43993
44136
|
bridge,
|
|
43994
44137
|
nativeTagBridgeInstalled: tagBridgeInstalled,
|
|
44138
|
+
markdownNoteBridgeInstalled: markdownBridgeInstalled,
|
|
43995
44139
|
mode: "background-only",
|
|
43996
44140
|
operations: Object.fromEntries(
|
|
43997
44141
|
native.map((name) => [
|
|
@@ -43999,8 +44143,8 @@ function registerNativeOperations(server2, manager) {
|
|
|
43999
44143
|
{
|
|
44000
44144
|
implemented: true,
|
|
44001
44145
|
verified: VERIFIED_BACKGROUND.has(name),
|
|
44002
|
-
available: VERIFIED_BACKGROUND.has(name) && (
|
|
44003
|
-
reason: !VERIFIED_BACKGROUND.has(name) ? UNAVAILABLE[name] || LIVE_VALIDATION_BLOCKERS[name] || "Live validation pending; install the shortcut and complete the isolated acceptance tests" :
|
|
44146
|
+
available: VERIFIED_BACKGROUND.has(name) && installed(name) && (name !== "replace-native-tag" || tagBridgeInstalled),
|
|
44147
|
+
reason: !VERIFIED_BACKGROUND.has(name) ? UNAVAILABLE[name] || LIVE_VALIDATION_BLOCKERS[name] || "Live validation pending; install the shortcut and complete the isolated acceptance tests" : !installed(name) ? "Run apple-notes-mcp setup and approve Add Shortcut in macOS" : name === "replace-native-tag" && !tagBridgeInstalled ? "Run apple-notes-mcp setup to install the Native Tags bridge for the addition phase" : void 0
|
|
44004
44148
|
}
|
|
44005
44149
|
])
|
|
44006
44150
|
),
|
|
@@ -44217,13 +44361,21 @@ function registerNativeOperations(server2, manager) {
|
|
|
44217
44361
|
// src/setupShortcuts.ts
|
|
44218
44362
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
44219
44363
|
import { existsSync as existsSync7 } from "node:fs";
|
|
44364
|
+
import { release } from "node:os";
|
|
44220
44365
|
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
44221
44366
|
import { fileURLToPath } from "node:url";
|
|
44367
|
+
var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
|
|
44368
|
+
var MARKDOWN_MIN_DARWIN_MAJOR = 25;
|
|
44222
44369
|
var shortcutFiles = [
|
|
44223
44370
|
{ name: NATIVE_TAGS_SHORTCUT, file: "Apple Notes MCP - Native Tags.shortcut" },
|
|
44224
44371
|
{
|
|
44225
44372
|
name: BACKGROUND_SHORTCUT,
|
|
44226
44373
|
file: "Apple Notes MCP - Background Operations v5.shortcut"
|
|
44374
|
+
},
|
|
44375
|
+
{
|
|
44376
|
+
name: MARKDOWN_NOTE_SHORTCUT,
|
|
44377
|
+
file: "Apple Notes MCP - Create Markdown Note.shortcut",
|
|
44378
|
+
optional: true
|
|
44227
44379
|
}
|
|
44228
44380
|
];
|
|
44229
44381
|
function setupShortcuts(checkOnly, dependencies = {}) {
|
|
@@ -44234,7 +44386,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
44234
44386
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
44235
44387
|
});
|
|
44236
44388
|
const baseDirectory = dependencies.baseDirectory || resolve2(dirname2(fileURLToPath(import.meta.url)), "../shortcuts");
|
|
44237
|
-
const
|
|
44389
|
+
const osRelease = (dependencies.osRelease || release)();
|
|
44390
|
+
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
44391
|
+
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
44238
44392
|
const path4 = resolve2(baseDirectory, file);
|
|
44239
44393
|
let installed = false;
|
|
44240
44394
|
let identifier;
|
|
@@ -44247,27 +44401,41 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
44247
44401
|
error2 = cause instanceof Error ? cause.message : String(cause);
|
|
44248
44402
|
}
|
|
44249
44403
|
let opened = false;
|
|
44404
|
+
let skipped;
|
|
44250
44405
|
if (!installed && !checkOnly) {
|
|
44251
|
-
if (!
|
|
44406
|
+
if (optional2 && !(darwinMajor >= MARKDOWN_MIN_DARWIN_MAJOR))
|
|
44407
|
+
skipped = `requires macOS 26 or later (this Mac reports Darwin ${osRelease})`;
|
|
44408
|
+
else if (!exists(path4)) error2 = `Packaged Shortcut is missing: ${path4}`;
|
|
44252
44409
|
else {
|
|
44253
44410
|
const result = open(path4);
|
|
44254
44411
|
opened = result.ok;
|
|
44255
44412
|
if (!result.ok) error2 = result.error || `Could not open ${file}`;
|
|
44256
44413
|
}
|
|
44257
44414
|
}
|
|
44258
|
-
return {
|
|
44415
|
+
return {
|
|
44416
|
+
name,
|
|
44417
|
+
installed,
|
|
44418
|
+
identifier,
|
|
44419
|
+
file: path4,
|
|
44420
|
+
opened,
|
|
44421
|
+
...optional2 ? { optional: optional2 } : {},
|
|
44422
|
+
...skipped ? { skipped } : {},
|
|
44423
|
+
...error2 ? { error: error2 } : {}
|
|
44424
|
+
};
|
|
44259
44425
|
});
|
|
44260
|
-
return { ready: items.every((item) => item.installed), checkOnly, items };
|
|
44426
|
+
return { ready: items.every((item) => item.optional || item.installed), checkOnly, items };
|
|
44261
44427
|
}
|
|
44262
44428
|
function formatShortcutSetup(report) {
|
|
44263
44429
|
const lines = ["Apple Notes MCP Shortcut setup", ""];
|
|
44264
44430
|
for (const item of report.items) {
|
|
44265
|
-
|
|
44266
|
-
|
|
44267
|
-
else lines.push(`\
|
|
44431
|
+
const note = item.optional ? ` ${OPTIONAL_BRIDGE_NOTE}` : "";
|
|
44432
|
+
if (item.installed) lines.push(`\u2713 ${item.name} (${item.identifier})${note}`);
|
|
44433
|
+
else if (item.opened) lines.push(`\u2192 ${item.name}: confirm \u201CAdd Shortcut\u201D in macOS${note}`);
|
|
44434
|
+
else if (item.skipped) lines.push(`\u2013 ${item.name}: skipped, ${item.skipped}${note}`);
|
|
44435
|
+
else lines.push(`\u2717 ${item.name}: ${item.error || "not installed"}${note}`);
|
|
44268
44436
|
}
|
|
44269
44437
|
lines.push("");
|
|
44270
|
-
if (report.ready) lines.push("Both Shortcut bridges are installed.");
|
|
44438
|
+
if (report.ready) lines.push("Both required Shortcut bridges are installed.");
|
|
44271
44439
|
else if (report.checkOnly) lines.push("Run `apple-notes-mcp setup` to open missing workflows.");
|
|
44272
44440
|
else
|
|
44273
44441
|
lines.push(
|
|
@@ -44383,9 +44551,11 @@ registerTool(
|
|
|
44383
44551
|
content: external_exports.string().min(1, "Content is required").max(MAX.CONTENT).describe(
|
|
44384
44552
|
'Note body. AppleScript cannot create true Apple Notes checklists \u2014 `<input type="checkbox">`, checklist CSS classes, and markdown `- [ ]` lines do not render as checkable items. To produce a checklist, create the note with a plain `<ul>` or `- ` list and convert it in Notes.app with \u21E7\u2318L.'
|
|
44385
44553
|
),
|
|
44386
|
-
format: external_exports.enum(["plaintext", "html"]).optional().default("plaintext").describe(
|
|
44554
|
+
format: external_exports.enum(["plaintext", "html", "markdown"]).optional().default("plaintext").describe(
|
|
44555
|
+
"Content format: 'plaintext' (default), 'html' for rich formatting, or 'markdown' for real Title/Heading/Subheading styles through the Create Markdown Note Shortcut (iCloud only; see get-capabilities)"
|
|
44556
|
+
),
|
|
44387
44557
|
tags: external_exports.array(external_exports.string().max(MAX.TAG)).max(MAX.TAGS).optional().describe(
|
|
44388
|
-
"Returned-only metadata \u2014 NOT written to Notes.app. Apple Notes tags can't be set via AppleScript, so any values passed here are echoed back in the response but do not appear on the created note. Use #hashtags in the body for searchable text; this does not create native tag objects. Native tags need the Notes Shortcuts action."
|
|
44558
|
+
"Returned-only metadata \u2014 NOT written to Notes.app. Apple Notes tags can't be set via AppleScript, so any values passed here are echoed back in the response but do not appear on the created note. Use #hashtags in the body for searchable text; this does not create native tag objects. Native tags need the Notes Shortcuts action. Refused with format 'markdown': add tags afterwards with add-native-tags."
|
|
44389
44559
|
),
|
|
44390
44560
|
folder: external_exports.string().max(MAX.FOLDER).optional().describe(
|
|
44391
44561
|
"Folder to create the note in (supports nested paths like 'Work/Clients'). The folder must already exist \u2014 this tool does not create it; call create-folder first, which is idempotent and creates intermediate segments."
|
|
@@ -44405,6 +44575,19 @@ registerTool(
|
|
|
44405
44575
|
}
|
|
44406
44576
|
},
|
|
44407
44577
|
withErrorHandling(({ title, content, format = "plaintext", tags = [], folder, account }) => {
|
|
44578
|
+
if (format === "markdown") {
|
|
44579
|
+
if (account)
|
|
44580
|
+
return errorResponse(
|
|
44581
|
+
"Markdown notes are created in the iCloud account, the only one where Notes interprets Markdown; omit account"
|
|
44582
|
+
);
|
|
44583
|
+
if (tags.length)
|
|
44584
|
+
return errorResponse(
|
|
44585
|
+
'tags are not supported with format "markdown"; create the note without tags, then add them with add-native-tags using the returned id'
|
|
44586
|
+
);
|
|
44587
|
+
requireValidated("create-note-markdown");
|
|
44588
|
+
const result = createMarkdownNote(notesManager, { title, content, folder });
|
|
44589
|
+
return successResponse(`Note created from Markdown: "${title}" [id: ${result.id}]`, result);
|
|
44590
|
+
}
|
|
44408
44591
|
const note = notesManager.createNote(title, content, tags, folder, account, format);
|
|
44409
44592
|
if (!note) {
|
|
44410
44593
|
const target = folder ? ` Most often the folder "${folder}" does not exist: run list-folders to check, then create-folder to create it (it is idempotent and creates intermediate segments).` : account ? ` Most often the account "${account}" is not configured in Notes.app: run list-accounts to check the exact name.` : "";
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>WFWorkflowName</key>
|
|
6
|
+
<string>Apple Notes MCP - Create Markdown Note</string>
|
|
7
|
+
<key>WFWorkflowActions</key>
|
|
8
|
+
<array>
|
|
9
|
+
<dict>
|
|
10
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
11
|
+
<string>is.workflow.actions.comment</string>
|
|
12
|
+
<key>WFWorkflowActionParameters</key>
|
|
13
|
+
<dict>
|
|
14
|
+
<key>UUID</key>
|
|
15
|
+
<string>9677FA88-6915-5FA3-A68B-56A5E5257DE6</string>
|
|
16
|
+
<key>WFCommentActionText</key>
|
|
17
|
+
<string>Creates one note from Markdown with Notes' own importer, in the iCloud default folder. Input: JSON {operation: create-markdown, text}. No search, edits, deletion, shell scripts, network or arbitrary action execution. The MCP finds the new note among notes added to the default folder and verifies it by exact-ID readback.</string>
|
|
18
|
+
</dict>
|
|
19
|
+
</dict>
|
|
20
|
+
<dict>
|
|
21
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
22
|
+
<string>is.workflow.actions.detect.dictionary</string>
|
|
23
|
+
<key>WFWorkflowActionParameters</key>
|
|
24
|
+
<dict>
|
|
25
|
+
<key>UUID</key>
|
|
26
|
+
<string>C826AE0C-31B7-508F-8AA6-2C1627425D3B</string>
|
|
27
|
+
<key>WFInput</key>
|
|
28
|
+
<dict>
|
|
29
|
+
<key>Value</key>
|
|
30
|
+
<dict>
|
|
31
|
+
<key>Type</key>
|
|
32
|
+
<string>ExtensionInput</string>
|
|
33
|
+
</dict>
|
|
34
|
+
<key>WFSerializationType</key>
|
|
35
|
+
<string>WFTextTokenAttachment</string>
|
|
36
|
+
</dict>
|
|
37
|
+
</dict>
|
|
38
|
+
</dict>
|
|
39
|
+
<dict>
|
|
40
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
41
|
+
<string>is.workflow.actions.getvalueforkey</string>
|
|
42
|
+
<key>WFWorkflowActionParameters</key>
|
|
43
|
+
<dict>
|
|
44
|
+
<key>UUID</key>
|
|
45
|
+
<string>D8DFFD6F-2EF2-5F63-8B77-9578929A96CD</string>
|
|
46
|
+
<key>WFInput</key>
|
|
47
|
+
<dict>
|
|
48
|
+
<key>Value</key>
|
|
49
|
+
<dict>
|
|
50
|
+
<key>Type</key>
|
|
51
|
+
<string>ActionOutput</string>
|
|
52
|
+
<key>OutputUUID</key>
|
|
53
|
+
<string>C826AE0C-31B7-508F-8AA6-2C1627425D3B</string>
|
|
54
|
+
<key>OutputName</key>
|
|
55
|
+
<string>request</string>
|
|
56
|
+
</dict>
|
|
57
|
+
<key>WFSerializationType</key>
|
|
58
|
+
<string>WFTextTokenAttachment</string>
|
|
59
|
+
</dict>
|
|
60
|
+
<key>WFDictionaryKey</key>
|
|
61
|
+
<string>operation</string>
|
|
62
|
+
<key>WFGetDictionaryValueType</key>
|
|
63
|
+
<string>Value</string>
|
|
64
|
+
</dict>
|
|
65
|
+
</dict>
|
|
66
|
+
<dict>
|
|
67
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
68
|
+
<string>is.workflow.actions.gettext</string>
|
|
69
|
+
<key>WFWorkflowActionParameters</key>
|
|
70
|
+
<dict>
|
|
71
|
+
<key>UUID</key>
|
|
72
|
+
<string>BA3704CC-7103-5406-A7A9-4ACA96514A95</string>
|
|
73
|
+
<key>WFTextActionText</key>
|
|
74
|
+
<dict>
|
|
75
|
+
<key>WFSerializationType</key>
|
|
76
|
+
<string>WFTextTokenString</string>
|
|
77
|
+
<key>Value</key>
|
|
78
|
+
<dict>
|
|
79
|
+
<key>string</key>
|
|
80
|
+
<string></string>
|
|
81
|
+
<key>attachmentsByRange</key>
|
|
82
|
+
<dict>
|
|
83
|
+
<key>{0, 1}</key>
|
|
84
|
+
<dict>
|
|
85
|
+
<key>Type</key>
|
|
86
|
+
<string>ActionOutput</string>
|
|
87
|
+
<key>OutputUUID</key>
|
|
88
|
+
<string>D8DFFD6F-2EF2-5F63-8B77-9578929A96CD</string>
|
|
89
|
+
<key>OutputName</key>
|
|
90
|
+
<string>operation-value</string>
|
|
91
|
+
</dict>
|
|
92
|
+
</dict>
|
|
93
|
+
</dict>
|
|
94
|
+
</dict>
|
|
95
|
+
</dict>
|
|
96
|
+
</dict>
|
|
97
|
+
<dict>
|
|
98
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
99
|
+
<string>is.workflow.actions.getvalueforkey</string>
|
|
100
|
+
<key>WFWorkflowActionParameters</key>
|
|
101
|
+
<dict>
|
|
102
|
+
<key>UUID</key>
|
|
103
|
+
<string>9EC8FFF3-7821-592E-86A4-BC712A669591</string>
|
|
104
|
+
<key>WFInput</key>
|
|
105
|
+
<dict>
|
|
106
|
+
<key>Value</key>
|
|
107
|
+
<dict>
|
|
108
|
+
<key>Type</key>
|
|
109
|
+
<string>ActionOutput</string>
|
|
110
|
+
<key>OutputUUID</key>
|
|
111
|
+
<string>C826AE0C-31B7-508F-8AA6-2C1627425D3B</string>
|
|
112
|
+
<key>OutputName</key>
|
|
113
|
+
<string>request</string>
|
|
114
|
+
</dict>
|
|
115
|
+
<key>WFSerializationType</key>
|
|
116
|
+
<string>WFTextTokenAttachment</string>
|
|
117
|
+
</dict>
|
|
118
|
+
<key>WFDictionaryKey</key>
|
|
119
|
+
<string>text</string>
|
|
120
|
+
<key>WFGetDictionaryValueType</key>
|
|
121
|
+
<string>Value</string>
|
|
122
|
+
</dict>
|
|
123
|
+
</dict>
|
|
124
|
+
<dict>
|
|
125
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
126
|
+
<string>is.workflow.actions.gettext</string>
|
|
127
|
+
<key>WFWorkflowActionParameters</key>
|
|
128
|
+
<dict>
|
|
129
|
+
<key>UUID</key>
|
|
130
|
+
<string>68CEF93A-EBC3-5F0C-831D-B459A0AEA703</string>
|
|
131
|
+
<key>WFTextActionText</key>
|
|
132
|
+
<dict>
|
|
133
|
+
<key>WFSerializationType</key>
|
|
134
|
+
<string>WFTextTokenString</string>
|
|
135
|
+
<key>Value</key>
|
|
136
|
+
<dict>
|
|
137
|
+
<key>string</key>
|
|
138
|
+
<string></string>
|
|
139
|
+
<key>attachmentsByRange</key>
|
|
140
|
+
<dict>
|
|
141
|
+
<key>{0, 1}</key>
|
|
142
|
+
<dict>
|
|
143
|
+
<key>Type</key>
|
|
144
|
+
<string>ActionOutput</string>
|
|
145
|
+
<key>OutputUUID</key>
|
|
146
|
+
<string>9EC8FFF3-7821-592E-86A4-BC712A669591</string>
|
|
147
|
+
<key>OutputName</key>
|
|
148
|
+
<string>text-value</string>
|
|
149
|
+
</dict>
|
|
150
|
+
</dict>
|
|
151
|
+
</dict>
|
|
152
|
+
</dict>
|
|
153
|
+
</dict>
|
|
154
|
+
</dict>
|
|
155
|
+
<dict>
|
|
156
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
157
|
+
<string>is.workflow.actions.conditional</string>
|
|
158
|
+
<key>WFWorkflowActionParameters</key>
|
|
159
|
+
<dict>
|
|
160
|
+
<key>UUID</key>
|
|
161
|
+
<string>C49370C1-C96A-567A-91A8-75AB9A8E9695</string>
|
|
162
|
+
<key>GroupingIdentifier</key>
|
|
163
|
+
<string>0DD3CC74-CF2E-5A9D-AE11-C1DC205557E8</string>
|
|
164
|
+
<key>WFControlFlowMode</key>
|
|
165
|
+
<integer>0</integer>
|
|
166
|
+
<key>WFInput</key>
|
|
167
|
+
<dict>
|
|
168
|
+
<key>Type</key>
|
|
169
|
+
<string>Variable</string>
|
|
170
|
+
<key>Variable</key>
|
|
171
|
+
<dict>
|
|
172
|
+
<key>Value</key>
|
|
173
|
+
<dict>
|
|
174
|
+
<key>Type</key>
|
|
175
|
+
<string>ActionOutput</string>
|
|
176
|
+
<key>OutputUUID</key>
|
|
177
|
+
<string>BA3704CC-7103-5406-A7A9-4ACA96514A95</string>
|
|
178
|
+
<key>OutputName</key>
|
|
179
|
+
<string>operation</string>
|
|
180
|
+
</dict>
|
|
181
|
+
<key>WFSerializationType</key>
|
|
182
|
+
<string>WFTextTokenAttachment</string>
|
|
183
|
+
</dict>
|
|
184
|
+
</dict>
|
|
185
|
+
<key>WFCondition</key>
|
|
186
|
+
<integer>4</integer>
|
|
187
|
+
<key>WFConditionalActionString</key>
|
|
188
|
+
<string>create-markdown</string>
|
|
189
|
+
</dict>
|
|
190
|
+
</dict>
|
|
191
|
+
<dict>
|
|
192
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
193
|
+
<string>com.apple.mobilenotes.SharingExtension</string>
|
|
194
|
+
<key>WFWorkflowActionParameters</key>
|
|
195
|
+
<dict>
|
|
196
|
+
<key>UUID</key>
|
|
197
|
+
<string>ACE3922A-BF8C-5266-88EF-5492F1E16DD9</string>
|
|
198
|
+
<key>WFCreateNoteInput</key>
|
|
199
|
+
<dict>
|
|
200
|
+
<key>WFSerializationType</key>
|
|
201
|
+
<string>WFTextTokenString</string>
|
|
202
|
+
<key>Value</key>
|
|
203
|
+
<dict>
|
|
204
|
+
<key>string</key>
|
|
205
|
+
<string></string>
|
|
206
|
+
<key>attachmentsByRange</key>
|
|
207
|
+
<dict>
|
|
208
|
+
<key>{0, 1}</key>
|
|
209
|
+
<dict>
|
|
210
|
+
<key>Type</key>
|
|
211
|
+
<string>ActionOutput</string>
|
|
212
|
+
<key>OutputUUID</key>
|
|
213
|
+
<string>68CEF93A-EBC3-5F0C-831D-B459A0AEA703</string>
|
|
214
|
+
<key>OutputName</key>
|
|
215
|
+
<string>text</string>
|
|
216
|
+
</dict>
|
|
217
|
+
</dict>
|
|
218
|
+
</dict>
|
|
219
|
+
</dict>
|
|
220
|
+
<key>interpretAsMarkdown</key>
|
|
221
|
+
<true/>
|
|
222
|
+
<key>OpenWhenRun</key>
|
|
223
|
+
<false/>
|
|
224
|
+
<key>WFNoteGroup</key>
|
|
225
|
+
<dict>
|
|
226
|
+
<key>DisplayString</key>
|
|
227
|
+
<string>Notes</string>
|
|
228
|
+
<key>Identifier</key>
|
|
229
|
+
<string>applenotes:folder/DefaultFolder-CloudKit</string>
|
|
230
|
+
</dict>
|
|
231
|
+
<key>folder</key>
|
|
232
|
+
<dict>
|
|
233
|
+
<key>identifier</key>
|
|
234
|
+
<string>applenotes:folder/DefaultFolder-CloudKit</string>
|
|
235
|
+
<key>subtitle</key>
|
|
236
|
+
<dict>
|
|
237
|
+
<key>key</key>
|
|
238
|
+
<string>Notes</string>
|
|
239
|
+
</dict>
|
|
240
|
+
<key>symbol</key>
|
|
241
|
+
<dict>
|
|
242
|
+
<key>systemName</key>
|
|
243
|
+
<string>folder</string>
|
|
244
|
+
</dict>
|
|
245
|
+
<key>title</key>
|
|
246
|
+
<dict>
|
|
247
|
+
<key>key</key>
|
|
248
|
+
<string>Notes</string>
|
|
249
|
+
</dict>
|
|
250
|
+
</dict>
|
|
251
|
+
<key>AppIntentDescriptor</key>
|
|
252
|
+
<dict>
|
|
253
|
+
<key>AppIntentIdentifier</key>
|
|
254
|
+
<string>CreateNoteLinkAction</string>
|
|
255
|
+
<key>BundleIdentifier</key>
|
|
256
|
+
<string>com.apple.Notes</string>
|
|
257
|
+
<key>TeamIdentifier</key>
|
|
258
|
+
<string>0000000000</string>
|
|
259
|
+
<key>Name</key>
|
|
260
|
+
<string>Notes</string>
|
|
261
|
+
</dict>
|
|
262
|
+
</dict>
|
|
263
|
+
</dict>
|
|
264
|
+
<dict>
|
|
265
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
266
|
+
<string>is.workflow.actions.gettext</string>
|
|
267
|
+
<key>WFWorkflowActionParameters</key>
|
|
268
|
+
<dict>
|
|
269
|
+
<key>UUID</key>
|
|
270
|
+
<string>01BCDBDD-C08D-5147-9FA4-EAA5AEE0249C</string>
|
|
271
|
+
<key>WFTextActionText</key>
|
|
272
|
+
<string>APPLE_NOTES_MARKDOWN_V1_DONE</string>
|
|
273
|
+
</dict>
|
|
274
|
+
</dict>
|
|
275
|
+
<dict>
|
|
276
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
277
|
+
<string>is.workflow.actions.output</string>
|
|
278
|
+
<key>WFWorkflowActionParameters</key>
|
|
279
|
+
<dict>
|
|
280
|
+
<key>UUID</key>
|
|
281
|
+
<string>3E99E727-244A-5162-808E-627E927B0BA7</string>
|
|
282
|
+
<key>WFOutput</key>
|
|
283
|
+
<dict>
|
|
284
|
+
<key>WFSerializationType</key>
|
|
285
|
+
<string>WFTextTokenString</string>
|
|
286
|
+
<key>Value</key>
|
|
287
|
+
<dict>
|
|
288
|
+
<key>string</key>
|
|
289
|
+
<string></string>
|
|
290
|
+
<key>attachmentsByRange</key>
|
|
291
|
+
<dict>
|
|
292
|
+
<key>{0, 1}</key>
|
|
293
|
+
<dict>
|
|
294
|
+
<key>Type</key>
|
|
295
|
+
<string>ActionOutput</string>
|
|
296
|
+
<key>OutputUUID</key>
|
|
297
|
+
<string>01BCDBDD-C08D-5147-9FA4-EAA5AEE0249C</string>
|
|
298
|
+
<key>OutputName</key>
|
|
299
|
+
<string>op-create-markdown-ok</string>
|
|
300
|
+
</dict>
|
|
301
|
+
</dict>
|
|
302
|
+
</dict>
|
|
303
|
+
</dict>
|
|
304
|
+
</dict>
|
|
305
|
+
</dict>
|
|
306
|
+
<dict>
|
|
307
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
308
|
+
<string>is.workflow.actions.conditional</string>
|
|
309
|
+
<key>WFWorkflowActionParameters</key>
|
|
310
|
+
<dict>
|
|
311
|
+
<key>UUID</key>
|
|
312
|
+
<string>64FA875D-F93B-5A6E-91E2-888D6F3192AF</string>
|
|
313
|
+
<key>GroupingIdentifier</key>
|
|
314
|
+
<string>0DD3CC74-CF2E-5A9D-AE11-C1DC205557E8</string>
|
|
315
|
+
<key>WFControlFlowMode</key>
|
|
316
|
+
<integer>2</integer>
|
|
317
|
+
</dict>
|
|
318
|
+
</dict>
|
|
319
|
+
<dict>
|
|
320
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
321
|
+
<string>is.workflow.actions.gettext</string>
|
|
322
|
+
<key>WFWorkflowActionParameters</key>
|
|
323
|
+
<dict>
|
|
324
|
+
<key>UUID</key>
|
|
325
|
+
<string>E3837313-32E2-513F-9F22-48146FAB7FCC</string>
|
|
326
|
+
<key>WFTextActionText</key>
|
|
327
|
+
<string>APPLE_NOTES_MARKDOWN_V1_REFUSED</string>
|
|
328
|
+
</dict>
|
|
329
|
+
</dict>
|
|
330
|
+
<dict>
|
|
331
|
+
<key>WFWorkflowActionIdentifier</key>
|
|
332
|
+
<string>is.workflow.actions.output</string>
|
|
333
|
+
<key>WFWorkflowActionParameters</key>
|
|
334
|
+
<dict>
|
|
335
|
+
<key>UUID</key>
|
|
336
|
+
<string>461330AD-B74B-54C8-BE23-04649AA3DCE8</string>
|
|
337
|
+
<key>WFOutput</key>
|
|
338
|
+
<dict>
|
|
339
|
+
<key>WFSerializationType</key>
|
|
340
|
+
<string>WFTextTokenString</string>
|
|
341
|
+
<key>Value</key>
|
|
342
|
+
<dict>
|
|
343
|
+
<key>string</key>
|
|
344
|
+
<string></string>
|
|
345
|
+
<key>attachmentsByRange</key>
|
|
346
|
+
<dict>
|
|
347
|
+
<key>{0, 1}</key>
|
|
348
|
+
<dict>
|
|
349
|
+
<key>Type</key>
|
|
350
|
+
<string>ActionOutput</string>
|
|
351
|
+
<key>OutputUUID</key>
|
|
352
|
+
<string>E3837313-32E2-513F-9F22-48146FAB7FCC</string>
|
|
353
|
+
<key>OutputName</key>
|
|
354
|
+
<string>refused</string>
|
|
355
|
+
</dict>
|
|
356
|
+
</dict>
|
|
357
|
+
</dict>
|
|
358
|
+
</dict>
|
|
359
|
+
</dict>
|
|
360
|
+
</dict>
|
|
361
|
+
</array>
|
|
362
|
+
<key>WFWorkflowClientVersion</key>
|
|
363
|
+
<string>2600</string>
|
|
364
|
+
<key>WFWorkflowMinimumClientVersion</key>
|
|
365
|
+
<integer>900</integer>
|
|
366
|
+
<key>WFWorkflowMinimumClientVersionString</key>
|
|
367
|
+
<string>900</string>
|
|
368
|
+
<key>WFWorkflowTypes</key>
|
|
369
|
+
<array/>
|
|
370
|
+
<key>WFWorkflowHasShortcutInputVariables</key>
|
|
371
|
+
<true/>
|
|
372
|
+
<key>WFWorkflowHasOutputAction</key>
|
|
373
|
+
<true/>
|
|
374
|
+
<key>WFWorkflowInputContentItemClasses</key>
|
|
375
|
+
<array>
|
|
376
|
+
<string>WFStringContentItem</string>
|
|
377
|
+
<string>WFDictionaryContentItem</string>
|
|
378
|
+
<string>WFGenericFileContentItem</string>
|
|
379
|
+
</array>
|
|
380
|
+
<key>WFWorkflowOutputContentItemClasses</key>
|
|
381
|
+
<array>
|
|
382
|
+
<string>WFStringContentItem</string>
|
|
383
|
+
</array>
|
|
384
|
+
<key>WFWorkflowIcon</key>
|
|
385
|
+
<dict>
|
|
386
|
+
<key>WFWorkflowIconStartColor</key>
|
|
387
|
+
<integer>4251333119</integer>
|
|
388
|
+
<key>WFWorkflowIconGlyphNumber</key>
|
|
389
|
+
<integer>59507</integer>
|
|
390
|
+
</dict>
|
|
391
|
+
<key>WFWorkflowImportQuestions</key>
|
|
392
|
+
<array/>
|
|
393
|
+
</dict>
|
|
394
|
+
</plist>
|
package/shortcuts/README.md
CHANGED
|
@@ -49,3 +49,35 @@ Open the signed file and confirm **Add Shortcut** once, then run it once in the
|
|
|
49
49
|
foreground in Shortcuts.app and choose **Always Allow** (see above). Do not install obsolete
|
|
50
50
|
workflow versions with the same display name; the server resolves one exact
|
|
51
51
|
installed UUID and refuses duplicates.
|
|
52
|
+
|
|
53
|
+
## Create Markdown Note bridge
|
|
54
|
+
|
|
55
|
+
`Apple Notes MCP - Create Markdown Note.shortcut` creates one note with Notes'
|
|
56
|
+
Create Note action and "Interpret as Markdown" (macOS 26+, iCloud accounts only)
|
|
57
|
+
in the iCloud default folder. It is optional: only `create-note` with
|
|
58
|
+
`format: "markdown"` uses it, and neither `setup --check`'s readiness nor
|
|
59
|
+
`doctor`'s native-write check depends on it. It reads the same JSON request file and runs only
|
|
60
|
+
for `operation: "create-markdown"`; it has no search, edit or delete actions.
|
|
61
|
+
The server finds the new note by exact ID and verifies it before reporting
|
|
62
|
+
success.
|
|
63
|
+
|
|
64
|
+
Regenerate and test the unsigned source with:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-markdown-note-shortcut.py
|
|
68
|
+
PYTHONDONTWRITEBYTECODE=1 python3 scripts/build-markdown-note-shortcut.py
|
|
69
|
+
shortcuts sign --mode anyone \
|
|
70
|
+
--input "shortcuts/Apple Notes MCP - Create Markdown Note.unsigned.shortcut" \
|
|
71
|
+
--output "shortcuts/Apple Notes MCP - Create Markdown Note.shortcut"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Open the signed file and confirm **Add Shortcut** once. Then run it once in the
|
|
75
|
+
foreground so macOS can ask for Notes access, and choose **Always Allow**. A
|
|
76
|
+
background run cannot show that prompt and waits until the server times out.
|
|
77
|
+
Run by hand with no input, the Shortcut stops before reaching Notes, so start the
|
|
78
|
+
first run with a request instead. This creates a note titled "Apple Notes MCP
|
|
79
|
+
check", which you can delete:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
open "shortcuts://run-shortcut?name=Apple%20Notes%20MCP%20-%20Create%20Markdown%20Note&input=text&text=%7B%22operation%22%3A%22create-markdown%22%2C%22text%22%3A%22%23%20Apple%20Notes%20MCP%20check%22%7D"
|
|
83
|
+
```
|